返回

Android 清洁架构:巧妙处理 Fused Location Provider 的 ResolvableApiException

Android

清洁架构中处理 Fused Location Provider 的挑战

在 Android 开发中,将 com.google.android.gms.location 库集成到遵循清洁架构的项目中,开发者经常会面临一些挑战。理解如何在数据层中获取位置数据并将其推送回领域层和展现层至关重要。一个常见的问题是如何处理 settingsClient.checkLocationSettings(locationSettingsRequest) 方法中的 ResolvableApiException

ResolvableApiExceptionPendingIntent 的难题

settingsClient.checkLocationSettings() 方法用于检查设备的位置设置是否满足应用的需求。如果设置不满足要求,该方法会通过 addOnFailureListener 分支返回一个 ResolvableApiException,其中包含一个 PendingIntent。这个 PendingIntent 需要传递给 registerForActivityResult 才能启动一个 Activity 来解决位置设置问题。然而,在清洁架构中,所有这些操作都应该在数据层进行,将 PendingIntent 或者 Intent 作为 Any 对象传递回领域层再进行类型转换,感觉像是在破坏架构的完整性。

现有的代码及其局限性

提供的代码示例展示了在数据层中使用 FusedLocationProviderClientSettingsClient 来检查位置设置。然而,它并没有处理如何将 ResolvableApiException 中的 PendingIntent 传递到展现层来启动解析 Activity 的问题。

解決方案:定义中间层对象

为了避免直接传递 Android 相关的对象,我们可以定义一个中间层的数据对象,用于在数据层和领域层之间传递信息。

  1. 定义LocationSettingsResult密封类:
sealed class LocationSettingsResult {
    data class Success(val ready: Boolean): LocationSettingsResult()
    data class ResolutionRequired(val resolutionCode: Int): LocationSettingsResult()
    data class Failure(val exception: Exception): LocationSettingsResult()
}
  1. 修改isLocationTrackingReady()方法:
override suspend fun isLocationTrackingReady(): LocationSettingsResult = suspendCancellableCoroutine { continuation ->
    settingsClient.checkLocationSettings(locationSettingsRequest)
        .addOnSuccessListener {
            continuation.resume(LocationSettingsResult.Success(true))
        }
        .addOnFailureListener { exception ->
             if (exception is ResolvableApiException) {
                 continuation.resume(LocationSettingsResult.ResolutionRequired(exception.statusCode))
             } else {
                 continuation.resume(LocationSettingsResult.Failure(exception))
             }
        }
}
  1. 在 ViewModel 中处理结果:
viewModelScope.launch {
    when (val result = locationTracking.isLocationTrackingReady()) {
        is LocationSettingsResult.Success -> { /* 处理成功的情况 */ }
        is LocationSettingsResult.ResolutionRequired -> { 
            // 使用 startIntentSenderForResult 处理 resultCode
            startIntentSenderForResult(
                 // ...获取PendingIntent 的逻辑... ,
                result.resolutionCode,
                null,
                0, 0, 0,
                null
             )
         }
        is LocationSettingsResult.Failure -> { /* 处理失败的情况 */ }
    }
}


// 获取PendingIntent的示例方法, 注意,这个方法应该在你的data层
 suspend fun getPendingIntentForResolution(resultCode: Int): PendingIntent?{

  //.... 根据resultCode 构建或获取 PendingIntent
 }

在这个方案中,我们通过 resultCode 在 ViewModel 中获取PendingIntent 并使用 startIntentSenderForResult, 从而避免了直接传递 PendingIntent 对象。

解決方案二:利用回调机制

除了定义中间层对象外,还可以使用回调机制来处理 ResolvableApiException

  1. 定义回调接口:
interface LocationSettingsCallback {
    fun onResolutionRequired(resolutionCode: Int)
    fun onSuccess()
    fun onFailure(exception: Exception)
}
  1. 修改isLocationTrackingReady()方法:
 override fun isLocationTrackingReady(callback: LocationSettingsCallback) {
    settingsClient.checkLocationSettings(locationSettingsRequest)
        .addOnSuccessListener {
            callback.onSuccess()
        }
        .addOnFailureListener { exception ->
            if (exception is ResolvableApiException) {
                callback.onResolutionRequired(exception.statusCode)
            } else {
                callback.onFailure(exception)
            }
        }
}
  1. 在展现层实现回调:
class YourActivity : AppCompatActivity(), LocationSettingsCallback{

//.....

    private fun checkLocationSettings() {
      locationTracking.isLocationTrackingReady(this)
    }


    override fun onResolutionRequired(resolutionCode: Int) {
        // 使用 startIntentSenderForResult 处理 resultCode
          // ...获取PendingIntent 的逻辑...
     }

      //....实现其他回调方法...


}


安全建议

在处理位置信息时,请务必注意用户的隐私。 仅在绝对必要时才请求位置权限,并向用户清楚地说明为什么需要这些权限。 遵守所有适用的数据隐私法规和准则。

总结

选择哪种方法取决于你的具体需求和项目结构。 这两种方案都能有效地解决在清洁架构中处理 ResolvableApiException 的问题, 并避免直接传递 Android 相关的对象到领域层,从而保持架构的清晰和可维护性。 你还有其他更好的建议吗?

相关资源

希望这篇文章对你有帮助! 这两种方案你更倾向于哪一种呢?在评论区分享你的想法吧!