FragmentStatePagerAdapter与LiveDa
2019-12-26 本文已影响0人
Magic旭
问题描述
- LiveData调用了setValue方法,observe也得不到回调。
问题产生条件
- 在历史前辈的老代码下,因为需要在页面增加埋点曝光,埋点曝光系统会调用getItem获取当前不可见的fragment,拿到里面接口提供的方法作为上报参数。这时候老前辈们用的SparseArray把fragment的实例存在,根据position去拿里面对应的frament来复用。(可以忽略为什么用SparseArray,原本可能意思就是用下发id作为key的,后面竟然用了position当做key了)。
- 开发中用了ViewModel+LiveData实现MVVM模式,LiveData作为数据请求的发生者,observe在Fragment中最为一个订阅者。
- PagerAdapter使用的是FragmentStatePagerAdapter(超过limit数量,ViewPager会把fragment给onDetach掉)
问题场景
ViewPagerAdapter
- adapter是继承FragmentStatePagerAdapter的。
- 可以看到通过List数组里面判断是否有存到对应的实例,如果没有就重新创建,把实例存到数组里面。
class XXXXXlAllPagerAdapter(val context: Context, fm: FragmentManager, private val categoryList: List<XXXXXXX>)
: FragmentStatePagerAdapter(fm) {
override fun getItem(position: Int): Fragment? {
var result: Fragment? = mFragments.get(position)
if (result == null) {
result = if (categoryList[position].tab_type == MY_SUBSCRIBE_TYPE) {
generateMySubscribeFragment(categoryList[position])
} else {
generateFragment(categoryList[position])
}
mFragments.put(position, result)
}
return result
}
}
fragment里的LiveData(出错的用法)
- 用LiveData作为接口请求数据的发送者,在Fragment的onAttach方法注册observe。
温馨提示:这里ViewModel的of方法建议大家都去看下源码,很简单的,就是把fragment或者Activity作为参数生成HolderFragment,然后将fragment or activity作为key,HolderFragment作为value存在HashMap中。 - 我们这里用activity作为of的参数,这样子我的viewMode生命周期将跟随activity的生命周期。
- 在onAttach方法注册observe(owner,liveData),然后等待LiveData的回调。当owner状态是DESTROY时候,就不接受回调。(下面再细讲)
class XXXXXXListFragment : BaseXXXXXFragment(){
private var typeId: Long = 0L
private var mTabName: String? = null
private var mAdapter: XXXXXXAdapter? = null
private var datas: List<XXXXXX>? = null
private var viewModel: XXXXViewModel? = null
private val dataObserver: Observer<Resource<List<XXXXX>>> = Observer { res ->
when (res?.status) {
Status.LOADING -> {
……
}
Status.SUCCESS -> {
……
}
Status.ERROR -> {
……
}
}
}
companion object {
const val KEY_XXXX_ID = "key_xxxx_id"
const val KEY_XXXX_NAME = "key_xxxx_name"
const val INVALID_XXXX_ID = -1L
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
………
//重建后数据恢复
if (viewModel != null) {
datas = viewModel?.getDataOf(typeId)?.value?.data
}
}
override fun setUserVisibleCompat(isVisibleToUser: Boolean) {
super.setUserVisibleCompat(isVisibleToUser)
if (datas.isNullOrEmpty() && isVisibleToUser) {
refresh(typeId)
}
}
override fun onAttach(context: Context?) {
super.onAttach(context)
val idStr = arguments?.getString(KEY_XXXX_ID)
typeId = idStr?.toLongOrNull() ?: INVALID_XXXX_ID
mTabName = arguments?.getString(KEY_XXXX_NAME)
activity?.let {
//activity实例相同情况下,ViewModel返回的实例子也是一样的
viewModel = ViewModelProviders.of(
it,
ViewModelProvider.AndroidViewModelFactory.getInstance(it.application)
).get(ChannelAllListViewModel::class.java)
viewModel?.getDataOf(typeId)?.observe(this, dataObserver)
}
}
override fun onViewCreated(recyclerView: RecyclerView?, savedInstanceState: Bundle?) {
setupRecyclerView()
}
fun refresh(id: Long) {
if (id == ChannelAllListFragment.INVALID_CHANNEL_ID) {
//id invalid
return
}
val meta = getMetaData(id)
val res = getDataOf(id)
if (meta.loading) return
//请求接口
viewModel?.refresh(meta,res)
……
}
private fun setupRecyclerView() {
if (mAdapter == null) {
mAdapter = ChannelAllListAdapter(this)
}
datas?.let {
//Diff计算adapter的差异数据并刷新
mAdapter?.setData(it)
}
……
}
}
ViewModel
- ViewModel主要做的事情就是数据请求与数据二次处理。这里主要看点在于描述清楚我没有用错liveData的回调方法而已。
class XXXXXViewModel(application: Application) : AndroidViewModel(application) {
//MutableLiveResource可以自己看下源码就懂了
val tabList: MutableLiveResource<List<B>> = MutableLiveData()
private val dataMap: LongSparseArray<MutableLiveResource<out List<B>>> = LongSparseArray()
private val metaMap: LongSparseArray<A> = LongSparseArray()
//这里主要通过一个ViewModel把全部fragment的数据都存起来了
//meta就是接口返回的真实数据,res就是LiveData(一个LiveData对应一个页面的observe)
private fun refresh(meta: A, res: MutableLiveResource<out List<B>>) {
……
api.getAllChannel(accessKey, meta.typeId, meta.offset)
.enqueue(object : XXXXApiDataCallback<XXXX>() {
override fun onDataSuccess(data: XXXXX?) {
……
if (items != null && items.isNotEmpty()) {
……
meta.list.addAll(items)
postData(res, cloneItemList(meta.list))
} else {
onError(null)
}
}
override fun onError(t: Throwable?) {
……
res.value = Resource.error(t ?: BiliApiException())
}
})
}
……
}
问题描述
- 当我的页面第一次进来的时候,liveData正常回调,当我的ViewPager切换,相邻页面超过limit,页面A被onDetach后,重新onAttach回来,这时候我viewModel里面的LiveData.setValue后,fragment的observe得不到任何回调。(注意,这时候出错的liveData.observe方法还是放在onAttach上面的)
-
问题截图,当fragmentA重新onAttach的时候,第二次打印owner的state是ONDESTROY
State状态
方案的启蒙者
LifecycleRegistry与LiveData源码解析
LifecycleRegistry
- handleLifecycleEvent方法,将Lifecycle.Event转换成State类的对应状态。State的状态在LiveData里面会用到的。
public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
//getStateAfter太简单了,自己去看吧,不粘代码了
//主要就是
//ON_CREATE和ON_STOP 对应State的CREATED状态
//ON_START和ON_PAUSE对应State的STARTED状态
//ON_RESUME对应State的RESUMED
//ON_DESTROY对应State的DESTROYED
State next = getStateAfter(event);
moveToState(next);
}
- moveToState方法。内部源码主要就是讲observe方法存储起来的Observe是否新旧state状态是否一致,如果不一致就把存储起来的Observe状态全部变成newState。
private void sync() {
……
//isSynced判断是否需要更新状态
while (!isSynced()) {
……
if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) {
//注意这里和下面的方法,会涉及到这次bug出现的原因
backwardPass(lifecycleOwner);
}
……
if (!mNewEventOccurred && newest != null
&& mState.compareTo(newest.getValue().mState) > 0) {
//注意这里和上面的方法,会涉及到这次bug出现的原因
forwardPass(lifecycleOwner);
}
}
……
}
- 这里就以backwardPass方法讲解就可以了。首先遍历Map数组拿到Iterator,然后更新Observe的State状态值。
private void forwardPass(LifecycleOwner lifecycleOwner) {
Iterator<Entry<LifecycleObserver, ObserverWithState>> ascendingIterator =
mObserverMap.iteratorWithAdditions();
while (ascendingIterator.hasNext() && !mNewEventOccurred) {
……
//dispatchEvent将当前mState新状态值回调过去,接下来跟踪下dispatchEvent方法
observer.dispatchEvent(lifecycleOwner, upEvent(observer.mState));
……
}
}
}
LiveData源码
- ObserverWithState类的dispatchEvent方法里,会调用GenericLifecycleObserver的onStateChanged方法。这里我们看其中一个子类的处理方式,大致上实现GenericLifecycleObserver接口的子类处理都大同小异。
- 在LifecycleBoundObserver我们就能找到LiveData调用了setValue也得不到回调的原因了。
//这里是LiveData里面的一个内部类
class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
……
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
//内部判断当前owner的State状态,如果等于DESTROYED,直接移除了Observer
//如果我们添加上去也会被系统给移除了
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}
……
}
问题如果产生思考过程
- FragmentA重新onAttach上去后,我在onAttach的时候注册了Observer,onStateChanged方法被系统调用时候,就把我的Observer给移除了。
试错:将注册方法放在onCreate里,还是没效果,因此思考了方法2. - 在启蒙者给的思路,大概是Fragment重新onAttach上去后,内部的State永远都是处于DESTROYED状态,需要开发者手动去改变。
总结:这就是用FragmentStatePagerAdapter与把fragment实例存下来导致的锅。如果是重新创建实例是没这个问题的。
解决方案
- 需要在这个Fragment里面的生命周期内都手动handleLifecycleEvent。
- 抽象一个BaseFragment,把生命周期内都手动handleLifecycleEvent的代码写到基类里面,这个基类专门处理这种类型的场景。(个人推荐方法2)
- 中间遇到小插曲,也验证了我上面方法1是理解正确的。我刚开始用方案1时候,把LiveData的observe方法放在了handleLifecycleEvent(Lifecycle.Event.ON_CREATE)方法的前面,运行后发现setValue还是失效。突然想到看源码领悟的知识,将observe方法放在handleLifecycleEvent方法后面,就解决了这个bug了。
//抽一个基类,observe要放在super.onCreate之后执行
open class BaseLifecycleFragment : BaseFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(lifecycle as? LifecycleRegistry)?.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}
override fun onStart() {
super.onStart()
(lifecycle as? LifecycleRegistry)?.handleLifecycleEvent(Lifecycle.Event.ON_START)
}
override fun onResume() {
super.onResume()
(lifecycle as? LifecycleRegistry)?.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
override fun onPause() {
(lifecycle as? LifecycleRegistry)?.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
super.onPause()
}
override fun onStop() {
(lifecycle as? LifecycleRegistry)?.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
super.onStop()
}
override fun onDestroyView() {
super.onDestroyView()
(lifecycle as? LifecycleRegistry)?.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
}
}
总结
- LiveData的源码可读性强,手机debug查看完美支持,建议大家抽空可以去自己阅读下。
- FragmentStatePagerAdapter是用在多个Fragment的场景下的,超过limit要被回收的。如果你在需求上遇到了也需要保持实例的引用,而且遇到了和我一样相同的问题,不妨一起讨论下。