在 Android 开发中,依赖注入(DI)是一种重要的设计模式,它能帮助我们降低代码耦合度、提高可测试性和可维护性。目前主流的 DI 框架中,Koin 以其轻量、简洁的特性逐渐成为许多开发者的首选。Koin 的核心设计理念是 "基于 Kotlin DSL 的运行时依赖解析",区别于 Hilt(基于 Dagger2 的编译期代码生成),它无需注解处理器(KAPT/KSP),完全通过 Kotlin 语法特性实现依赖的声明与注入,整体架构轻量且透明。
为什么选择 Koin?
Koin 作为一款基于 Kotlin 的依赖注入框架,相比其他框架具有多项显著优势:
- 简单且对开发人员友好:Koin 拥有干净的 DSL 语法,无需处理复杂的注解和代码生成,零编译时开销,最少的配置设置让开发者可以专注于业务逻辑实现。
- 极致轻量:无论是小型项目还是复杂项目,Koin 都能轻松扩展以满足需求,不会给应用带来过多的体积负担。
- 安全性保障:Koin 在运行时会进行依赖对象合法性检查,同时其 IDE 插件也即将发布,将进一步提升开发体验,弥补在 IDE 导航方面的短板。
- 跨平台支持:作为纯 Kotlin 实现的框架,Koin 能够无缝管理 iOS、Android、桌面和 Web 等多平台的依赖项,是 Kotlin Multiplatform 开发的理想选择。
- Jetpack Compose 友好:Koin 与 Jetpack Compose 集成极为轻松,完美支持 ViewModel 等界面组件的依赖注入,为未来迁移到 Compose Multiplatform 跨平台开发铺平道路。
Koin vs Hilt:核心差异
与 Google 官方推荐的 Hilt 框架相比,Koin 的优势主要体现在:
- 学习曲线:Koin 无需学习复杂的注解体系,通过简单直观的 DSL 即可完成依赖配置
- 跨平台能力:Hilt 基于 Java 的 Dagger2,对 Kotlin Multiplatform 支持有限,而 Koin 天生支持多平台
- 灵活性:Koin 无需代码生成,修改依赖配置后无需等待编译,开发效率更高
- 轻量性:Koin 体积更小,对应用包体大小影响更小
性能方面,国外开发者对使用 Hilt 的官方示例 Now in Android 和使用 Koin 重写的版本进行的基准测试显示,两者性能差距极小,Koin 完全能满足日常开发需求。
核心组件
Koin 的架构由 5 个核心组件构成,各组件职责明确且协同工作:
组件 | 核心职责 |
---|---|
KoinApplication | Koin 入口,负责初始化 Koin 容器、加载模块(Module)、管理全局配置(如日志、上下文) |
Module | 依赖声明载体,通过 DSL 定义 "如何创建实例"(如 single/factory/viewModel) |
Scope | 依赖生命周期容器,管理某一 "作用域" 内的实例(如 Activity 生命周期内的单例) |
InstanceFactory | 实例创建器,根据 Module 中声明的类型(single/factory/viewModel)创建并缓存实例 |
KoinComponent | 依赖消费者接口,实现该接口的类可通过 inject() /get() 方法获取依赖实例 |
架构协同流程:
- 初始化:
KoinApplication
加载Module
,将依赖声明注册到内部容器; - 声明:
Module
通过 DSL(如single { }
)定义实例创建规则,绑定到指定Scope
; - 注入:
KoinComponent
调用inject()
/get()
,触发InstanceFactory
按规则创建 / 获取实例; - 生命周期管理:
Scope
监听组件生命周期(如 Activity 销毁),自动清理该作用域内的实例。
Koin 引入
Koin 采用 "核心包 + 扩展包" 的设计,开发者可根据项目场景(如 Android 原生、Compose、KMP)按需引入。以下是常用依赖包的详细说明:
依赖包坐标(artifactId) | 核心作用 | 适用场景 |
---|---|---|
koin-bom |
用于集中管理所有 Koin 模块的版本,避免版本冲突 | 所有 |
koin-core |
Koin 核心功能:Module 定义、Scope 管理、依赖解析(平台无关) | 所有 Kotlin 项目(Android、KMP、后端) |
koin-android-compat |
Android 扩展:绑定 Application 上下文、支持 Activity/Fragment、ViewModel、SavedStateHandle注入 | Android 项目 |
koin-androidx-compose |
Compose 专用扩展:提供 koinInject() /koinViewModel() 等 Composable 方法(支持 Compose Multiplatform) |
Compose 项目 |
koin-androidx-startup |
AndroidX Startup 自动初始化 | AndroidX Startup 1.1.0+ |
koin-test |
测试支持:提供 module.verify() 验证依赖图、Mock 实例注入 |
单元测试(JVM/AndroidTest) |
gradle
dependencies {
// 引入 BOM 管理所有 Koin 模块版本
implementation platform("io.insert-koin:koin-bom:4.1.0")
// 核心包
implementation "io.insert-koin:koin-core"
// AndroidX 兼容(ViewModel)
implementation "io.insert-koin:koin-android-compat"
// Compose 集成
implementation "io.insert-koin:koin-androidx-compose"
// 测试支持
testImplementation "io.insert-koin:koin-test"
}
Koin 核心概念
模块(Module):依赖声明的载体
Module
是 Koin 中 "声明依赖" 的最小单元,通过 Kotlin DSL 定义 "如何创建实例",支持多模块拆分(如 networkModule
、repositoryModule
),便于模块化管理。
核心特性:
- 无侵入式 :无需修改被注入类(如无需加
@Inject
注解); - 支持分组 :通过
listOf(module1, module2)
合并多个模块; - 依赖传递 :模块内可通过
get()
自动解析依赖(如 ViewModel 依赖 Repository)。
示例:
kotlin
// 网络模块:提供 Retrofit 实例
val networkModule = module {
single {
Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
// 依赖 Retrofit 创建 ApiService
single { get<Retrofit>().create(ApiService::class.java) }
}
// 仓库模块:提供 Repository 实例
val repositoryModule = module {
single { UserRepository(get<ApiService>()) }
}
// 合并模块
val appModules = listOf(networkModule, repositoryModule)
实例类型:single、factory、viewModel 的区别
Koin 通过三种核心 DSL 函数定义实例的 "创建策略" 和 "生命周期",需根据业务场景选择:
函数 | 实例生命周期 | 适用场景 |
---|---|---|
single { } |
全局单例(Koin 容器销毁前唯一) | 无状态服务(如 Retrofit、Database) |
factory { } |
每次获取新实例(无缓存) | 有状态对象(如 Activity 级别的工具类) |
viewModel { } |
绑定 ViewModel 生命周期(自动销毁) | Android ViewModel(与 Activity/Fragment 生命周期同步) |
关键注意点:
viewModel { }
内部依赖ViewModelProvider.Factory
,无需手动实现工厂类;- 同一类型可通过 "限定符(Qualifier)" 区分多个实例(如不同环境的 Retrofit)。
限定符示例(多实例区分):
kotlin
val networkModule = module {
// 生产环境 Retrofit
single(qualifier = named("prod")) {
Retrofit.Builder().baseUrl("https://prod.example.com/").build()
}
// 测试环境 Retrofit
single(qualifier = named("test")) {
Retrofit.Builder().baseUrl("https://test.example.com/").build()
}
}
// 在 Module 中声明依赖时指定限定符
val repositoryModule = module {
single {
UserRepository(
prodRetrofit = get(named("prod")), // Koin 专用语法,无需注解
testRetrofit = get(named("test"))
)
}
}
// UserRepository 类(纯 Kotlin 类,无任何注解)
class UserRepository(
private val prodRetrofit: Retrofit,
private val testRetrofit: Retrofit
)
作用域(Scope):生命周期绑定的核心
Scope
是 Koin 中 "管理实例生命周期" 的关键概念,通过绑定 Android 组件或自定义生命周期,实现 "实例在指定范围内唯一",避免内存泄漏。
内置 Scope 组件
Koin 提供 3 个默认实现 AndroidScopeComponent
接口的组件,覆盖常见场景:
组件 | 生命周期特性 | 适用场景 |
---|---|---|
ScopeActivity |
Activity 内单例,屏幕旋转时重新创建实例 | 不依赖旋转后状态保留的 Activity |
RetainedScopeActivity |
Activity 内单例,屏幕旋转时通过 ViewModel 保留实例 | 需保留状态的 Activity(如表单页面) |
ScopeFragment |
Fragment 内单例,Fragment 销毁时清理实例 | Fragment 场景 |
自定义 Scope
若需更灵活的生命周期控制(如弹窗、组件化场景),可自定义 Scope:
kotlin
// 1. 定义 Scope ID
const val DIALOG_SCOPE_ID = "dialog_scope"
// 2. 在 Module 中声明 Scope 内的依赖
val dialogModule = module {
scope(named(DIALOG_SCOPE_ID)) {
scoped { DialogManager(get<Context>()) } // scoped 表示 Scope 内单例
}
}
// 3. 在代码中创建并使用 Scope
class CustomDialog(context: Context) : Dialog(context), KoinComponent {
// 创建 Scope 并绑定到对话框生命周期
private val dialogScope = getKoin().createScope(DIALOG_SCOPE_ID)
// 从 Scope 中获取依赖
private val dialogManager: DialogManager by dialogScope.inject()
override fun dismiss() {
super.dismiss()
// 销毁 Scope,释放实例
dialogScope.close()
}
}
依赖解析:inject () vs get ()
Koin 提供两种获取依赖的方式,核心区别在于 "是否懒加载":
方法 | 特性 | 适用场景 |
---|---|---|
by inject() |
懒加载(Lazy 委托),首次使用时才解析依赖 | 依赖可能不被使用(如条件初始化) |
get() |
立即解析依赖,返回实例本身 | 依赖必须立即使用(如构造函数参数) |
示例:
kotlin
class MainActivity : AppCompatActivity(), KoinComponent { // 注意需要实现KoinComponent接口
// 懒加载:首次调用时解析
private val userRepository: UserRepository by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 立即解析:必须在 onCreate 中使用
val apiService = get<ApiService>()
}
}
关键注意点:
- 在
Activity/Fragment
等需要使用 inject ()/get () 类中需实现KoinComponent
接口; - 在
ViewModel
中通过构造函数注入时无需实现; - 在
Compose
中使用koinInject()
时无需实现。
上下文隔离(Context Isolation):多模块解耦
Koin 支持 "多 Koin 容器" 隔离,通过 KoinApplication
创建独立上下文,解决多模块(如 SDK、组件库)间的依赖冲突问题。
示例(SDK 模块隔离):
kotlin
// SDK 内部创建独立 Koin 容器
object SdkKoinContext {
private val koinApp = koinApplication {
modules(sdkInternalModule) // SDK 内部模块
}
val koin: Koin = koinApp.koin
}
// SDK 内部组件实现隔离的 KoinComponent
internal interface SdkKoinComponent : KoinComponent {
override fun getKoin(): Koin = SdkKoinContext.koin
}
// SDK 内部使用隔离依赖
internal class SdkManager : SdkKoinComponent {
private val sdkService: SdkService by inject()
}
Koin 实战运用
初始化
简单项目,直接在 Application
类中初始化 Koin:
kotlin
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
// 启动 Koin
startKoin {
androidLogger(Level.DEBUG) // 开启日志(可选)
androidContext(this@MyApp) // 绑定 Application 上下文
modules(appModules) // 加载所有模块
}
}
}
也可以通过 koin-androidx-startup
集成 AndroidX Startup 组件,实现 Koin 的自动初始化,减少对 Application
类的侵入,更符合组件化架构:
kotlin
class MyApp : Application(), KoinStartup { // 实现KoinStartup接口
override fun onKoinStartup() = koinConfiguration {
androidLogger(Level.DEBUG) // 开启日志(可选)
androidContext(this@MyApp) // 绑定 Application 上下文
modules(appModules) // 加载所有模块
}
}
注入 ViewModel:无工厂类简化开发
传统 ViewModel 传参需手动实现 ViewModelProvider.Factory
,Koin 可通过 viewModel { }
简化:
无参 ViewModel
kotlin
// 1. 声明 ViewModel 模块
val viewModelModule = module {
viewModel { UserViewModel() }
}
// 2. 在 Activity 中获取
class UserActivity : AppCompatActivity() {
private val userViewModel: UserViewModel by viewModel()
}
有参 ViewModel(依赖 Repository)
kotlin
// 1. 声明依赖链:ViewModel → Repository → ApiService
val viewModelModule = module {
viewModel { UserViewModel(get()) } // get() 自动解析 UserRepository
}
val repositoryModule = module {
single { UserRepository(get()) } // get() 自动解析 ApiService
}
// 2. ViewModel 实现(纯 Kotlin 类,无需继承特定父类)
class UserViewModel(private val repository: UserRepository) : ViewModel() {
fun getUserInfo() = repository.getUserInfo()
}
Compose 中使用 Koin:Composable 专用 API
Koin 为 Compose 提供 koinInject()
(注入普通对象)和 koinViewModel()
(注入 ViewModel),需引入 koin-androidx-compose
依赖:
注入普通对象
kotlin
@Composable
fun UserProfile() {
// 注入 UserRepository(Composable 内专用)
val userRepository: UserRepository = koinInject()
LaunchedEffect(Unit) {
val userInfo = userRepository.getUserInfo()
}
}
注入 ViewModel(带参数)
kotlin
// 1. 声明带参数的 ViewModel
val viewModelModule = module {
viewModel { (userId: String) -> UserDetailViewModel(userId, get()) }
}
// 2. Compose 中传递参数
@Composable
fun UserDetailScreen(userId: String) {
val viewModel: UserDetailViewModel = koinViewModel(
parameters = { parametersOf(userId) } // 传递参数
)
}
多模块通信:接口解耦
Koin 可实现多模块(如 Base 模块与业务模块)间的接口通信,类似 ARouter 的跨模块调用:
示例(Base 模块定义接口,User 模块实现):
kotlin
// 1. Base 模块定义接口
interface IUserService {
fun getUserToken(): Flow<String>
}
// 2. User 模块实现接口并声明依赖
class UserServiceImpl : IUserService {
override fun getUserToken() = flow { emit("token_123") }
}
val userModule = module {
single<IUserService> { UserServiceImpl() } // 绑定接口与实现
}
// 3. 其他模块(如 Order 模块)使用接口
class OrderRepository : KoinComponent {
private val userService: IUserService by inject()
fun getOrderList() = userService.getUserToken().map { token ->
// 用 token 请求订单列表
}
}
测试支持:验证依赖与 Mock 注入
Koin 提供 koin-test
库,支持两种核心测试场景:
验证 Module 依赖图
通过 module.verify()
检查模块内依赖是否完整(避免运行时异常):
kotlin
class KoinModuleTest {
@Test
fun testModuleDependency() {
// 验证所有模块的依赖关系
appModules.verify()
}
}
Mock 依赖注入
使用 MockK/Mockito 替换真实依赖,便于单元测试:
kotlin
class UserViewModelTest : KoinTest {
private val mockRepository = mockk<UserRepository>()
@Before
fun setup() {
// 启动测试用 Koin,替换真实依赖为 Mock
startKoin {
modules(module {
single { mockRepository }
viewModel { UserViewModel(get()) }
})
}
}
@Test
fun testGetUserInfo() {
// 模拟 Repository 返回
coEvery { mockRepository.getUserInfo() } returns "test_user"
// 获取 ViewModel 并测试
val viewModel: UserViewModel = get()
viewModel.getUserInfo().test().assertValue("test_user")
}
}
架构设计
Koin 并非孤立存在,而是与现代 Android 架构(如 MVVM、Clean Architecture)深度契合的工具。合理的架构设计能最大化 Koin 的价值,减少依赖管理复杂度。
分层模块划分
一般简单应用采用分层模块划分,将依赖按"数据层-领域层-UI层"分离,每个层对应独立的 Koin 模块,通过接口实现层间解耦。
架构分层与模块对应关系
架构层 | 职责 | Koin 模块示例 | 核心依赖类型 |
---|---|---|---|
数据层 | 数据获取与存储(API/数据库) | networkModule 、storageModule |
single (Retrofit、Room) |
领域层 | 业务逻辑与数据处理 | repositoryModule 、useCaseModule |
single (Repository、UseCase) |
UI层 | 界面展示与用户交互 | viewModelModule 、uiModule |
viewModel 、factory |
示例:分层模块设计
kotlin
// 1. 数据层:网络模块
val networkModule = module {
single { Retrofit.Builder().baseUrl(BASE_URL).build() }
single { get<Retrofit>().create(UserService::class.java) }
}
// 2. 数据层:本地存储模块
val storageModule = module {
single { AppDatabase.getDatabase(get()) } // 依赖 Context
single { get<AppDatabase>().userDao() }
}
// 3. 领域层:仓库模块(依赖数据层接口)
val repositoryModule = module {
single { UserRepository(get<UserService>(), get<UserDao>()) }
}
// 4. 领域层:用例模块(依赖仓库)
val useCaseModule = module {
factory { GetUserInfoUseCase(get()) } // 每次使用创建新实例
factory { UpdateUserUseCase(get()) }
}
// 5. UI层:ViewModel模块(依赖用例)
val viewModelModule = module {
viewModel { UserProfileViewModel(get<GetUserInfoUseCase>(), get<UpdateUserUseCase>()) }
}
// 合并所有模块
val appModules = listOf(
networkModule,
storageModule,
repositoryModule,
useCaseModule,
viewModelModule
)
模块化项目
大型项目通常按业务拆分模块(如 user
、order
、common
),Koin 可通过模块组合实现跨模块依赖管理,避免模块间硬编码依赖。
关键原则:
- 公共模块暴露接口 :
common
模块定义通用接口(如AnalyticsService
),具体实现由业务模块提供; - 业务模块独立声明依赖 :
user
模块仅声明自身需要的依赖,不关心其他模块实现; - 主模块负责组合 :
app
模块汇总所有业务模块的 Koin 模块,完成最终组装。
示例:模块化项目的模块组合
kotlin
// common 模块:定义接口
interface AnalyticsService {
fun trackEvent(event: String)
}
// user 模块:声明依赖(依赖 common 接口)
val userModule = module {
single { UserManager(get<AnalyticsService>()) } // 依赖接口,不依赖实现
}
// order 模块:声明依赖
val orderModule = module {
single { OrderManager(get<AnalyticsService>()) }
}
// app 模块:提供接口实现并组合所有模块
val appModule = module {
single<AnalyticsService> { FirebaseAnalyticsService() } // 实现 common 接口
}
// 主模块组合
val allModules = listOf(appModule, userModule, orderModule)
循环依赖:检测、分析与解决方案
循环依赖(A 依赖 B,B 依赖 A)是 DI 中常见的问题,Koin 运行时会抛出 CyclicDependencyException
。解决循环依赖的核心是打破依赖闭环,而非绕过异常。
循环依赖的常见场景与检测
典型场景:
- 双向依赖 :
ViewModel
依赖Repository
,Repository
依赖ViewModel
(错误设计); - 多层闭环 :
A → B → C → A
的多层依赖链; - 隐式依赖:通过全局静态变量或单例间接形成的依赖闭环。
检测方法:
-
运行时异常 :Koin 会直接抛出
CyclicDependencyException
,堆栈信息会显示依赖链; -
静态分析 :使用
module.verify()
在测试中提前检测:
kotlin
@Test
fun testNoCyclicDependencies() {
// 验证模块是否存在循环依赖
appModules.verify()
}
解决方案:从设计层面打破闭环
方案 1:通过接口分离依赖
将依赖双方的交互抽象为接口,使一方依赖接口而非具体实现,打破直接依赖。
kotlin
// 问题场景:UserViewModel 依赖 UserRepository,UserRepository 依赖 UserViewModel
// 解决方案:抽象接口
// 1. 定义接口(由 ViewModel 实现)
interface UserStateListener {
fun onUserUpdated(user: User)
}
// 2. ViewModel 实现接口
class UserViewModel(
private val repository: UserRepository
) : ViewModel(), UserStateListener {
init {
repository.setListener(this) // 传递接口实例,而非自身
}
override fun onUserUpdated(user: User) {
// 处理更新
}
}
// 3. Repository 依赖接口,而非 ViewModel
class UserRepository(
private val api: UserService
) {
private var listener: UserStateListener? = null
fun setListener(listener: UserStateListener) {
this.listener = listener
}
fun updateUser(user: User) {
// 业务逻辑
listener?.onUserUpdated(user) // 调用接口方法
}
}
// 4. Koin 模块声明
val module = module {
viewModel { UserViewModel(get()) }
single { UserRepository(get()) }
}
方案 2:使用懒加载(by inject()
)延迟依赖解析
Koin 的 by inject()
是懒加载委托,可延迟依赖实例化,避免初始化时的闭环(仅适用于 "依赖双方非初始化阶段互相调用" 的场景。若初始化时就调用对方方法,仍会触发循环依赖异常)。
kotlin
// 适用场景:双方依赖但非初始化阶段立即使用
class A : KoinComponent {
// 懒加载依赖 B
private val b: B by inject()
fun doSomething() {
b.action() // 首次使用时才解析 B
}
}
class B : KoinComponent {
// 懒加载依赖 A
private val a: A by inject()
fun action() {
a.react() // 首次使用时才解析 A
}
}
val module = module {
single { A() }
single { B() }
}
方案 3:重构业务逻辑,移除不必要依赖
循环依赖往往是业务逻辑设计不合理的信号,可通过职责分离重构代码。
例如:Repository
不应依赖 ViewModel
,可将共享逻辑提取到独立的 UseCase
或 Manager
中:
kotlin
// 重构前:Repository 依赖 ViewModel
// 重构后:引入中间层 UserManager
class UserManager {
// 存放原 Repository 和 ViewModel 的共享逻辑
private val userFlow = MutableStateFlow<User?>(null)
fun updateUser(user: User) {
userFlow.value = user
}
fun getUserFlow() = userFlow.asStateFlow()
}
// Repository 依赖 UserManager
class UserRepository(
private val api: UserService,
private val manager: UserManager
) {
fun fetchUser() {
// 业务逻辑
manager.updateUser(user) // 通知更新
}
}
// ViewModel 依赖 UserManager
class UserViewModel(
private val manager: UserManager
) : ViewModel() {
val user by manager.getUserFlow().collectAsState()
}
// Koin 模块
val module = module {
single { UserManager() }
single { UserRepository(get(), get()) }
viewModel { UserViewModel(get()) }
}
结合工具与库
Koin 可与众多 Android 主流库无缝集成:
网络与数据存储
Retrofit + Koin
网络请求是依赖注入的典型场景,Koin 可集中管理 Retrofit 实例和 API 服务:
kotlin
val networkModule = module {
single {
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor())
.build()
}
single {
Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(get())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
single { get<Retrofit>().create(UserApi::class.java) }
single { get<Retrofit>().create(OrderApi::class.java) }
}
Room + Koin
数据库实例适合作为全局单例,通过 Koin 注入可避免重复创建:
kotlin
val databaseModule = module {
single {
Room.databaseBuilder(
get(), // 从 Koin 获取 Context
AppDatabase::class.java,
"app_db"
).build()
}
single { get<AppDatabase>().userDao() }
single { get<AppDatabase>().orderDao() }
}
图片加载与缓存
Coil + Koin
Coil 是 Kotlin 编写的图片加载库,与 Koin 结合可自定义全局配置:
kotlin
val imageModule = module {
single {
ImageLoader.Builder(get()) // 依赖 Context
.crossfade(true)
.okHttpClient {
OkHttpClient.Builder()
.cache(CoilUtils.createDefaultCache(get()))
.build()
}
.build()
}
}
// 在 Compose 中使用
@Composable
fun UserAvatar(url: String) {
val imageLoader: ImageLoader by koinInject()
AsyncImage(
model = url,
contentDescription = "User avatar",
imageLoader = imageLoader
)
}
状态管理与事件总线
Flow + Koin
Kotlin Flow 作为现代状态管理方案,与 Koin 结合可实现数据流的注入:
kotlin
// 用户状态管理器:负责持有和更新用户状态 Flow
class UserStateManager {
// 1. 私有可变 Flow(MutableStateFlow):内部可修改
// 初始值设为"空用户",确保页面有默认数据可展示
private val _userState = MutableStateFlow<User>(EMPTY_USER)
// 2. 公开只读 Flow(StateFlow):外部只能观察,不能修改
// 用 asStateFlow() 转换,避免外部强制类型转换后修改数据
val userState: StateFlow<User> = _userState.asStateFlow()
// 3. 公开方法:更新用户状态(外部只能通过此方法修改数据)
fun updateUser(newUser: User) {
// 切换到主线程更新(StateFlow 推荐在主线程发射数据,避免线程安全问题)
CoroutineScope(Dispatchers.Main).launch {
_userState.value = newUser
}
}
}
// 用户状态相关的 Koin 模块
val userStateModule = module {
// 注册 UserStateManager 为全局单例(single)
// 原因:整个 APP 只需要一个用户状态管理器,确保所有页面观察的是同一数据流
single { UserStateManager() }
}
// 合并到 APP 总模块(与其他模块一起初始化)
val appModules = listOf(
userStateModule, // 加入用户状态模块
networkModule, // 其他模块(如网络、数据库)
viewModelModule // 其他模块(如 ViewModel)
)
/ 个人中心 ViewModel
class ProfileViewModel(
// 1. 通过构造函数注入 UserStateManager(Koin 自动提供实例)
private val userStateManager: UserStateManager,
// 2. 同时注入网络依赖(示例:登录请求需要的 API 服务)
private val userApi: UserApi
) : ViewModel() {
// 3. 暴露用户状态 Flow 给 UI(直接转发管理器的只读 Flow)
// UI 层只需观察这个 Flow,无需关心数据来源
val userState: StateFlow<User> = userStateManager.userState
// 4. 业务逻辑:登录(成功后更新用户状态)
fun login(username: String, password: String) {
viewModelScope.launch {
try {
// 发起网络请求(示例:调用登录接口)
val loginResponse = userApi.login(username, password)
if (loginResponse.isSuccess) {
val userInfo = loginResponse.data
// 登录成功:更新用户状态(触发所有观察此 Flow 的 UI 刷新)
userStateManager.updateUser(userInfo)
} else {
// 登录失败:重置为未登录状态
userStateManager.updateUser(EMPTY_USER)
}
} catch (e: Exception) {
// 网络异常:重置状态
userStateManager.updateUser(EMPTY_USER)
}
}
}
}
EventBus(如 TinyBus) + Koin
轻量级事件总线可通过 Koin 注入,避免全局静态引用:
kotlin
val eventModule = module {
single { TinyBus(get<Application>()) } // 依赖 Application 上下文
}
// 在 ViewModel 中使用
class UserViewModel(
private val eventBus: TinyBus
) : ViewModel() {
init {
eventBus.register(this)
}
@Subscribe
fun onUserEvent(event: UserEvent) {
// 处理事件
}
override fun onCleared() {
eventBus.unregister(this)
super.onCleared()
}
}