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

Android清洁架构中Google导航API的正确使用方案

在Android清洁架构中使用Google Navigation API的困惑

我在Android清洁架构中使用Google Navigation API时遇到了问题。要调用谷歌导航功能,必须通过NavigationApi.getNavigator获取Navigator对象。

一开始我想把所有相关实现放在Data层,但Data层的职责应该仅限于本地/远程数据的存储与访问,而Navigator高度依赖Presentation层的SupportNavigationFragment,这明显违反了下层不能依赖上层的架构原则。

后来我考虑把所有实现移到Fragment里,但不确定这种做法是否完全符合清洁架构的规范。有没有在清洁架构中使用过Google API的开发者能给点经验?

我需要实现的功能代码如下:

private var navigator: Navigator? = null

override fun initializeNavigator(activity: FragmentActivity) {
    NavigationApi.getNavigator(activity, object : NavigationApi.NavigatorListener {
        override fun onNavigatorReady(navigator: Navigator?) {
            navigator = navigator
            addListeners()
        }

        override fun onError(errorCode: Int) {
            handleError(errorCode)
        }
    })
}

override fun startNavigation() {
    navigator?.setAudioGuidance(Navigator.AudioGuidance.VOICE_ALERTS_AND_GUIDANCE)
    navigator?.startGuidance()
}

override fun stopNavigation() {
    navigator?.stopGuidance()
    navigator?.clearDestinations()
}


override fun getRouteSummary(waypoint: Waypoint, options: RoutingOptions) = callbackFlow {
    navigator?.let { navigator ->
        val pendingRoute = navigator.setDestination(waypoint, options)
        pendingRoute.setOnResultListener { code ->
            _navigationStateFlow.value = NavigationState.SUMMARY
            val result = trySend(
                NavigationSummary(
                    response = code,
                    meters = navigator.currentTimeAndDistance.meters,
                    seconds = navigator.currentTimeAndDistance.seconds
                )
            )
        }

        awaitClose {
            // 清理逻辑
        }
    }
}

private fun addListeners() {
    val arrivalListener = Navigator.ArrivalListener {
        navigator?.clearDestinations()
    }
    navigator?.addArrivalListener(arrivalListener)

    val routeChangedListener = Navigator.RouteChangedListener {
        // 路线变更处理逻辑
    }
    navigator?.addRouteChangedListener(routeChangedListener)
}

private fun handleError(errorCode: Int) {
    when (errorCode) {
        NavigationApi.ErrorCode.NOT_AUTHORIZED -> {
            // 未授权处理
        }
        NavigationApi.ErrorCode.TERMS_NOT_ACCEPTED -> {
            // 未接受条款处理
        }
        NavigationApi.ErrorCode.NETWORK_ERROR -> {
            // 网络错误处理
        }
        NavigationApi.ErrorCode.LOCATION_PERMISSION_MISSING -> {
            // 缺少定位权限处理
        }
        else -> {
            // 其他错误处理
        }
    }
}

解决方案思路

1. 核心原则:抽象隔离,避免直接依赖

清洁架构的核心是依赖倒置,不要让业务逻辑直接依赖Google的具体API。先在Domain层定义一个抽象的导航服务接口,把导航相关的核心能力(初始化、启动/停止导航、获取路线摘要等)抽象出来:

// Domain层 - 抽象导航服务接口
interface NavigationService {
    fun initialize(activity: FragmentActivity, onReady: () -> Unit, onError: (Int) -> Unit)
    fun startNavigation()
    fun stopNavigation()
    fun getRouteSummary(waypoint: Waypoint, options: RoutingOptions): Flow<NavigationSummary>
}

2. 具体实现放在Presentation层的辅助类中

因为Navigator依赖Presentation层的FragmentActivity,所以把Google API的具体实现放在Presentation层的导航服务实现类里,而非直接塞在Fragment中。这样Fragment只需要依赖Domain层的抽象接口,不用直接和Google API耦合:

// Presentation层 - Google导航服务实现
class GoogleNavigationService : NavigationService {
    private var navigator: Navigator? = null

    override fun initialize(activity: FragmentActivity, onReady: () -> Unit, onError: (Int) -> Unit) {
        NavigationApi.getNavigator(activity, object : NavigationApi.NavigatorListener {
            override fun onNavigatorReady(navigator: Navigator?) {
                this@GoogleNavigationService.navigator = navigator
                addListeners()
                onReady()
            }

            override fun onError(errorCode: Int) {
                onError(errorCode)
            }
        })
    }

    override fun startNavigation() {
        navigator?.setAudioGuidance(Navigator.AudioGuidance.VOICE_ALERTS_AND_GUIDANCE)
        navigator?.startGuidance()
    }

    override fun stopNavigation() {
        navigator?.stopGuidance()
        navigator?.clearDestinations()
    }

    override fun getRouteSummary(waypoint: Waypoint, options: RoutingOptions): Flow<NavigationSummary> = callbackFlow {
        navigator?.let { nav ->
            val pendingRoute = nav.setDestination(waypoint, options)
            pendingRoute.setOnResultListener { code ->
                trySend(
                    NavigationSummary(
                        response = code,
                        meters = nav.currentTimeAndDistance.meters,
                        seconds = nav.currentTimeAndDistance.seconds
                    )
                )
            }
            awaitClose {
                // 清理pendingRoute等资源
            }
        } ?: trySend(NavigationSummary(response = -1, meters = 0, seconds = 0))
    }

    private fun addListeners() {
        val arrivalListener = Navigator.ArrivalListener {
            navigator?.clearDestinations()
        }
        navigator?.addArrivalListener(arrivalListener)

        val routeChangedListener = Navigator.RouteChangedListener {
            // 路线变更处理逻辑
        }
        navigator?.addRouteChangedListener(routeChangedListener)
    }
}

3. Fragment通过依赖注入使用抽象接口

Fragment作为UI层,只需要依赖Domain层的NavigationService接口,具体实现由DI框架(比如Hilt)注入。这样Fragment逻辑更简洁,也符合清洁架构的依赖方向(上层依赖下层的抽象):

// Presentation层 - Fragment
class NavigationFragment : Fragment() {
    @Inject
    lateinit var navigationService: NavigationService

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        navigationService.initialize(requireActivity(), 
            onReady = { /* 导航就绪后的UI逻辑 */ },
            onError = { errorCode -> /* 错误处理UI逻辑 */ }
        )
    }

    // 点击启动导航按钮的处理逻辑
    fun onStartNavClick() {
        navigationService.startNavigation()
    }
}

4. 错误与状态管理的职责划分

把错误处理的UI逻辑留在Fragment里,导航服务只负责传递错误码;导航状态(如导航中、路线变更)可以通过Flow从导航服务暴露给Fragment,Fragment订阅后更新UI,确保职责单一。


关键问题解析

为什么不放在Data层?

Data层的核心是数据获取与存储,而Google Navigation API本质是功能服务(提供导航能力),不属于数据访问范畴。而且它依赖UI层组件,强行放在Data层会打破清洁架构的依赖规则,导致分层混乱。

为什么不直接放在Fragment?

直接放在Fragment里会导致Fragment代码臃肿,职责不单一(既要处理UI,又要处理导航API的细节)。如果多个Fragment需要导航功能,还会出现代码重复,同时不利于单元测试(无法Mock导航服务)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 03:02:29