+关注继续查看

ViewPager+FragmentPagerAdapter 被标记为过时

Tab 页是绝大多数项目中很常见的样式了,如果 Tab 采用的是 ViewPager+FragmentPagerAdapter 的方式来布局,使用如下:

val mViewPager: ViewPager by id(R.id.m_pager)
val mViewPagerAdapter = ChapterViewPagerAdapter(childFragmentManager)
mViewPager.adapter = mViewPagerAdapter

注:其中 ChapterViewPagerAdapter 对应的 FragmentPagerAdapter 实现。

源码浅析

FragmentPagerAdapter 点进去发现目前的使用方式已经被标记为过时了,源码如下:

private final int mBehavior;
@Deprecated
public FragmentPagerAdapter(@NonNull FragmentManager fm) {
    this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
public FragmentPagerAdapter(@NonNull FragmentManager fm,
        @Behavior int behavior) {
    mFragmentManager = fm;
    mBehavior = behavior;
}

之前使用的一个参数的构造方法已经被标记位 Deprecated 了,系统推荐使用2个参数的构造方法,所以直接来看第2个参数的含义是什么:

@Retention(RetentionPolicy.SOURCE)
@IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
private @interface Behavior { }
@Deprecated
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;

FragmentPagerAdapter 构造方法第2个参数传入的是一个 int 值并且只能传入上面对应的两个值: BEHAVIOR_SET_USER_VISIBLE_HINT、BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT

FragmentPagerAdapter(FragmentManager fm) 默认给我们传入的是 BEHAVIOR_SET_USER_VISIBLE_HINT ,此值也被标记为 Deprecated 了,重点来看 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 的含义,先来看一下被赋值的 mBehavior 都在哪里使用了,按关键字搜一下:

@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    final long itemId = getItemId(position);
    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
        } else {
            fragment.setUserVisibleHint(false);
    return fragment;
@Override
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    Fragment fragment = (Fragment)object;
    if (fragment != mCurrentPrimaryItem) {
        if (mCurrentPrimaryItem != null) {
            mCurrentPrimaryItem.setMenuVisibility(false);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                if (mCurTransaction == null) {
                    mCurTransaction = mFragmentManager.beginTransaction();
                mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
            } else {
                mCurrentPrimaryItem.setUserVisibleHint(false);
        fragment.setMenuVisibility(true);
        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
        } else {
            fragment.setUserVisibleHint(true);
        mCurrentPrimaryItem = fragment;
}

instantiateItem() 是初始化 Fragment 时调用的方法, setPrimaryItem() 是替换显示的 Fragment 时执行,逻辑很简单,以 instantiateItem() 为例,
看下 mBehavior 做了哪些改动:

if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
   mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
 } else {
   fragment.setUserVisibleHint(false);
}

可以看到如果 FragmentPagerAdapter 构造函数中传入的是 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT ,则会执行 FragmentTransaction.setMaxLifecycle() ,否则会执行我们熟悉的 Fragment.setUserVisibleHint() 方法,继续看 setMaxLifecycle() 用来干什么的。

@NonNull
public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
        @NonNull Lifecycle.State state) {
    addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
    return this;
}

第2个参数传入的是 Lifecycle.State ,其实是依赖 Jetpack Lifecycle 生命周期来管理 Fragment 的状态,顾名思义, setMaxLifecycle 是为了设置 Fragment 的最大状态、

  • Fragment状态值 INITIALIZING、CREATED、ACTIVITY_CREATED、STARTED、RESUMED
  • Lifecycle State :生命周期状态,包括 DESTROYED、INITIALIZED、CREATED、STARTED、RESUMED ,两者关系:

Fragment与Lifecycle关系
FragmentTransaction.setMaxLifecycle() 参数传入 Lifecycle.State.STARTED 为例:

  • Lifecycle.State.STARTED 对应 Fragment STARTED 状态,如果当前 Fragment 状态低于 STARTED ,那么 Fragment 的状态会变为 STARTED ,以当前 Fragment 状态为 CREATED 为例,接下来会依次执行 onCreateView()、onActivityCreate()和onStart() 方法;
  • 如果当前 Fragment 状态高于 STARTED ,也就是 RESUMED ,那么 Fragment 的状态会被强制降为 STARTED ,接下来会执行 onPause() 方法。
  • 如果当前 Fragment 的状态恰好为 STARTED ,那么就什么都不做。

结论

看到这里,基本知道如何替换setUserVisibleHint()了,列一下结论:

  • 使用 FragmentPagerAdapter 时直接使用两个参数的构造方法 FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)
  • 本质上是通过 FragmentTransaction的setMaxLifecycle() 方法来替代 setUserVisibleHint() 方法实现 Fragment 的懒加载效果。 instantiateItem() setMaxLifecycle() 设置的 Fragment 状态为 STARTED ,即通过 ViewPager.offscreenPageLimit 设置提前初始化时,临近的 Fragment 最多执行到 onStart() 方法,不会再执行 onResume() 方法了。
  • 如果需要 Fragment 只执行一次对应逻辑且 Fragment 重新加载 View 时需要重置,之前通常会通过 setUserVisibleHint(isVisibleToUser: Boolean) 里通过 isVisibleToUser 以及自定义的 isFirstLoad 去判断,现在可以直接将逻辑写到 onResume 中,形如:
private var isFirstLoad: Boolean = true //是否第一次加载
@Override
public void onResume() {
    super.onResume();
    if (isFirstLoad) {
        //......
        isFirstLoad = false;
//对应Fragment的CREATED状态
@Override
public void onDestroyView() {
    super.onDestroyView();
    isFirstLoad = true;
} 

举个栗子

//LazyViewPagerAdapter.kt
class LazyViewPagerAdapter(fm: FragmentManager) :
    FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
    override fun getCount(): Int = 3
    override fun getItem(position: Int): Fragment {
        return LazyFragment.newInstance(position.toString())
//MainActivity.kt
class MainActivity : AppCompatActivity() {
    private lateinit var mViewPager: ViewPager
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mViewPager = findViewById(R.id.view_pager)
        val adapter = LazyViewPagerAdapter(supportFragmentManager)
        mViewPager.adapter = adapter
        mViewPager.offscreenPageLimit = 1 //默认是1  小于1的时候会被置为1
//LazyFragment.kt
class LazyFragment : Fragment() {
    private var position: String? = null
    private var param2: String? = null
    private lateinit var mTvContent: TextView
    override fun onAttach(context: Context) {
        super.onAttach(context)
        arguments?.let {
            position = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        log("Fragment$position: onAttach()")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        log("Fragment$position: onCreate()")
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_lazy, container, false)
        mTvContent = view.findViewById(R.id.tv_content)
        mTvContent.text = position.toString()
        log("Fragment$position: onCreateView()")
        return view
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        log("Fragment$position: onActivityCreated()")
    override fun onStart() {
        super.onStart()
        log("Fragment$position: onStart()")
    override fun onResume() {
        super.onResume()
        log("Fragment$position: onResume()")
    override fun onPause() {
        super.onPause()
        log("Fragment$position: onPause()")
    override fun onStop() {
        super.onStop()
        log("Fragment$position: onStop()")
    override fun onDestroyView() {
        super.onDestroyView()
        log("Fragment$position: onDestroyView()")
    override fun onDestroy() {
        super.onDestroy()
        log("Fragment$position: onDestroy()")
    override fun onDetach() {
        super.onDetach()
        log("Fragment$position: onDetach()")
    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        super.setUserVisibleHint(isVisibleToUser)
        log("Fragment$position: setUserVisibleHint()->$isVisibleToUser")
    companion object {
        @JvmStatic
        fun newInstance(param1: String = "") =
            LazyFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putString(ARG_PARAM2, "")
                    position = param1
}

不传BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT时

LazyViewPagerAdapter 构造参数中没有传 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT ,日志如下:
执行结果
可以看到, setUserVisibleHint() 会执行,且当前显示 Fragment0 的时候, Fragment1 onResume() 也执行了。

传入BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT时

LazyViewPagerAdapter 构造参数中传入 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 参数,日志如下:

执行结果

可以看到, 如果把BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT参数去掉,setUserVisibleHint()不再执行,且当前显示Fragment0的时候,Fragment1的执行到onStart()之后不再执行 。对应了 instantiateItem() mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED)

其他

测试中的 Fragment androidx.fragment:fragment:1.1.0 版本,且使用的是 ViewPager 。在 Fragment 高版本(如测试使用1.3.6版本)中, FragmentPagerAdapter 整个类已经被标记为过时了,推荐直接使用 ViewPager2 实现懒加载效果。

Android——RecyclerView简单实现及Viewbinding优化
本文是博主对Adapter(适配器)的一些理解,为了加深对Adapter的理解以及记录自己的阶段学习而写,同时也适合初学者阅读,参考本条博客的逻辑进行学习。
Android Fragment懒加载新思路
在Android x以前,我们实现懒加载通常是通过 setUserVisibleHint 方法来控制Fragment是否可见。在Android x之后,Google 提供了新的方案给我们。今天我们就来学习一下。
终于建了一个自己个人小站:https://huangtianyu.gitee.io,以后优先更新小站博客,欢迎进站,O(∩_∩)O~~ 在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势。 基于Android官方AsyncListUtil优化改进RecyclerView分页加载机制(一) Android AsyncListUtil是Android官方提供的专为列表这样的数据更新加载提供的异步加载组件。