FragmentFactory + Koin 实现Fragment依赖注入
Posted fundroid_方卓
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FragmentFactory + Koin 实现Fragment依赖注入相关的知识,希望对你有一定的参考价值。
在 《FragmentFactory介绍:构建Fragment的好帮手 》这一文章的结尾处,我对 FragmentFactory 做过如下点评:
“ FragmentFactory 允许开发者使用带参数的构造函数创建 Fragment, 能够在 dagger、koin 等DI 框架的使用场景中发挥作用。”
这之后就有人询问其在 DI 中的具体使用方式。 因此本文以 Koin 为例,介绍如何基于 FragmentFactory 实现 Fragment 的依赖注入
Koin 简介
相信不少朋友对 Koin 都有所了解了,这里再做一个简单介绍:
Koin 是一个轻量级的依赖注入框架,通过 Kotlin 的 DSL 完成配置,全程无反射无代码生成。相对于Dagger/Hilt 来说有以下特点:
- 易上手:Dagger 的学习曲线陡峭,Hilt 好一些,但仍没有 Koin 简单易用
- 编译速度快:Koin 没有额外的代码生成,编译速度快
- 轻量:Dagger/Hilt 在编译后会生大量代码,增加安装包体积,Koin 没有这种烦恼
当然,Koin 也有一些不足,比如缺少编译期检查,需要集中配置不够智能等, 所以综合来说,在大型项目中推荐使用 Dagger/Hilt,而中小项目中 Koin 是一个不错的选择。
Koin DSL
Koin 基于 Kotlin DSL 完成 DI 配置,常用的 DSL 有以下几个:
- module { } : 类似于 Dagger 的
@Module
,提供依赖的单元模块 - factory { } : 类似于 Dagger 的
@Provide
,提供依赖对象,每次都会生成新的实例 - single { } : 与
factory{}
功能一样,相对于 factory 提供多实例,single 提供单例
需要注意的是 Koin 并不只能用于 android,所以上面的 DSL 在任意 Kotlin 项目中是通用的。 Koin 面向 Android 提供了专用的扩展库及 DSL:
例如下面的扩展库提供了对 Fragment
、ViewModel
的依赖注入能力
implementation "org.koin:koin-androidx-fragment:$koin_version"
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
上面是对 Koin 的一本基本介绍。 本文还是聚焦在 Koin 中如何使用 FragmentFactoy 实现 Fragment 的依赖注入
创建 Modules
通常情况,在 module{}
中通过 factory{}
提供所需的依赖。
private val fragmentModules = module {
fragment { HomeFragment() }
fragment { DetailsFragment(get()) } //通过get()获取依赖的参数
}
private val viewModelsModule = module {
viewModel { DetailsViewModel(get()) }
}
private val dataModule = module {
single { DetailsRepository() }
}
//appModules 聚合了所有Koin Modules
val appModules = listOf(fragmentModules, viewModelsModule, dataModule)
而 fragment{}
和 viewmodel{}
是 koin-androidx
为 Fragment 和 ViewModle 提供的 DSL, 本质就是一个 factory {}
:
inline fun <reified T : Fragment> Module.fragment(
qualifier: Qualifier? = null,
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<T>> = factory(qualifier, definition)
上面代码的 DetailFragment
通过构造参数依赖了 DetailsViewModel
, DetailsViewModel
又通过构造参数依赖 DetailsRepository
inline fun <reified T : Any> get(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
): T {
return get(T::class, qualifier, parameters)
}
get()
是 Koin 中常见的获取依赖的方法,通过泛型 T
对应的 class 作为 Key 查找(或创建)被依赖的对象:
inline fun <reified T : Any> get(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
): T {
return get(T::class, qualifier, parameters)
}
reified
关键字通过类型推断,帮助 Koin 减少模板代码,这个技巧在 Koin 中被广泛使用。
加载 Modules
在 application
的 onCreate
中,对 Koin 进行初始化,即加载 fragmentModules
、 viewModelsModule
等各类 Koin 的 Modules。
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@App)
fragmentFactory() // 添加 KoinFragmentFactory
loadKoinModules(appModules)
}
}
fragmentFactory()
是一个扩展函数,通过加载 fragmentFactoryModule
, 为 Fragment 的构建提供 KoinFragmentFatory
private val fragmentFactoryModule = module {
single<FragmentFactory> { KoinFragmentFactory() }
}
fun KoinApplication.fragmentFactory() {
koin.loadModules(listOf(fragmentFactoryModule))
}
KoinFragmentFactory
当 FragmentTransaction
创建 Fragment 时会调用 KoinFragmentFactory
的 instantiate
class KoinFragmentFactory : FragmentFactory() {
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
val clazz = Class.forName(className).kotlin
val instance = getKoin().getOrNull<Fragment>(clazz) //通过 Koin 创建 Fragment
return instance ?: super.instantiate(classLoader, className)
}
}
KoinFragmentFactory 通过 Koin 创建 Fragment。 具体是通过 fragment 的 class 作为 key 找到对应 factory ,而这个 factory 就是上面定义的 fragment{...}
看到这里,整个流程就很清楚了:
Koin 通过 KoinFragmentFactory 创建 Fragment,构造函数中允许有参数,可以通过 koin 的依赖注入获取
之前的文章中介绍过,FragmentFactory 需要被设置到 FragmentManager 中使用。那么 KoinFragmentFactory 是何时被设置的呢?
setupKoinFragmentFactory()
override fun onCreate(savedInstanceState: Bundle?) {
setupKoinFragmentFactory()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
需要在 Activity#onCreate
或 Fragment#onCreate
中调用 setupKoinFragmentFactory()
, 将 KoinFragmentFactory 添加到当前 FragmentManager 中。
fun FragmentActivity.setupKoinFragmentFactory(scope: Scope? = null) {
if (scope == null) {
supportFragmentManager.fragmentFactory = get()
} else {
supportFragmentManager.fragmentFactory = KoinFragmentFactory(scope)
}
}
需要特别注意,这个调用必须在
super.onCreate
之前完成,因为super.onCreate
中会进行 fragment 的重建, 此时就需要用到 FragmentFactory 了
当 FragmentFactory 设置完毕后,我们在 MainActivity
中添加 DetailsFragment
:
supportFragmentManager.beginTransaction()
.replace(R.id.container, DetailsFragment::class.java, null)
.commit()
FragmentTransaction
会自动调用 KoinFragmentFactory#instantiate()
创建
DetailsFragment::class.java
对应的 Fragment。
通过断点调试,可以确认,DetailsFragment 中的 viewModel
被成功注入
因为屏幕旋转等造成 Fragment 销毁重建时,viewModel 可以被再次注入,状态不会丢失。
如果在 replace()
中为 Fragment 添加了参数 arguments
val arguments = Bundle().apply {
putString("key", "value")
}
supportFragmentManager.beginTransaction()
.replace(R.id.container, HomeFragment::class.java, arguments)
.commit()
FragmentTransaction
会在 FragmentFactory
创建完 Fragment 后,通过 setArguments
设置这些参数
不使用 get() 获取依赖
上面例子中 Fragment 通过 get()
获取参数依赖。实际上,Koin 提供了更加简单的方式:
private val fragmentModules = module {
fragment<HomeFragment>()
fragment<DetailsFragment>()
}
仅仅声明一个泛型类型,不显示的调用构造函数。
//显示调用构造函数
inline fun <reified T : Fragment> Module.fragment(
qualifier: Qualifier? = null,
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<T>> = factory(qualifier, definition)
//不调用构造函数
inline fun <reified T : Fragment> Module.fragment(
qualifier: Qualifier? = null
): Pair<Module, InstanceFactory<T>> = factory(qualifier) { newInstance(it) } //调用newInstance
Definition<T>
是一个工厂,我们在里面显示调用构造函数,不调用构造函数时,Koin 帮我们调用了 newInstance()
:
fun <T : Any> Scope.newInstance(kClass: KClass<T>, params: ParametersHolder): T {
val instance: Any
val constructor = kClass.java.constructors.firstOrNull()
?: error("No constructor found for class '${kClass.getFullName()}'")
val args = getArguments(constructor, this, params)
instance = createInstance(args, constructor)
return instance as T
}
这里使用了一点反射,获取了构造函数以及构造函数的参数信息
fun getArguments(constructor: Constructor<*>, scope: Scope, parameters: ParametersHolder): Array<Any> {
val length = constructor.parameterTypes.size
return if (length == 0) emptyArray()
else {
val result = Array<Any>(length) {}
for (i in 0 until length) {
val p = constructor.parameterTypes[i]
val parameterClass = p.kotlin
result[i] = scope.getOrNull(parameterClass, null) { parameters } ?: parameters.getOrNull(parameterClass) ?: throw NoBeanDefFoundException("No definition found for class '$parameterClass'")
}
result
}
}
最后根据构造参数类型从 Koin 中成功获取依赖。 这种方式省掉了 get()
,写法更加简单。
一句话总结
FragmentFactory 允许 Fragment 的构造函数中出现参数,而 Koin 允许构造参数通过依赖注入获取。
以上是关于FragmentFactory + Koin 实现Fragment依赖注入的主要内容,如果未能解决你的问题,请参考以下文章
如何在运行时使用带有参数的 FragmentFactory?