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

多模块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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:28:18