- 原標題: Android Fragments: FragmentFactory
- 原文地址: proandroiddev.com/fragmentfac…
- 原文做者:Husayn Hakeem
在以前的文章 [譯][Google工程師] 剛剛發佈了 Fragment 的新特性 「Fragment 間傳遞數據的新方式」 以及源碼分析 介紹了 Fragment 1.3.0 中添加了幾個重要的 API。html
繼續上一篇文章,介紹一下 FragmentFactory 和 FragmentContainerView 以及如何和 Koin 一塊兒使用, 這是 Google 在 Fragment 1.2.0 上作的重要的更新,強烈建議你們去使用java
經過這篇文章你將學習到如下內容,將在譯者思考部分會給出相應的答案android
這篇文章涉及不少重要新的知識點,帶着本身理解,請耐心讀下去,應該能夠從中學到不少技巧。git
如今咱們可使用 FragmentFactory 來完成 Fragment 構造函數的注入,可是這不是開發人員必須使用的 API, 在某些狀況下,它能夠被認爲是一種很好的設計方法,幫助咱們測試帶有外部依賴項的 Fragment。github
這篇文章將會解釋什麼是 FragmentFactory,何時以及如何使用它,如何處理嵌套 Fragment。算法
以前 Fragment 的實例都是經過使用默認的空的構造函數進行實例化,這是由於系統須要在某些狀況下從新初始化它,好比配置更改或者 App 的進程重建,若是沒有默認構造函數的限制,系統不知道如何從新初始化 Fragment 實例。編程
FragmentFactory 出現就是爲了解決這個限制,經過向其提供實例化 Fragment 所需的參數/依賴關係,FragmentFactory 能夠幫助系統建立 Fragment 實例。性能優化
若是你的 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 負責在 Activity 和 parent Fragment 初始化 Fragment,因此應該在建立 Fragment 以前設置它。
<fragment>
或者 FragmentContainerView。有了這些限制,能夠在 Activity#onCreate() 和 Fragment#onCreate() 以前關聯 FragmentFactory 和 FragmentManager,在這兩個調用處 view 建立以前會從新初始化 Fragment。
這也就意味着應該在 super#onCreate() 以前關聯 FragmentFactory 和 FragmentManager。
class HostActivity : AppCompatActivity() {
private val customFragmentFactory = CustomFragmentFactory(Dependency())
override fun onCreate(savedInstanceState: Bundle?) {
supportFragmentManager.fragmentFactory = customFragmentFactory
super.onCreate(savedInstanceState)
// ...
}
}
複製代碼
class ParentFragment : Fragment() {
private val customFragmentFactory = CustomFragmentFactory(Dependency())
override fun onCreate(savedInstanceState: Bundle?) {
childFragmentManager.fragmentFactory = customFragmentFactory
super.onCreate(savedInstanceState)
// ...
}
}
複製代碼
到目前爲止,您可能已經使用它們的默認構造函數建立 Fragment,而後使用 Dagger 或 Koin 這樣的庫注入它們須要的依賴項,或者在它們被使用以前在 Fragment 中初始化它們。
若是你的 Fragment 有一個默認的空構造函數,那麼就不須要使用 FragmentFactory,若是在 Fragment 構造函數中接受參數,必須使用 FragmentFactory,否者會拋出 Fragment.InstantiationException 異常
只須要在建立 Fragment 以前,設置 FragmentFactory,它就會被用來實例化,這意味着在添加 Fragments 以前使用自定義的 FragmentFactory。
<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)
}
}
複製代碼
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()
}
}
}
複製代碼
若是 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 幾個重要的更新,以及在什麼狀況下使用:
接下來一塊兒瞭解一下什麼 FragmentContainerView,爲何 Google 強烈建議使用 FragmentContainerView 容器來存儲動態添加的 Fragment。
咱們先來看一下 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 將先開啓退出動畫而後纔是進入動畫。
在以前的文章 [譯][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 系統源碼、逆向分析、算法、翻譯相關的文章,目前正在翻譯一系列歐美精選文章,請持續關注,除了翻譯還有對每篇歐美文章思考,若是對你有幫助,請幫我點個贊,感謝!!!期待與你一塊兒成長。