Clean Architecture下集成Fused Location Provider的定位设置处理难题
基于Clean Architecture处理谷歌定位设置解析的最优方案
针对你的问题,先直接否定你纠结的两个方案,再给出符合Clean Architecture原则的实现思路:
为什么你的两个方案都不合适
- 方案一(传递异常到ViewModel强转):完全违背Clean Architecture核心原则——领域层不应传递Android特定类型(
ResolvableApiException),展示层也不应直接依赖数据层的具体异常类,这会导致层级耦合度剧增,后续维护成本极高。 - 方案二(重复定义LocationRequest):会产生冗余代码,且两处
LocationRequest的配置容易出现不一致,后续修改定位参数时需要同步修改两处,极易出错。
符合Clean Architecture的解决方案
核心思路:领域层只处理纯业务抽象,数据层封装谷歌定位的具体实现,展示层负责Android特定的Intent/通知逻辑,通过事件驱动+抽象结果类型实现层级解耦。
1. 领域层定义抽象模型
领域层只保留与业务相关的纯Kotlin类型,不引入任何Android/第三方库:
// 定位配置参数(替代重复的LocationRequest) data class LocationConfig( val priority: LocationPriority, val intervalMs: Long, val fastestIntervalMs: Long ) enum class LocationPriority { HIGH_ACCURACY, BALANCED_POWER, LOW_POWER } // 定位设置检查结果(纯业务抽象) sealed class LocationSettingsResult { object Ready : LocationSettingsResult() object NeedsUserAction : LocationSettingsResult() data class Failed(val errorMsg: String) : LocationSettingsResult() } // 领域层用例 class CheckLocationSettingsUseCase @Inject constructor( private val locationDataSource: LocationDataSource ) { suspend operator fun invoke(config: LocationConfig): LocationSettingsResult { return locationDataSource.checkLocationSettings(config) } }
2. 数据层封装谷歌定位实现
数据层负责处理谷歌定位的具体逻辑,捕获ResolvableApiException后保存解析所需信息,返回领域层定义的抽象结果:
interface LocationDataSource { suspend fun checkLocationSettings(config: LocationConfig): LocationSettingsResult suspend fun resolveSettings(launcher: ActivityResultLauncher<IntentSenderRequest>): Boolean } class GoogleLocationDataSource @Inject constructor( private val settingsClient: SettingsClient ) : LocationDataSource { private var pendingException: ResolvableApiException? = null override suspend fun checkLocationSettings(config: LocationConfig): LocationSettingsResult { val googleLocationRequest = config.toGoogleLocationRequest() val settingsRequest = LocationSettingsRequest.Builder() .addLocationRequest(googleLocationRequest) .build() return try { settingsClient.checkLocationSettings(settingsRequest).await() LocationSettingsResult.Ready } catch (e: ResolvableApiException) { pendingException = e LocationSettingsResult.NeedsUserAction } catch (e: Exception) { LocationSettingsResult.Failed(e.message ?: "定位设置检查失败") } } override suspend fun resolveSettings(launcher: ActivityResultLauncher<IntentSenderRequest>): Boolean { val exception = pendingException ?: return false val resultDeferred = CompletableDeferred<Boolean>() launcher.launch(IntentSenderRequest.Builder(exception.resolution).build()) { result -> resultDeferred.complete(result.resultCode == Activity.RESULT_OK) pendingException = null } return resultDeferred.await() } // 转换领域层配置为谷歌定位请求 private fun LocationConfig.toGoogleLocationRequest(): LocationRequest { return LocationRequest.create().apply { priority = when (this@toGoogleLocationRequest.priority) { LocationPriority.HIGH_ACCURACY -> LocationRequest.PRIORITY_HIGH_ACCURACY LocationPriority.BALANCED_POWER -> LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY LocationPriority.LOW_POWER -> LocationRequest.PRIORITY_LOW_POWER } interval = intervalMs fastestInterval = fastestIntervalMs } } }
3. 展示层处理Android特定逻辑
展示层(Activity/Fragment)负责注册ActivityResultLauncher、监听事件、显示通知等操作:
// Activity中注册Launcher private val resolutionLauncher = registerForActivityResult( ActivityResultContracts.StartIntentSenderForResult() ) { result -> viewModel.onResolutionResult(result.resultCode == Activity.RESULT_OK) } // ViewModel处理事件流 class LocationViewModel @Inject constructor( private val checkSettingsUseCase: CheckLocationSettingsUseCase, private val locationDataSource: LocationDataSource ) : ViewModel() { private val _uiState = MutableStateFlow<LocationUiState>(LocationUiState.Idle) val uiState = _uiState.asStateFlow() private val _events = MutableSharedFlow<LocationEvent>() val events = _events.asSharedFlow() private lateinit var currentConfig: LocationConfig suspend fun initLocationTracking(config: LocationConfig) { currentConfig = config _uiState.value = LocationUiState.Loading when (val result = checkSettingsUseCase(config)) { LocationSettingsResult.Ready -> { _uiState.value = LocationUiState.TrackingStarted // 启动定位更新 } LocationSettingsResult.NeedsUserAction -> { _events.emit(LocationEvent.ShowResolutionPrompt) } is LocationSettingsResult.Failed -> { _uiState.value = LocationUiState.Error(result.errorMsg) } } } suspend fun resolveSettings() { val success = locationDataSource.resolveSettings(resolutionLauncher) if (success) initLocationTracking(currentConfig) else _uiState.value = LocationUiState.Error("定位设置未开启") } fun onResolutionResult(success: Boolean) { viewModelScope.launch { if (success) initLocationTracking(currentConfig) else _uiState.value = LocationUiState.Error("用户拒绝开启定位") } } } // UI状态与事件定义 sealed class LocationUiState { object Idle : LocationUiState() object Loading : LocationUiState() object TrackingStarted : LocationUiState() data class Error(val msg: String) : LocationUiState() } sealed class LocationEvent { object ShowResolutionPrompt : LocationEvent() } // Activity中监听事件,显示类似谷歌地图的通知 lifecycleScope.launch { viewModel.events.collect { event -> when (event) { LocationEvent.ShowResolutionPrompt -> showLocationSettingsNotification() } } } private fun showLocationSettingsNotification() { val settingsIntent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } val pendingIntent = PendingIntent.getActivity( this, 0, settingsIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) val notification = NotificationCompat.Builder(this, LOCATION_CHANNEL_ID) .setContentTitle("需要开启定位服务") .setContentText("点击前往设置开启定位") .setSmallIcon(R.drawable.ic_location) .setContentIntent(pendingIntent) .setAutoCancel(true) .build() NotificationManagerCompat.from(this).notify(LOCATION_NOTIFICATION_ID, notification) }
方案优势
- 完全符合Clean Architecture:领域层无Android依赖,数据层与展示层通过领域层抽象解耦
- 无冗余代码:定位配置统一由领域层定义,数据层负责转换,避免重复定义
LocationRequest - 职责清晰:数据层处理定位逻辑,展示层处理Android特定的Intent/通知,层级职责明确
内容的提问来源于stack exchange,提问作者No_Name
相关产品推荐
相关产品推荐

