[译][Google工程师] 详解 FragmentFactory 如何优雅使用 Koin 以及部分源码分析

前言

在以前的文章 [译][Google工程师] 刚刚发布了 Fragment 的新特性 “Fragment 间传递数据的新方式” 以及源码分析 介绍了 Fragment 1.3.0 中添加了几个重要的 API。html

继续上一篇文章,介绍一下 FragmentFactory 和 FragmentContainerView 以及如何和 Koin 一块儿使用, 这是 Google 在 Fragment 1.2.0 上作的重要的更新,强烈建议你们去使用java

经过这篇文章你将学习到如下内容,将在译者思考部分会给出相应的答案android

  • FragmentFactory 是什么?
  • 什么状况下使用 FragmentFactory?
  • FragmentContainerView 是什么?
  • 为何 Google 强烈建议使用 FragmentContainerView?
  • Koin 如何和 FragmentFactory 一块儿使用以及部分源码分析?
  • 如何处理嵌套 Fragment?

这篇文章涉及不少重要新的知识点,带着本身理解,请耐心读下去,应该能够从中学到不少技巧。git

译文

如今咱们可使用 FragmentFactory 来完成 Fragment 构造函数的注入,可是这不是开发人员必须使用的 API, 在某些状况下,它能够被认为是一种很好的设计方法,帮助咱们测试带有外部依赖项的 Fragment。github

这篇文章将会解释什么是 FragmentFactory,何时以及如何使用它,如何处理嵌套 Fragment。算法

什么是 FragmentFactory?

以前 Fragment 的实例都是经过使用默认的空的构造函数进行实例化,这是由于系统须要在某些状况下从新初始化它,好比配置更改或者 App 的进程重建,若是没有默认构造函数的限制,系统不知道如何从新初始化 Fragment 实例。编程

FragmentFactory 出现就是为了解决这个限制,经过向其提供实例化 Fragment 所需的参数/依赖关系,FragmentFactory 能够帮助系统建立 Fragment 实例。性能优化

如何使用 FragmentFactory?

若是你的 Fragment 没有空的构造函数,您须要建立一个 FragmentFactory 来处理初始化它,经过继承 FragmentFactory 而且覆盖 FragmentFactory#instantiate() 来完成。bash

class CustomFragmentFactory(private val dependency: Dependency) : FragmentFactory() {
    override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
        if (className == CustomFragment::class.java.name) {
            return CustomFragment(dependency)
        }
        return super.instantiate(classLoader, className)
    }
}
复制代码

Fragment 是由 FragmentManagers 来管理的,因此为了使用 FragmentFactory 须要关联 FragmentManager,更具体的说它必须分配给包含 Fragment 组件的 FragmentManager,它能够是 Activity 或者 Fragment。app

何时 FragmentFactory 和 FragmentManager 作关联

FragmentFactory 负责在 Activity 和 parent Fragment 初始化 Fragment,因此应该在建立 Fragment 以前设置它。

  • 在建立 component’s View 以前:若是在 XML 中定义 Fragment,应该使用 Fragment 的 tag <fragment> 或者 FragmentContainerView。
  • 在建立 Fragment 以前:若是 Fragment 是动态添加的应该使用 FragmentTransaction。
  • 在系统恢复 Fragment 以前:若是是由于配置更改或者 App 的进程重建,致使 Fragment 重建。

有了这些限制,能够在 Activity#onCreate()Fragment#onCreate() 以前关联 FragmentFactory 和 FragmentManager,在这两个调用处 view 建立以前会从新初始化 Fragment。

这也就意味着应该在 super#onCreate() 以前关联 FragmentFactory 和 FragmentManager。

  • 在 Activity 关联 FragmentFactory 和 FragmentManager
class HostActivity : AppCompatActivity() {
    private val customFragmentFactory = CustomFragmentFactory(Dependency())

    override fun onCreate(savedInstanceState: Bundle?) {
        supportFragmentManager.fragmentFactory = customFragmentFactory
        super.onCreate(savedInstanceState)
        // ...
    }
}
复制代码
  • 在 Fragment 关联 FragmentFactory 和 FragmentManager
class ParentFragment : Fragment() {
    private val customFragmentFactory = CustomFragmentFactory(Dependency())

    override fun onCreate(savedInstanceState: Bundle?) {
        childFragmentManager.fragmentFactory = customFragmentFactory
        super.onCreate(savedInstanceState)
        // ...
    }
}
复制代码

须要使用 FragmentFactory 吗?

到目前为止,您可能已经使用它们的默认构造函数建立 Fragment,而后使用 Dagger 或 Koin 这样的库注入它们须要的依赖项,或者在它们被使用以前在 Fragment 中初始化它们。

若是你的 Fragment 有一个默认的空构造函数,那么就不须要使用 FragmentFactory,若是在 Fragment 构造函数中接受参数,必须使用 FragmentFactory,否者会抛出 Fragment.InstantiationException 异常

如何同时使用 Fragment 和 FragmentFactory?

只须要在建立 Fragment 以前,设置 FragmentFactory,它就会被用来实例化,这意味着在添加 Fragments 以前使用自定义的 FragmentFactory。

  • 静态添加: 使用 Fragment 的 tag <fragment> 和 FragmentContainerView。
<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/customFragment"
    android:name="com.example.CustomFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:tag="custom_fragment" />
复制代码

设置 FragmentFactory 用于初始化在Fragment 声明的 FragmentContainerView

class HostActivity : AppCompatActivity() {
    private val customFragmentFactory = CustomFragmentFactory(Dependency())

    override fun onCreate(savedInstanceState: Bundle?) {
        supportFragmentManager.fragmentFactory = customFragmentFactory
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_with_fragment_container_view)
    }
}
复制代码
  • 动态添加: 使用 FragmentTransaction#add() 方法动态的添加 Fragment
class HostActivity : AppCompatActivity() {
    private val customFragmentFactory = CustomFragmentFactory(Dependency())

    override fun onCreate(savedInstanceState: Bundle?) {
        supportFragmentManager.fragmentFactory = customFragmentFactory
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_with_empty_frame_layout)
        if (savedInstanceState == null) {
            supportFragmentManager.beginTransaction()
                .add(R.id.content, CustomFragment::class.java, arguments)
                .commit()
        }
    }
}
复制代码

FragmentFactory 和嵌套的 Fragment

若是 parent Fragment 包含嵌套的 Fragment 或者多层次嵌套的 Fragment,它们都会使用 parent Fragment 的相同 FragmentFactory,嵌套 Fragment 须要调用 Fragment#childFragmentManager.fragmentFactory

class ParentFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        childFragmentManager.fragmentFactory = parentFragmentFactory
        super.onCreate(savedInstanceState)
        if (savedInstanceState == null) {
            // Add NestedFragment
        }
    }
}

class NestedFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        childFragmentManager.fragmentFactory = childFragmentFactory
        super.onCreate(savedInstanceState)
        if (savedInstanceState == null) {
            // Add NestedNestedFragment
        }
    }
}

class NestedNestedFragment : Fragment()
复制代码

译者思考

咱们来总结一下 Fragment 几个重要的更新,以及在什么状况下使用:

  • 以前 Fragment 的实例都是经过使用默认的空的构造函数进行实例化的,FragmentFactory 出现就是为了解决这个限制。
  • FragmentFactory 不是必需要使用的,若是在 Fragment 构造函数中接受参数,必须使用 FragmentFactory
  • FragmentFactory 须要在 Activity 或者 Fragment 中使用,而且须要在 Activity#onCreate()Fragment#onCreate() 以前和 FragmentManager 作关联
  • 嵌套的 Fragment 或者多层次嵌套的 Fragment,使用的是相同 FragmentFactory
  • 正由于 FragmentFactory 出现,能够在 Fragment 构造函数中传递参数,意味着可使用 Koin 等框架,能够实现构造函数依赖注入,后面我会演示如何使用

接下来一块儿了解一下什么 FragmentContainerView,为何 Google 强烈建议使用 FragmentContainerView 容器来存储动态添加的 Fragment。

FragmentContainerView 是什么?为何 Google 强烈建议使用?

咱们先来看一下 Google 的更新说明:

FragmentContainerView: FragmentContainerView 是一个自定义 View 继承 FrameLayout,与 ViewGroups 不一样,它只接受 Fragment Views。

为何 Google 强烈建议使用?

以前在 Google issue 提了一个 fragment z-ordering 的问题,就是说 Fragment 进入和退出动画会致使一个问题,进入的 Fragment 会在退出的 Fragment下面,直到它彻底退出屏幕,这会致使在 Fragment 之间切换时产生错误的动画。

使用 FragmentContainerView 带来的好处是改进了对 fragment z-ordering 的处理。这是 Google 演示的例子,优化了两个 Fragment 退出和进入过渡不会互相重叠,使用 FragmentContainerView 将先开启退出动画而后才是进入动画。

Koin 如何和 FragmentFactory 一块儿使用以及源码分析

在以前的文章 [译][2.4K Start] 放弃 Dagger 拥抱 Koin 分析了 Koin 性能,若是没有看过,建议能够去了解一下。

Koin 团队在 2.1.0 版本开始支持 Fragment 的依赖注入,截图以下所示:

1. 添加 Koin Fragment 依赖

implementation "org.koin:koin-androidx-fragment:2.1.5"
复制代码

2. 建立 Fragment 并传递 ViewModel

class FragmentTest(val mainViewModel: MainViewModel) : Fragment(){
    ......
}
复制代码

3. 建立 Fragment modules

val viewModelsModule = module {
    viewModel { MainViewModel() }
}

val fragmentModules = module {
    fragment { FragmentTest(get()) }
}

val appModules = listOf(fragmentModules, viewModelsModule)
复制代码

4. 在调用 startKoin 方法时设置 KoinFragmentFactory

startKoin {
    AndroidLogger(Level.DEBUG)
    androidContext(this@App)
    fragmentFactory()
    loadKoinModules(appModules)
}
复制代码

fragmentFactory 是 KoinApplication 的扩展函数,提供了 KoinFragmentFactory 代码以下所示:

koin.loadModules(listOf(module {
        single<FragmentFactory> { KoinFragmentFactory() }
    }))
}
复制代码

一块儿来分析 KoinFragmentFactory 内部的源码:

class KoinFragmentFactory(val scope: Scope? = null) : FragmentFactory(), KoinComponent {

    override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
        val clazz = Class.forName(className).kotlin
        val instance = if (scope != null) {
            scope.getOrNull<Fragment>(clazz)
        }else{
            getKoin().getOrNull<Fragment>(clazz)
        }
        return instance ?: super.instantiate(classLoader, className)
    }

}
复制代码

继承 FragmentFactory 而且重写了 FragmentFactory#instantiate() 方法,在这个函数中,咱们使用 className 做为参数获取 Fragment,并尝试从 Koin 中检索 Fragment 实例

5. 在 onCreate 方法以前 调用 setupKoinFragmentFactory 绑定 FragmentFactory

override fun onCreate(savedInstanceState: Bundle?) {
    setupKoinFragmentFactory()
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
}
复制代码

6. 添加 Fragment 并传递 Bundle

val arguments = Bundle().apply {
    putString(FragmentTest.KEY_NAME, "来源于 MainActivity")
}

supportFragmentManager.beginTransaction()
    .replace(R.id.container, FragmentTest::class.java, arguments)
    .commit()
复制代码

相关源码已经上传到 JDataBinding 中, 能够查看 App、MainActivity、AppModule 和 FragmentTest 这几个类

参考文献

结语

致力于分享一系列 Android 系统源码、逆向分析、算法、翻译相关的文章,目前正在翻译一系列欧美精选文章,请持续关注,除了翻译还有对每篇欧美文章思考,若是对你有帮助,请帮我点个赞,感谢!!!期待与你一块儿成长。

文章列表

Android 10 源码系列

Android 应用系列

工具系列

逆向系列

相关文章
相关标签/搜索