乐趣区

Do034探索兼容的Fragment懒加载模式

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

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

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

为了能够更好的观察几种嵌套方式在滑动过程中的引用,除了上面的生命周期之外,我还在日志中打印了 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)的生命周期


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

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


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

第三种嵌套:ViewPager2+Fragment


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

制作一个通用的 BaseLazyFragment

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

如我们所料,至此我们只需要在 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()}

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

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

退出移动版