面试题:ViewPager中的Framgent如何实现懒加载?

Posted fundroid_方卓

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试题:ViewPager中的Framgent如何实现懒加载?相关的知识,希望对你有一定的参考价值。

在这里插入图片描述

问:ViewPager中的Fragment如何实现懒加载?


当被问到上述问题时,很多人可能首先会想到借助setUserVisiblity实现

如下,当Fragment可见时调用 onVisible 从而实现异步加载

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (getUserVisibleHint()) {
        isVisible = true;
        onVisible();
    } else {
        isVisible = false;
        onInVisible();
    }
}

放在两年前,这个答案是OK的,但是2021年的今天还这么回答可能就不过关了。

image.png
https://android-review.googlesource.com/c/platform/frameworks/support/+/945776

AndroidX 自 1.1.0-alpha07 起, 为 FragmentTransaction 增加了新的方法 setMaxLifeCycle, 官方建议开发者以此取代setUserVisibleHint,这将带来如下好处:

  1. 基于 Lifecycle 的懒加载更加科学,可以配合 Livedata 等组件在MVVM架构中使用
  2. setMaxLifeCycle 无需额外定义 Fragment 基类,使用起来更加无侵

使用 setMaxLifecycle 进行懒加载


FragmentPagerAdapter 的构造方法新增了一个 behavior 参数,
当被设置为FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT时,会通过setMaxLifecycle 来限制 Fragment 的生命周期:只有当 Fragment 显示在屏幕中时才执行onResume()

这样就可以把加载数据等处理放在 onResume() 中从而实现懒加载了。

代码如下:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
        setContentView(R.layout.activity_main)
        val viewPager: ViewPager = findViewById(R.id.viewpager)
        val fragmentList: MutableList<Fragment> = ArrayList()
        fragmentList.add(Fragment1())
        fragmentList.add(Fragment2())
        fragmentList.add(Fragment3())
        // 为MyPagerAdapter适配器设置FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 参数
        val myPagerAdapter: MyPagerAdapter = MyPagerAdapter(
            getSupportFragmentManager(),
            FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, fragmentList
        )
        viewPager.setAdapter(myPagerAdapter)
        // 设置预加载为3页,来测试懒加载是否成功
        viewPager.offscreenPageLimit = 3
    }


    class MyPagerAdapter(
        fm: FragmentManager,
        behavior: Int,
        val fragmentList: List<Fragment>
    ) :
        FragmentPagerAdapter(fm, behavior) {

        override fun getCount() = fragmentList.size
        override fun getItem(position: Int) = fragmentList[position]

    }
}

FragmentPagerAdapter 在创建 Fragment后,根据 behavior 调用了setMaxLifecycle。

//FragmentPagerAdapter.java

 public FragmentPagerAdapter(@NonNull FragmentManager fm,
        @Behavior int behavior) {
    mFragmentManager = fm;
    mBehavior = behavior;
}

@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
    ...
    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        // mBehaviour为1的时候走新逻辑
        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            // 初始化item时将其生命周期限制为STARTED
            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) {
                ...
                // 滑走的会变成非主item, 设置其Lifecycle为STARTED
                mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
            } else {
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
        }
        fragment.setMenuVisibility(true);
        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            ...
            // 设置新滑到的主item的Lifecycle为RESUMED
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
        } else {
            fragment.setUserVisibleHint(true);
        }
        mCurrentPrimaryItem = fragment;
    }
}

不借助 behavior,在自定义Adapter中构建 Framgent时直接调用setMaxLifecycle 也是等价的。


setMaxLifecycle 实现原理


setMaxLifecycle 使用方法很简单,接下来通过梳理源码了解一下实现原理(基于1.3.0-rc01),即使面试官追问其原理你也能沉着应对。

OP_SET_MAX_LIFECYCLE

我们知道 FramgentTransition 对 Fragment 的所有操作都将转换为一个Op,针对setMaxLifecycle也同样增加了一个新的Op – OP_SET_MAX_LIFECYCLE, 专门用来设置生命周期的上限。

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

当 FramgentTransition 对 Frament 添加了 OP_SET_MAX_LIFECYCLE 后,在实现类 BackStackRecord 中, FragmentManager 会遍历 Transaction 的 Op 列表

void executeOps() {
    final int numOps = mOps.size();
    for (int opNum = 0; opNum < numOps; opNum++) {
        final Op op = mOps.get(opNum);
        final Fragment f = op.mFragment;
        //...
        switch (op.mCmd) {
            //...
            // 新引入的这个Op类型, 在这里会给这个Fragment设置允许的生命周期上限
            case OP_SET_MAX_LIFECYCLE:
                mManager.setMaxLifecycle(f, op.mCurrentMaxState);
                break;
            //...
        }
}

当遇到 OP_SET_MAX_LIFECYCLE 时,通过调用 FragmentManager 的 setMaxLifeCycle 方法设置 fragment 的 mMaxState,以标记其生命周期上限

void setMaxLifecycle(@NonNull Fragment f, @NonNull Lifecycle.State state) {
    //...
    f.mMaxState = state;
}

FragmentStateManager

FragmentManager 通过 FragmentStateManager 推进 Fragment 的生命周期。 推进过程中根据 mMaxState 对生命周期

值得一提的是,FragmentStateManager 是 1.3.0-alpha08 之后新增的类,将原来和 State 相关的逻辑从FragmentManager 抽离了出来, 降低了与 Fragment 的耦合, 职责更加单一。

image.png

看一下在 FragmentStateManager 中具体是如何推进 Fragment 生命周期的:

void moveToExpectedState() {
    try {
        ...
        // 循环计算声明周期是否可以推进
        while ((newState = computeExpectedState()) != mFragment.mState) {
            if (newState > mFragment.mState) {
                // 生命周期向前推进
                int nextStep = mFragment.mState + 1;
                //...
                switch (nextStep) {
                    //...
                    case Fragment.ACTIVITY_CREATED:
                        //...
                    case Fragment.STARTED:
                        start();
                        break;
                    //...
                    case Fragment.RESUMED:
                        resume();
                        break;
                }
            } else {
                // 如果应有的生命周期小于当前, 后退
                int nextStep = mFragment.mState - 1;
                //...
                switch (nextStep) {
                   // 与上面的switch类似
                   //...
                }
            }
        }
        ...
    }
    ...
}
int computeExpectedState() {
    // 其他计算expected state的逻辑, 算出maxState
    //...
    
    // mMaxState 对生命周期做出限制
    switch (mFragment.mMaxState) {
        case RESUMED:
            break;
        case STARTED:
            maxState = Math.min(maxState, Fragment.STARTED);
            break;
        case CREATED:
            maxState = Math.min(maxState, Fragment.CREATED);
            break;
        default:
            maxState = Math.min(maxState, Fragment.INITIALIZING);
    }
    
    // 其他计算expected state的逻辑, 算出 maxState
    // ...
    return maxState;
}

整体流程图如下

image.png


最后


除了使用默认的 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,我们甚至可以在自定义 Adapter 的instantiateItem 中为将 Fragment的 MaxLifecycle 设置为 CREATED, 这样可以让 Fragment 只走到onCreate 从而延迟更多操作,比如在 onCreateView 中的 inflate 以及 onViewCreated 中的一些操作。

以上是关于面试题:ViewPager中的Framgent如何实现懒加载?的主要内容,如果未能解决你的问题,请参考以下文章

ViewPager+Fragment实现页面的切换

实现ViewPager懒加载的三种方法

Android ListView 相关问题(面试常用)

android面试题

干货2021最新《Android高级开发面试题2.0》中高级程序员必备“面试宝典”

全集Java程序员面试题教你如何应对面试中的手足无措