Android清洁架构中Google导航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

