Koin Android ViewModel 完整指南:生命周期感知注入、声明式 DSL 与作用域实战
【免费下载链接】koinKoin - a pragmatic lightweight dependency injection framework for Kotlin & Kotlin Multiplatform项目地址: https://gitcode.com/gh_mirrors/ko/koin
Koin 为 Android 的ViewModel提供了一整套生命周期感知的注入能力:既支持在Activity、Fragment、Service中通过by viewModel()懒加载获取实例,也支持activityViewModel()共享、参数传递、SavedStateHandle自动注入、导航图作用域以及@KoinViewModel注解/编译器插件声明等高级用法。本文基于 docs/reference/koin-android/viewmodel.md 展开,并结合仓库源码(koin-core-viewmodel、koin-android、koin-androidx-navigation、koin-android-compat)逐层剖析底层实现,帮助你在真实项目中安全、正确地使用 Koin 管理 ViewModel。
概述:Koin 如何支持 ViewModel
ViewModel 是 Android Architecture Components 中的核心组件,用于在配置变更(旋转、主题切换)时保存 UI 相关数据。Koin 对 ViewModel 提供了专门支持,其核心特性包括:
- 配置变更存活—— ViewModel 在旋转、主题切换等配置变更后依然保留;
- 生命周期作用域—— 绑定到 Activity、Fragment 或 Navigation Graph 的生命周期;
- 懒创建—— 仅在首次访问时才真正创建实例;
- 共享实例—— 可以在 Fragment 与宿主 Activity 之间共享同一个实例。
从 Koin 源码看,Android 侧的 ViewModel 支持建立在koin-core-viewmodel模块之上(projects/core/koin-core-viewmodel),该模块提供了跨平台的 ViewModel DSL 与解析内核;koin-android再在其上提供面向ComponentActivity/Fragment的注入扩展。若需了解不依赖 Android 的多平台 ViewModel DSL,参见 ViewModel(koin-core);Compose Multiplatform 场景参见 Compose ViewModel。
作用域限制:为什么 ViewModel 拿不到 Activity 作用域依赖
需要特别强调的是:ViewModel 是在 Koin 根作用域(root scope)下创建的,无法访问 Activity 或 Fragment 作用域中的依赖。这样设计的目的在于防止内存泄漏——ViewModel 的存活时间通常长于其宿主 Activity / Fragment,如果它持有了宿主作用域内的对象引用,就会导致宿主无法被回收。
如果你的 ViewModel 确实需要作用域依赖,官方建议使用 ViewModel Scope(koin-core/scopes) 创建一个与 ViewModel 生命周期绑定的独立作用域(下文"ViewModel 与作用域依赖"一节有完整示例)。
声明 ViewModel:三种 DSL 方式
Koin 提供三种声明 ViewModel 定义的方式,可按项目风格选用。
方式一:编译器插件 DSL(Compiler Plugin DSL)
val appModule = module { viewModel<DetailViewModel>() viewModel<UserViewModel>() }方式二:注解(Annotations)
配合 Koin 注解编译器,用@KoinViewModel标注类即可:
@KoinViewModel class DetailViewModel( private val repository: DetailRepository ) : ViewModel() @KoinViewModel class UserViewModel( private val userRepository: UserRepository ) : ViewModel()在 CoreAnnotations.kt 中可以看到@KoinViewModel的定义:它可标注类或函数,所有构造函数依赖都会自动填充(等价于生成viewModel { MyViewModel(get()) }),并可通过binds参数声明额外的绑定类型。关于注解方式的更多细节,可参考 Koin 注解参考。
方式三:经典 DSL(Classic DSL)
val appModule = module { // 构造引用方式 viewModelOf(::DetailViewModel) // Lambda 方式 viewModel { DetailViewModel(get()) } }实现原理:无论哪种 DSL,Module.viewModel本质上都注册为 Koin 的factory定义(每次获取新建实例,同时由 AndroidX 的ViewModelStore保证同一 store 内的复用)。见 koin-core-viewmodel 的 ModuleExt.kt:
inline fun <reified T : ViewModel> Module.viewModel( qualifier: Qualifier? = null, noinline definition: Definition<T> ): KoinDefinition<T> { return factory(qualifier, definition) }(注:koin-android早期org.koin.androidx.viewmodel.dsl包下的同名扩展已标记@Deprecated,建议统一使用org.koin.core.module.dsl.*。)
注入 ViewModel:懒加载与立即获取
在Activity、Fragment或Service中注入 ViewModel 有两种方式:
by viewModel()—— 懒加载委托属性(推荐);getViewModel()—— 立即获取。
class DetailActivity : AppCompatActivity() { // 懒加载注入 ViewModel private val viewModel: DetailViewModel by viewModel() // 或者立即获取 // private val viewModel: DetailViewModel = getViewModel() }底层调用链:ComponentActivity.viewModel()的实现位于 ActivityVM.kt,其核心逻辑是:
@MainThread inline fun <reified T : ViewModel> ComponentActivity.viewModel( qualifier: Qualifier? = null, noinline extrasProducer: (() -> CreationExtras)? = null, noinline parameters: (() -> ParametersHolder)? = null, ): Lazy<T> { return lazy(LazyThreadSafetyMode.NONE) { getViewModel(qualifier, extrasProducer, parameters) } }它返回Lazy<T>委托,首次访问时调用getViewModel(),最终进入 GetViewModel.kt 的resolveViewModel():创建KoinViewModelFactory,通过ViewModelProvider.create(viewModelStore, factory, extras)完成解析,并依据qualifier/key计算 ViewModel key(getViewModelKey的规则是:显式key优先;有qualifier时使用qualifier.value_className;否则用类名默认键)。Fragment侧的viewModel()/getViewModel()实现逻辑相同,只是额外支持通过ownerProducer指定ViewModelStoreOwner,见 FragmentVM.kt。
注意:懒加载使用
LazyThreadSafetyMode.NONE,且标注@MainThread,应在主线程访问。
共享 ViewModel:Fragment 与宿主 Activity 共用实例
多个 Fragment 需要共享同一个 ViewModel 时,使用activityViewModel():
by activityViewModel()—— 懒加载委托;getActivityViewModel()—— 立即获取。
class WeatherActivity : AppCompatActivity() { private val weatherViewModel: WeatherViewModel by viewModel() } class WeatherHeaderFragment : Fragment() { // 与 Activity 共享 private val weatherViewModel: WeatherViewModel by activityViewModel() } class WeatherListFragment : Fragment() { // 与 WeatherHeaderFragment 拿到同一个实例 private val weatherViewModel: WeatherViewModel by activityViewModel() }实现原理:activityViewModel()的默认ownerProducer是{ requireActivity() },即把宿主的 Activity 作为ViewModelStoreOwner,从而与 Activity 使用同一个ViewModelStore,见 FragmentActivityVM.kt。因此所有 Fragment 中解析到的是同一个实例,并随 Activity 销毁而清理。
为 ViewModel 传递参数
编译器插件 DSL
class DetailViewModel( @InjectedParam val itemId: String, private val repository: DetailRepository ) : ViewModel() val appModule = module { viewModel<DetailViewModel>() }注解
@KoinViewModel class DetailViewModel( @InjectedParam val itemId: String, private val repository: DetailRepository ) : ViewModel()经典 DSL
val appModule = module { viewModel { params -> DetailViewModel( itemId = params.get(), repository = get() ) } }注入点传参
在注入位置通过parametersOf(...)传入参数:
class DetailActivity : AppCompatActivity() { private val itemId: String by lazy { intent.getStringExtra("ITEM_ID")!! } // 注入时传入参数 private val viewModel: DetailViewModel by viewModel { parametersOf(itemId) } }参数最终会以ParametersHolder形式传入viewModel()的parameters参数(见上文 ActivityVM.kt 的函数签名),由 ViewModel 构造函数按位置或类型解析。
SavedStateHandle:自动注入
只要把SavedStateHandle加进 ViewModel 构造函数,Koin 就会自动注入,无需任何额外声明:
注解方式
@KoinViewModel class MyStateViewModel( private val handle: SavedStateHandle, private val repository: MyRepository ) : ViewModel()DSL 方式
class MyStateViewModel( private val handle: SavedStateHandle, private val repository: MyRepository ) : ViewModel() val appModule = module { viewModel<MyStateViewModel>() // 编译器插件 DSL // 或 viewModelOf(::MyStateViewModel) // 经典 DSL }使用
class DetailActivity : AppCompatActivity() { // SavedStateHandle 自动注入 private val viewModel: MyStateViewModel by viewModel() }实现原理:Koin 在参数解析阶段(AndroidParametersHolder.kt)会检测构造函数参数类型是否为SavedStateHandle,若是则通过CreationExtras.createSavedStateHandle()创建。当CreationExtras缺少SavedStateRegistryOwner时,会抛出带有明确提示的异常(提示将SavedStateHandle放在构造函数中而非懒加载/外部注入),该行为有专门的测试用例 SavedStateHandleErrorTest.kt 覆盖。
注意:所有
stateViewModel系列函数均已废弃,请统一使用viewModel函数——SavedStateHandle会自动注入。
导航图作用域 ViewModel
可以把 ViewModel 的作用域绑定到 Navigation graph,使同一导航图内的所有 Fragment 共享该实例:
class NavFragment : Fragment() { // 作用域绑定到导航图 private val navViewModel: NavViewModel by koinNavGraphViewModel(R.id.my_graph) }该 ViewModel 具备以下生命周期特征:
- 在图中第一个 Fragment 访问它时才创建;
- 同一导航图中的所有 Fragment 共享同一实例;
- 导航图被弹出(pop)时销毁。
实现原理:koinNavGraphViewModel定义在 NavGraphExt.kt(属于koin-androidx-navigation模块)。它通过findNavController().getBackStackEntry(navGraphId)拿到导航图的NavBackStackEntry作为ViewModelStoreOwner和默认CreationExtras,再复用Fragment.viewModel()完成解析,从而天然获得"随图创建、随图销毁"的生命周期。
ViewModel 与作用域依赖
如果 ViewModel 需要自己的作用域依赖(而不是根作用域),请使用 ViewModel Scope。声明方式:
val appModule = module { viewModelScope { scoped<UserCache>() scoped<UserRepository>() viewModel<UserViewModel>() } }注解方式配合@ViewModelScope:
@ViewModelScope class UserCache @ViewModelScope class UserRepository(private val cache: UserCache) @KoinViewModel @ViewModelScope class UserViewModel( private val repository: UserRepository ) : ViewModel()viewModelScope {}定义在 ViewModelScopeArchetypeDSL.kt,它会创建一个以ViewModelScopeArchetype为 qualifier 的作用域段(标记为@KoinExperimentalAPI,需启用viewModelScopeFactory()选项);@ViewModelScope注解定义见 CoreScopeArchetypes.kt。更完整的说明参见 Scopes(koin-core)。
ViewModel 通用 API(Generic API)
对于进阶场景(例如需要显式指定 key、owner 或 state),Koin 提供更低层的viewModelForClass:
// 从 ComponentActivity 或 Fragment 调用 val viewModel = viewModelForClass( clazz = MyViewModel::class, qualifier = null, owner = this, key = null, parameters = { parametersOf("param") } )其签名(见 ViewModelLazy.kt)支持clazz、qualifier、owner(ViewModelStoreOwner)、state(SavedStateDefinition)、key与parameters六个维度,返回Lazy<T>。其中key与qualifier会直接影响ViewModelStore中的实例键(参见上文getViewModelKey规则),适合需要手动控制实例复用场景的开发者。
Java 兼容:koin-android-compat
若项目以 Java 为主,可添加兼容依赖:
implementation "io.insert-koin:koin-android-compat:$koin_version"然后通过ViewModelCompat的静态方法获取:
MyViewModel viewModel = ViewModelCompat.getViewModel(this, MyViewModel.class);该 API 实现在 ViewModelCompat.kt,内部通过resolveViewModelCompat使用owner.viewModelStore与全局根作用域解析实例,同样支持qualifier、extrasProducer、parameters参数(另提供viewModel()返回Lazy的懒加载版本)。
快速参考
| 操作 | 代码 |
|---|---|
| 声明 ViewModel | viewModel<MyVM>()/@KoinViewModel |
| 在 Activity/Fragment 中注入 | by viewModel() |
| 与 Activity 共享 | by activityViewModel() |
| 传递参数 | by viewModel { parametersOf(id) } |
| 导航图作用域 | by koinNavGraphViewModel(R.id.graph) |
| 使用 SavedStateHandle | 直接加入构造函数即可 |
相关文档与源码索引
- ViewModel(koin-core 多平台 DSL)
- Scopes(含 ViewModel Scope)
- Testing(ViewModel 测试)
- Compose(Compose 中的 ViewModel)
- 核心解析实现:GetViewModel.kt、ModuleExt.kt(koin-core-viewmodel DSL)
- Android 注入扩展:ActivityVM.kt、FragmentVM.kt、FragmentActivityVM.kt
- 导航图与 Java 兼容:NavGraphExt.kt、ViewModelCompat.kt
- 完整可运行示例可参考仓库中的 androidx-samples 与 sample-android-compose 示例模块。
【免费下载链接】koinKoin - a pragmatic lightweight dependency injection framework for Kotlin & Kotlin Multiplatform项目地址: https://gitcode.com/gh_mirrors/ko/koin
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考