在"單Activity"架構(gòu)的App中柏卤,頁面功能常常由Fragment承載全释,而Fragment之間的通信方式往往有兩種:通過Fragment Result API或ViewModel;通過Fragment Result API的格式如下:
class FragmentA : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 此函數(shù)在 fragment-ktx 中
setFragmentResultListener("requestKey") { requestKey, bundle ->
val result = bundle.getString("bundleKey")
}
}
}
class FragmentB : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
button.setOnClickListener {
val result = "result"
// 此函數(shù)在 fragment-ktx 中
setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}
}
}
FragmentA
只有處于STARTED
狀態(tài)下才會接收到結(jié)果井氢。此種方式適用于比較簡單的參數(shù)通信狰挡,對于比較大的參數(shù)(比如Bitmap
)或者比較復(fù)雜的通信邏輯吴趴,ViewModel才是更好的選擇。Fragment通過ViewModel通信也比較簡單亭畜,只需要在創(chuàng)建ViewModel時扮休,指定ViewModelStore
為Fragment的宿主Activity即可,這樣多個Fragment中獲取到的是同一個ViewModel拴鸵,fragment-ktx庫也提供了activityViewModels()
函數(shù)可以直接獲取宿主Activity持有的ViewModel玷坠。
上面的方式非常簡單也非常方便,但也帶來了一些問題劲藐,由于ViewModel存儲在Activity的ViewModelStore中八堡,當(dāng)使用此ViewModel的Fragment全都被銷毀時,ViewModel仍然不會被釋放聘芜,事實上此ViewModel已經(jīng)泄漏了兄渺,除此以外,當(dāng)我們再次創(chuàng)建Fragment使用ViewModel時厉膀,ViewModel中仍然是上一次使用時產(chǎn)生的數(shù)據(jù)溶耘,由此可能會帶來一些難以預(yù)料的問題二拐。
為了解決上面的兩個問題,比較常見的解決方案是在使用的Fragment銷毀時凳兵,手動將ViewModel中的數(shù)據(jù)重置百新,如下:
class TestViewModel : ViewModel() {
private val dataList: MutableList<String> = mutableListOf()
fun addData(string: String) {
dataList.add(string)
}
// 沒有使用onCleared()是因為此函數(shù)無法在外部調(diào)用
fun tearDown() {
dataList.clear()
}
}
然后在Fragment的onDestory()
中調(diào)用TestViewModel#tearDown()
即可。
這種手動調(diào)用的方式比較繁瑣庐扫,可以借助LifecycleEventObserver
以及ViewModel的onCleared()
函數(shù)來自動進行“拆卸”ViewModel:
open class AutoTearDownViewModel(
private val lifecycleOwner: LifecycleOwner
) : ViewModel(), LifecycleEventObserver {
init {
if (lifecycleOwner.lifecycle.currentState == Lifecycle.State.DESTROYED) {
onCleared()
} else {
lifecycleOwner.lifecycle.addObserver(this)
}
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
onCleared()
lifecycleOwner.lifecycle.removeObserver(this)
}
}
}
class TestViewModel(val lifecycleOwner: LifecycleOwner) : AutoTearDownViewModel(lifecycleOwner) {
private val dataList: MutableList<String> = mutableListOf()
override fun onCleared() {
dataList.clear()
}
}
class AutoTearDownViewModelFactory(
private val lifecycleOwner: LifecycleOwner
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return modelClass.getConstructor(LifecycleOwner::class.java).newInstance(lifecycleOwner)
}
}
在Fragment中可以通過使用ViewModelProvider(requireActivity(), AutoTearDownViewModelFactory(this))
創(chuàng)建ViewModel饭望,在多個Fragment中使用時,創(chuàng)建的ViewModel會跟隨第一個創(chuàng)建此ViewModel的Fragment的生命周期形庭。
此種方式雖然不用我們手動去調(diào)用清理函數(shù)铅辞,但是仍然無法解決ViewModel泄漏的問題,并且還是依賴開發(fā)者手動在onCleared()
中清理或重置數(shù)據(jù)萨醒,在開發(fā)時很容易忘記斟珊。
那如何能夠讓不再使用的ViewModel能夠自動從Activity的ViewModelStore中消失呢?在上面我們通過LifecycleEventObserver
實現(xiàn)了當(dāng)Fragment銷毀時自動清理ViewModel富纸,剩下需要做的就是當(dāng)Fragment銷毀時囤踩,將ViewModel從Activity的ViewModelStore中刪掉。通過查看ViewModelStore
的源碼不難發(fā)現(xiàn)晓褪,ViewModel是存儲在HashMap<String, ViewModel>
中堵漱,此字段是private
的,所以我們需要通過反射拿到這個Map涣仿,從而將ViewModel刪除勤庐,所以我們將AutoTearDownViewModel
進行以下改造:
// AutoTearDownViewModel.kt
const val VIEW_MODEL_KEY = "auto_tear_down_view_model"
fun <T> getVmKey(clazz: Class<T>): String {
return VIEW_MODEL_KEY + ":" + clazz.canonicalName
}
open class AutoTearDownViewModel(
private val fragment: Fragment
) : ViewModel(), LifecycleEventObserver {
init {
if (fragment.lifecycle.currentState == Lifecycle.State.DESTROYED) {
onCleared()
} else {
fragment.lifecycle.addObserver(this)
}
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
val mapField = ViewModelStore::class.java.getDeclaredField("mMap")
mapField.isAccessible = true
val viewModelMap = mapField.get(fragment.requireActivity().viewModelStore) as HashMap<String, ViewModel>
viewModelMap.remove(getVmKey(this::class.java))?.let {
val method = ViewModel::class.java.getDeclaredMethod("clear")
method.isAccessible = true
method.invoke(it)
}
fragment.lifecycle.removeObserver(this)
}
}
}
class AutoTearDownViewModelFactory(
private val fragment: Fragment,
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return modelClass.getConstructor(Fragment::class.java).newInstance(fragment) as T
}
}
通過上面的改造后,ViewModel會跟隨第一個創(chuàng)建它的Fragment的生命周期好港,當(dāng)此Fragment銷毀時愉镰,ViewModel也將被清理掉,下次再進入到此Fragment時媚狰,會創(chuàng)建一個新的ViewModel岛杀。
需要注意的是,上面的代碼在remove
了ViewModel
后崭孤,會調(diào)用一次ViewModel
的clear()
方法类嗤,而此方法是在androidx.lifecycle:lifecycle-viewmodel:2.1.0-alpha01
版本之后才被添加進去,如果是之前的版本辨宠,則需要調(diào)用onCleared()
方法遗锣。
為了使用方便,我們可以借助Kotlin擴展函數(shù)以及委派嗤形,實現(xiàn)一個類似activityViewModels()
的函數(shù):
// AutoTearDownViewModelProvider.kt
inline fun <reified VM : ViewModel> Fragment.autoTearDownViewModel(): Lazy<VM> {
return AutoTearDownViewModelLazy(VM::class, this)
}
class AutoTearDownViewModelLazy<VM : ViewModel>(
private val viewModelClass: KClass<VM>,
private val fragment: Fragment
) : Lazy<VM> {
private var cached: VM? = null
override val value: VM
get() {
return cached
?: ViewModelProvider(
fragment.requireActivity(),
AutoTearDownViewModelFactory(fragment)
).get(getVmKey(viewModelClass.java), viewModelClass.java).also {
cached = it
}
}
override fun isInitialized() = cached != null
}
上述提到的解決方案也并非完美精偿,由于用到了反射,并且反射的字段和方法非public
,所以在之后的版本中如果ViewModel或ViewModelStore修改了方法聲明可能會導(dǎo)致失效笔咽;對于配置更改的情況(比如旋轉(zhuǎn)屏幕)搔预,ViewModel仍然會被重建,這其實是和ViewModel的初衷相背的叶组,但如果App限制了只能使用豎屏拯田,那么此種方案也不失為一種比較好的方式。