You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
}

方案优势

  1. 完全符合Clean Architecture:领域层无Android依赖,数据层与展示层通过领域层抽象解耦
  2. 无冗余代码:定位配置统一由领域层定义,数据层负责转换,避免重复定义LocationRequest
  3. 职责清晰:数据层处理定位逻辑,展示层处理Android特定的Intent/通知,层级职责明确

内容的提问来源于stack exchange,提问作者No_Name

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 22:40:55