美文网首页Android-Jetpack
FragmentStatePagerAdapter与LiveDa

FragmentStatePagerAdapter与LiveDa

作者: Magic旭 | 来源:发表于2019-12-26 18:13 被阅读0次

    问题描述

    1. LiveData调用了setValue方法,observe也得不到回调。

    问题产生条件

    1. 在历史前辈的老代码下,因为需要在页面增加埋点曝光,埋点曝光系统会调用getItem获取当前不可见的fragment,拿到里面接口提供的方法作为上报参数。这时候老前辈们用的SparseArray把fragment的实例存在,根据position去拿里面对应的frament来复用。(可以忽略为什么用SparseArray,原本可能意思就是用下发id作为key的,后面竟然用了position当做key了)。
    2. 开发中用了ViewModel+LiveData实现MVVM模式,LiveData作为数据请求的发生者,observe在Fragment中最为一个订阅者。
    3. PagerAdapter使用的是FragmentStatePagerAdapter(超过limit数量,ViewPager会把fragment给onDetach掉)

    问题场景

    ViewPagerAdapter
    1. adapter是继承FragmentStatePagerAdapter的。
    2. 可以看到通过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(出错的用法)
    1. 用LiveData作为接口请求数据的发送者,在Fragment的onAttach方法注册observe。
      温馨提示:这里ViewModel的of方法建议大家都去看下源码,很简单的,就是把fragment或者Activity作为参数生成HolderFragment,然后将fragment or activity作为key,HolderFragment作为value存在HashMap中。
    2. 我们这里用activity作为of的参数,这样子我的viewMode生命周期将跟随activity的生命周期。
    3. 在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
    1. 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())
                    }
                })
        }
        ……
    }
    
    问题描述
    1. 当我的页面第一次进来的时候,liveData正常回调,当我的ViewPager切换,相邻页面超过limit,页面A被onDetach后,重新onAttach回来,这时候我viewModel里面的LiveData.setValue后,fragment的observe得不到任何回调。(注意,这时候出错的liveData.observe方法还是放在onAttach上面的)
    2. 问题截图,当fragmentA重新onAttach的时候,第二次打印owner的state是ONDESTROY


      State状态

    方案的启蒙者

    英文博客,需要翻墙

    LifecycleRegistry与LiveData源码解析

    LifecycleRegistry
    1. 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);
        }
    
    1. 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);
                }
            }
            ……
        }
    
    1. 这里就以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源码
    1. ObserverWithState类的dispatchEvent方法里,会调用GenericLifecycleObserver的onStateChanged方法。这里我们看其中一个子类的处理方式,大致上实现GenericLifecycleObserver接口的子类处理都大同小异。
    2. 在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());
            }
          ……
        }
    
    问题如果产生思考过程
    1. FragmentA重新onAttach上去后,我在onAttach的时候注册了Observer,onStateChanged方法被系统调用时候,就把我的Observer给移除了。
      试错:将注册方法放在onCreate里,还是没效果,因此思考了方法2.
    2. 在启蒙者给的思路,大概是Fragment重新onAttach上去后,内部的State永远都是处于DESTROYED状态,需要开发者手动去改变。
      总结:这就是用FragmentStatePagerAdapter与把fragment实例存下来导致的锅。如果是重新创建实例是没这个问题的。

    解决方案

    1. 需要在这个Fragment里面的生命周期内都手动handleLifecycleEvent。
    2. 抽象一个BaseFragment,把生命周期内都手动handleLifecycleEvent的代码写到基类里面,这个基类专门处理这种类型的场景。(个人推荐方法2)
    3. 中间遇到小插曲,也验证了我上面方法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)
        }
    }
    

    总结

    1. LiveData的源码可读性强,手机debug查看完美支持,建议大家抽空可以去自己阅读下。
    2. FragmentStatePagerAdapter是用在多个Fragment的场景下的,超过limit要被回收的。如果你在需求上遇到了也需要保持实例的引用,而且遇到了和我一样相同的问题,不妨一起讨论下。

    相关文章

      网友评论

        本文标题:FragmentStatePagerAdapter与LiveDa

        本文链接:https://www.haomeiwen.com/subject/vyfloctx.html