多模块项目:多模块架构下的Koin配置与依赖管理

说实话,多模块架构在Android项目里已经不是什么新鲜事了。我这些年经手的项目,但凡规模大一点的,几乎都是多模块的。但每次接手一个新项目,看到Koin的配置方式,我都能感受到不同团队对依赖注入的理解差异有多大。

今天我们就来聊聊,在多模块架构下,Koin到底该怎么配置,模块间的依赖关系怎么管理。嗯,这里面的坑,我踩过不少。

为什么多模块下Koin配置会变复杂?

单模块项目里,你只需要一个Application类,把所有module都注册进去就行了。但多模块不一样——每个模块都有自己的依赖,模块之间还有依赖关系。你想想看,如果模块A依赖模块B的某个类,但模块B的Koin配置还没加载,那不就出问题了?

我曾经在一个项目中遇到过这样的情况:两个模块互相依赖,结果启动时直接崩溃,报错信息是"no definition found"。排查了半天,发现是模块加载顺序搞反了。

多模块Koin配置的核心原则

我个人习惯把Koin的配置分成三个层次:

层次 说明 示例
基础层 网络、数据库、日志等基础设施 networkModule, databaseModule
业务层 各业务模块的依赖 userModule, orderModule
表现层 ViewModel、UI相关 viewModelModule

说白了,就是按照依赖的先后顺序来组织。基础层先加载,业务层后加载,表现层最后加载。这个顺序不能乱。

模块间依赖管理的几种方式

我见过团队用不同的方式处理模块间依赖,这里列出三种比较常见的:

方式一:统一注册

在app模块里,把所有子模块的Koin module都收集起来,统一注册。这种方式最简单,但耦合度高。

// app模块的Application类
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MyApplication)
            modules(
                networkModule,
                databaseModule,
                userModule,
                orderModule,
                viewModelModule
            )
        }
    }
}

这种方式的问题在于:app模块需要知道所有子模块的存在。如果项目有几十个模块,那这个列表会变得非常长。

方式二:模块内自注册

每个模块在自己的Application类里注册自己的依赖,然后通过某种机制让app模块知道。这种方式更解耦,但实现起来稍微复杂一些。

// user模块
class UserModuleInitializer : ModuleInitializer {
    override fun getModules(): List<Module> = listOf(userModule)
}

// app模块
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MyApplication)
            // 通过ServiceLoader或手动收集所有ModuleInitializer
            modules(allModules)
        }
    }
}
我的建议:如果项目模块数量在10个以内,用方式一就够了。超过10个,建议用方式二,维护成本会低很多。

方式三:按需加载

这种方式比较高级,适合大型项目。每个模块只在需要的时候才加载自己的依赖。

// 在Feature模块中
class FeatureModule {
    fun loadDependencies() {
        loadKoinModules(featureModule)
    }
}

// 使用时
class FeatureActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        FeatureModule().loadDependencies()
        super.onCreate(savedInstanceState)
    }
}

这种方式的好处是启动速度快,但要注意:如果多个模块同时加载,可能会有并发问题。

模块间依赖的常见问题与解决方案

我在项目中遇到过几个典型问题,这里分享给大家:

问题一:循环依赖

模块A依赖模块B,模块B又依赖模块A。这种情况在Koin里会直接报错。

解决方案:引入一个公共模块C,把A和B共同依赖的部分放到C里。或者使用懒加载(lazy)来打破循环。

问题二:依赖找不到

某个模块的依赖在另一个模块里定义,但加载顺序不对。

解决方案:确保基础模块先加载。我一般会在Application的onCreate里按顺序加载:先加载基础模块,再加载业务模块。

实战:一个典型的多模块Koin配置

下面是一个我实际项目中用过的配置结构,大家可以参考:

// 基础模块:网络层
val networkModule = module {
    single { Retrofit.Builder()
        .baseUrl("https://api.example.com")
        .addConverterFactory(GsonConverterFactory.create())
        .build() 
    }
    single { get<Retrofit>().create(ApiService::class.java) }
}

// 基础模块:数据库层
val databaseModule = module {
    single { Room.databaseBuilder(
        get<Context>(), 
        AppDatabase::class.java, 
        "app.db"
    ).build() }
    single { get<AppDatabase>().userDao() }
}

// 业务模块:用户模块
val userModule = module {
    single { UserRepository(get(), get()) }
    viewModel { UserViewModel(get()) }
}

// 业务模块:订单模块
val orderModule = module {
    single { OrderRepository(get(), get()) }
    viewModel { OrderViewModel(get()) }
}

// app模块:统一注册
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MyApplication)
            modules(
                networkModule,    // 先加载基础
                databaseModule,   // 再加载基础
                userModule,       // 然后业务
                orderModule       // 最后业务
            )
        }
    }
}

多模块依赖关系图

为了让大家更直观地理解模块间的依赖关系,我画了一张图:

多模块Koin依赖关系图 基础层 networkModule databaseModule 业务层 userModule orderModule 表现层 viewModelModule 依赖 依赖 加载顺序说明: 1. 基础层先加载(网络、数据库等基础设施) 2. 业务层后加载(用户、订单等业务模块) 3. 表现层最后加载(ViewModel、UI相关) 注意:业务层依赖基础层,表现层依赖业务层 如果加载顺序颠倒,Koin会报"no definition found"错误 建议:在Application的onCreate中按顺序调用loadKoinModules

避坑指南

最后,分享几个我踩过的坑:

  • 模块间不要有循环依赖——我曾经在两个模块之间搞了循环依赖,结果Koin直接崩溃。后来用了一个公共模块才解决。
  • 注意单例的作用域——在多模块下,single的作用域是整个Koin容器。如果你希望某个单例只在某个模块内有效,可以考虑用scoped。
  • 测试时要小心——多模块的测试比单模块复杂得多。我一般会为每个模块写独立的测试,用KoinTestRule来管理依赖。

好了,关于多模块下的Koin配置,今天就聊到这里。记住一个原则:按依赖顺序加载,避免循环依赖,保持模块间解耦。做到这三点,多模块的Koin配置就不会出大问题。

公众号:蓝海资料掘金营,微信deep3321