多模块Android架构下非Deep Linking无依赖循环导航方案咨询
替代Deep Linking的多模块Android导航方案(无依赖循环)
嘿,我之前在多模块Android项目里也踩过类似的导航坑,完全懂你不想用Deep Linking又要避免依赖循环的需求!下面几个方案都是我实际用过或者验证过的,分享给你:
1. 接口驱动的导航(Interface-Driven Navigation)
这是最常见也最稳妥的解耦方案,核心思路是把导航契约放在base模块,让功能模块实现契约,app模块做协调:
- 在base模块定义通用的导航接口,比如:
// base模块 interface FeatureNavigator { fun navigateToFeatureA(context: Context, params: FeatureAParams? = null) fun navigateToFeatureB(context: Context, params: FeatureBParams? = null) // 其他模块的导航方法 }
- 每个功能模块(比如featureA)实现自己的导航逻辑,但不需要暴露给其他模块:
// featureA模块 class FeatureANavigatorImpl : FeatureNavigator { override fun navigateToFeatureA(context: Context, params: FeatureAParams?) { val intent = Intent(context, FeatureAActivity::class.java) params?.let { intent.putExtra("params", it) } context.startActivity(intent) } // 其他方法默认实现或者空实现,根据需求调整 override fun navigateToFeatureB(context: Context, params: FeatureBParams?) { /* 空实现 */ } }
- 在app模块通过依赖注入(比如Hilt)把各个模块的导航实现整合起来,提供统一的导航实例:
// app模块 @Module @InstallIn(SingletonComponent::class) class NavigationModule { @Provides fun provideFeatureNavigator( featureANavigator: FeatureANavigatorImpl, featureBNavigator: FeatureBNavigatorImpl ): FeatureNavigator { // 用组合模式整合多个实现 return object : FeatureNavigator { override fun navigateToFeatureA(context: Context, params: FeatureAParams?) { featureANavigator.navigateToFeatureA(context, params) } override fun navigateToFeatureB(context: Context, params: FeatureBParams?) { featureBNavigator.navigateToFeatureB(context, params) } } } }
- 任何模块需要导航时,只需要注入base模块的
FeatureNavigator接口,调用对应方法即可,完全不需要依赖目标模块,从根源避免了依赖循环。
2. 事件驱动的导航(Event-Driven Navigation)
用发布订阅模式让模块间通过事件通信,不需要直接依赖:
- 在base模块定义导航事件类,比如:
// base模块 sealed class NavigationEvent { data class NavigateToFeatureA(val params: FeatureAParams?) : NavigationEvent() data class NavigateToFeatureB(val params: FeatureBParams?) : NavigationEvent() }
- 在base模块提供一个事件分发器(比如用Jetpack的
SharedFlow):
// base模块 class NavigationEventDispatcher { private val _events = MutableSharedFlow<NavigationEvent>() val events = _events.asSharedFlow() suspend fun sendEvent(event: NavigationEvent) { _events.emit(event) } }
- 每个功能模块订阅自己关心的事件,处理导航逻辑:
// featureA模块 class FeatureANavigationHandler( private val eventDispatcher: NavigationEventDispatcher, private val context: Context ) { init { CoroutineScope(Dispatchers.Main).launch { eventDispatcher.events.collect { event -> when (event) { is NavigationEvent.NavigateToFeatureA -> { val intent = Intent(context, FeatureAActivity::class.java) event.params?.let { intent.putExtra("params", it) } context.startActivity(intent) } else -> {} } } } } }
- 当某个模块需要导航时,只需要发送对应的事件,不需要知道目标模块的存在。比如featureB要跳转到featureA:
// featureB模块 eventDispatcher.sendEvent(NavigationEvent.NavigateToFeatureA(params))
这个方案的优点是模块间完全解耦,缺点是需要注意事件的类型管理和生命周期,避免内存泄漏(比如用viewModelScope订阅事件)。
3. Jetpack Navigation 多模块扩展
如果你已经在使用Jetpack Navigation,可以用导航图合并的方式实现无依赖导航:
- 在base模块定义所有目的地的ID常量:
// base模块 object NavigationDestinations { const val FEATURE_A = "feature_a_dest" const val FEATURE_B = "feature_b_dest" }
- 每个功能模块在自己的导航图(
nav_graph_feature_a.xml)中使用base的常量作为目的地ID:
<!-- featureA模块的导航图 --> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/nav_graph_feature_a" app:startDestination="@id/feature_a_dest"> <fragment android:id="@+id/feature_a_dest" android:name="com.example.featurea.FeatureAFragment" android:label="Feature A"> </fragment> </navigation>
- 在app模块的主导航图中include所有功能模块的导航图:
<!-- app模块的主导航图 --> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/nav_graph_main" app:startDestination="@id/home_dest"> <include app:graph="@navigation/nav_graph_feature_a" /> <include app:graph="@navigation/nav_graph_feature_b" /> <!-- 其他主页面目的地 --> </navigation>
- 模块间跳转时,使用base模块的常量ID,通过
NavController跳转:
// featureB模块中跳转到featureA findNavController().navigate(NavigationDestinations.FEATURE_A)
这个方案利用Jetpack Navigation的合并能力,模块间只依赖base的常量,不需要直接引用对方的类,避免了依赖循环。
注意事项
- 不管用哪种方案,导航逻辑尽量集中在app模块或者base模块,不要让功能模块直接处理跨模块导航的细节。
- 依赖注入时要注意作用域,确保导航实例是单例或者符合生命周期的实例。
- 测试时可以轻松模拟导航接口或者事件,不需要依赖真实的目标模块,降低测试难度。
内容的提问来源于stack exchange,提问作者Gokhan Arik
相关产品推荐
相关产品推荐

