IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> FragmentFactory + Koin 实现Fragment依赖注入 -> 正文阅读

[移动开发]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:

例如下面的扩展库提供了对 FragmentViewModel 的依赖注入能力

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 通过构造参数依赖了 DetailsViewModelDetailsViewModel 又通过构造参数依赖 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

applicationonCreate 中,对 Koin 进行初始化,即加载 fragmentModulesviewModelsModule 等各类 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 时会调用 KoinFragmentFactoryinstantiate

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#onCreateFragment#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 允许构造参数通过依赖注入获取。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-27 16:20:36  更:2021-07-27 16:23:09 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/7 6:21:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码