
Koin Android ViewModel 完整指南生命周期感知注入、声明式 DSL 与作用域实战【免费下载链接】koinKoin - a pragmatic lightweight dependency injection framework for Kotlin Kotlin Multiplatform项目地址: https://gitcode.com/gh_mirrors/ko/koinKoin 为 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 如何支持 ViewModelViewModel 是 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参见 ViewModelkoin-coreCompose Multiplatform 场景参见 Compose ViewModel。作用域限制为什么 ViewModel 拿不到 Activity 作用域依赖需要特别强调的是ViewModel 是在 Koin 根作用域root scope下创建的无法访问 Activity 或 Fragment 作用域中的依赖。这样设计的目的在于防止内存泄漏——ViewModel 的存活时间通常长于其宿主 Activity / Fragment如果它持有了宿主作用域内的对象引用就会导致宿主无法被回收。如果你的 ViewModel 确实需要作用域依赖官方建议使用 ViewModel Scopekoin-core/scopes 创建一个与 ViewModel 生命周期绑定的独立作用域下文ViewModel 与作用域依赖一节有完整示例。声明 ViewModel三种 DSL 方式Koin 提供三种声明 ViewModel 定义的方式可按项目风格选用。方式一编译器插件 DSLCompiler Plugin DSLval appModule module { viewModelDetailViewModel() viewModelUserViewModel() }方式二注解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 注解参考。方式三经典 DSLClassic DSLval appModule module { // 构造引用方式 viewModelOf(::DetailViewModel) // Lambda 方式 viewModel { DetailViewModel(get()) } }实现原理无论哪种 DSLModule.viewModel本质上都注册为 Koin 的factory定义每次获取新建实例同时由 AndroidX 的ViewModelStore保证同一 store 内的复用。见 koin-core-viewmodel 的 ModuleExt.ktinline fun reified T : ViewModel Module.viewModel( qualifier: Qualifier? null, noinline definition: DefinitionT ): KoinDefinitionT { 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, ): LazyT { return lazy(LazyThreadSafetyMode.NONE) { getViewModel(qualifier, extrasProducer, parameters) } }它返回LazyT委托首次访问时调用getViewModel()最终进入 GetViewModel.kt 的resolveViewModel()创建KoinViewModelFactory通过ViewModelProvider.create(viewModelStore, factory, extras)完成解析并依据qualifier/key计算 ViewModel keygetViewModelKey的规则是显式key优先有qualifier时使用qualifier.value_className否则用类名默认键。Fragment侧的viewModel()/getViewModel()实现逻辑相同只是额外支持通过ownerProducer指定ViewModelStoreOwner见 FragmentVM.kt。注意懒加载使用LazyThreadSafetyMode.NONE且标注MainThread应在主线程访问。共享 ViewModelFragment 与宿主 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 传递参数编译器插件 DSLclass DetailViewModel( InjectedParam val itemId: String, private val repository: DetailRepository ) : ViewModel() val appModule module { viewModelDetailViewModel() }注解KoinViewModel class DetailViewModel( InjectedParam val itemId: String, private val repository: DetailRepository ) : ViewModel()经典 DSLval 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 { viewModelMyStateViewModel() // 编译器插件 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 { scopedUserCache() scopedUserRepository() viewModelUserViewModel() } }注解方式配合ViewModelScopeViewModelScope 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。更完整的说明参见 Scopeskoin-core。ViewModel 通用 APIGeneric API对于进阶场景例如需要显式指定 key、owner 或 stateKoin 提供更低层的viewModelForClass// 从 ComponentActivity 或 Fragment 调用 val viewModel viewModelForClass( clazz MyViewModel::class, qualifier null, owner this, key null, parameters { parametersOf(param) } )其签名见 ViewModelLazy.kt支持clazz、qualifier、ownerViewModelStoreOwner、stateSavedStateDefinition、key与parameters六个维度返回LazyT。其中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的懒加载版本。快速参考操作代码声明 ViewModelviewModelMyVM()/KoinViewModel在 Activity/Fragment 中注入by viewModel()与 Activity 共享by activityViewModel()传递参数by viewModel { parametersOf(id) }导航图作用域by koinNavGraphViewModel(R.id.graph)使用 SavedStateHandle直接加入构造函数即可相关文档与源码索引ViewModelkoin-core 多平台 DSLScopes含 ViewModel ScopeTestingViewModel 测试ComposeCompose 中的 ViewModel核心解析实现GetViewModel.kt、ModuleExt.ktkoin-core-viewmodel DSLAndroid 注入扩展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),仅供参考