news 2026/9/24 15:49:53

Koin Android ViewModel 完整指南:生命周期感知注入、声明式 DSL 与作用域实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Koin Android ViewModel 完整指南:生命周期感知注入、声明式 DSL 与作用域实战

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提供了一整套生命周期感知的注入能力:既支持在ActivityFragmentService中通过by viewModel()懒加载获取实例,也支持activityViewModel()共享、参数传递、SavedStateHandle自动注入、导航图作用域以及@KoinViewModel注解/编译器插件声明等高级用法。本文基于 docs/reference/koin-android/viewmodel.md 展开,并结合仓库源码(koin-core-viewmodelkoin-androidkoin-androidx-navigationkoin-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:懒加载与立即获取

ActivityFragmentService中注入 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)支持clazzqualifierownerViewModelStoreOwner)、stateSavedStateDefinition)、keyparameters六个维度,返回Lazy<T>。其中keyqualifier会直接影响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与全局根作用域解析实例,同样支持qualifierextrasProducerparameters参数(另提供viewModel()返回Lazy的懒加载版本)。

快速参考

操作代码
声明 ViewModelviewModel<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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 15:47:23

从ET1100到AX58100:EtherCAT从站硬件改版实战记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 15:40:57

简述 Word Embedding 可以怎样运用于文本分类任务?

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:AI大模型原理和应用面试题 文章目录 一、🍀回答重点 二、🍀扩展知识 一、🍀回答重点 Word Embedding 可以通过将词汇表示为稠密的向量,从而使文本…

作者头像 李华