「Do.034」探索兼容的Fragment懒加载模式

image

首发公众号:黑客五六七
作者:贤榆的榆
如果喜欢,请关注、赞赏、点在看
阅读时间:1738字 6分钟

Fragment的懒加载,通常都是在一个Activity中通过ViewPager管理了多个Fragment界面时,会用到的一种模式。当我们每个Fragment都很复杂的时候,为了保证整个Activity的流畅度,我们通常会将第一个Fragment先加载出来,后面的Fragment在其可见时再加载。每当我们想到Fragment懒加载的时候通常都是使用setUserVisiable()方法配合onViewCreated方法来做懒加载,但是你会发现当使用ViewPager2+Fragment的时候,它就并不起作用了。由于种种的历史原因,现在的Fragment嵌套的方式比较多了,所以这里研究一下这些嵌套方式,看看是否能够找到一个合适方式懒加载方式作为我这个BaseLazyFragment的实现,同时兼容这几种嵌套方式。

当然,我的出发点是仍是从生命周期开始探索。这里先温故一下Fragment的生命周期吧:


image

为了能够更好的观察几种嵌套方式在滑动过程中的引用,除了上面的生命周期之外,我还在日志中打印了onViewCreated和setUserVisibleHint()方法的调用时机。下面看一下我的日志代码:

class LifeCycleFragment : Fragment() {
    companion object {
        fun create(position: Int): LifeCycleFragment {
            val fragment = LifeCycleFragment()
            fragment.arguments = bundleOf(Pair("p", position))
            return fragment
        }
    }

    var position: String = ""

    override fun onAttach(context: Context) {
        position = arguments?.getInt("p").toString()
        super.onAttach(context)
        Log.d("ResumeOnly", "Fragment${position}onAttach")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("ResumeOnly", "Fragment${position}onCreate")
    }


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        Log.d("ResumeOnly", "Fragment${position}onCreateView")
        return TextView(activity).apply {
            text = position
            layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
            textSize = 100f
            typeface = Typeface.DEFAULT_BOLD
            gravity = Gravity.CENTER
        }
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        Log.d("ResumeOnly", "Fragment${position}onActivityCreated")
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Log.d("ResumeOnly", "Fragment${position}onViewCreated")
    }


    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        super.setUserVisibleHint(isVisibleToUser)
        Log.d("ResumeOnly", "Fragment${position}isVisibleToUser$isVisibleToUser")
    }

    override fun onStart() {
        super.onStart()
        Log.d("ResumeOnly", "Fragment${position}onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d("ResumeOnly", "Fragment${position}onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d("ResumeOnly", "Fragment${position}onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d("ResumeOnly", "Fragment${position}onStop")
    }

    override fun onDestroyView() {
        super.onDestroyView()
        Log.d("ResumeOnly", "Fragment${position}onDestroyView")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("ResumeOnly", "Fragment${position}onDestroy")
    }

}

第一种嵌套:ViewPager+FragmentPagerAdapter(fa,BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)的生命周期

image

通过上面的Log日志,其实不难看出通过ViewPager+FragmentPagerAdapter(fm,BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)进行管理Fragment,在默认没有设置offscreenPageLimit的值时,也会帮我们多预加载一个,而预加载的这个是走到了onStart方法,当左滑时,原来预加载的这个会走onResume,同时再预加载下一个。很明显我们的lazyLoad方法可以放到onResume去做。当我们滑动到当前页的时候走当前页的onResume方法然后再onResume中调用lazyLoad方法。

第二种嵌套:ViewPager+FragmentPagerAdapter(fm)

image

根据上面的日志可以看到,再进入当前页面的时候和第一种一样,在默认没有设置offscreenPageLimit的值时,也会帮我们多预加载一个Fragment,但不同的时候,两个Fragment的生命周期都会直接走到onResume的生命周期,并且它们都多了一个isVisibleToUser布尔值来控制Fragment是否可见。

第三种嵌套:ViewPager2+Fragment

image

根据上面的log可以看到,进入界面时它只会初始化当前的Activity,它的可见与否也是由onResume和onPause进行回调的。它的生命周期就和第一种很像了,只是在没有设置offscreenPageLimit的情况下,它不会去预加载下一个的生命周期。所以我们仍然可以通过onResume来控制懒加载。

制作一个通用的BaseLazyFragment

通过上面的三种嵌套的日志。我们只需要最后确定一件事:就是mUserVisibleHint的默认值


image

如我们所料,至此我们只需要在onResume和setUserVisibleHint方法中都调用一个lazyLoad方法,并在lazyLoad中去判断mUserVisibleHint的值以及自定义的一个loaded的值即可,另外需要注意的一点是setUserVisibleHint的方法调用的时机可能并不在Fragment的生命周期内

This method may be called outside of the fragment lifecycle.
and thus has no ordering guarantees with regard to fragment lifecycle method calls

所以我们还需要判断根布局是否为null,最后的实现代码如下:

abstract class BaseLazyFragment : Fragment() {
    private var cacheView: View? = null
    private var loaded: Boolean = false


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        if (cacheView == null) {
            cacheView = inflater.inflate(layoutId(), container, false)
        }
        return cacheView
    }


    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        super.setUserVisibleHint(isVisibleToUser)
        lazyLoad()
    }

    override fun onResume() {
        super.onResume()
        lazyLoad()

    }

    private fun lazyLoad() {
        if (userVisibleHint && !loaded&& cacheView != null) {
            initView()
            initData()
            loaded = true
        }
    }

    abstract fun layoutId(): Int

    abstract fun initData()

    abstract fun initView()
}

最后我们一起看一下这三嵌套模式的一个懒加载实现的效果:

1588834300301

这个代码是在google的ViewPager2的代码基础上新增了三个页面进行日志打印,
代码地址:https://github.com/luorenyu/ExplorLazyFragment

欢迎关注黑客五六七

你可能感兴趣的:(「Do.034」探索兼容的Fragment懒加载模式)