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

简介: setUserVisiblity已被废弃,推荐使用 setMaxLifecycle 处理 Fragment 在 ViewPager 中的懒加载

image.png

问: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 中的一些操作。 Fragment 1.3.0-rc01 已经支持设置最大生命周期为 INITIALIZED

目录
相关文章
|
Android开发
两种方法,教你解决 ViewPager 嵌套 ViewPager滑动冲突(一)
两种方法,教你解决 ViewPager 嵌套 ViewPager滑动冲突
|
Android开发 UED 开发者
两种方法,教你解决 ViewPager 嵌套 ViewPager滑动冲突(二)
两种方法,教你解决 ViewPager 嵌套 ViewPager滑动冲突
|
缓存 Android开发
ViewPager的简单使用
本节带来的是Android 3.0后引入的一个UI控件——ViewPager(视图滑动切换工具),实在想不到如何来称呼这个控件,他的大概功能:通过手势滑动可以完成View的切换,一般是用来做APP的引导页或者实现图片轮播,因为是3.0后引入的,如果想在低版本下使用,就需要引入v4兼容包,我们也可以看到,ViewPager在:android.support.v4.view.ViewPager目录下。下面我们就来学习一下这个控件的基本用法。
186 0
|
XML Java 数据格式
ViewPager和Fragment使用(附源码)
1 初级版 ViewPager通过滑动来切换Fragment,无底部导航栏。 用法:数据List<Fragment>+适配器FragmentPagerAdapter+ViewPager 通过FragmentPagerAdapter连接两者的桥梁,里面需要重写两个方法getItem,getCount。 第一个方法是获取一个Fragment,从数据源获取。 第二个方法是获取数据源的大小,也就是有几页。
169 0
|
Android开发
ViewPager源码分析(2):滑动及冲突处理
我的简书同步发布:ViewPager源码分析(2):滑动及冲突处理 转载请注明出处:【huachao1001的专栏:http://blog.csdn.net/huachao1001】 上一篇介绍了ViewPager的onMeasure和onLayout两个方法,这是自定义View最基本的两个函数。但是我们的ViewPager有个需求就是滑动,接下来我们一起去学习ViewPager在滑动方面做了哪些工作,以及ViewPager如何处理与子View之间的滑动冲突。由于ViewPager的子View有Decor View还有普通的子View,而本篇文章讲的主要是普通子View,因此,不再去刻意区
ViewPager源码分析(2):滑动及冲突处理
|
Java
viewpage 添加fragment 报错 viewpage demo LayoutInflater 自定义控件轮播图demo
viewpage 添加fragment 报错 viewpage demo LayoutInflater 自定义控件轮播图demo
113 0
viewpage 添加fragment 报错 viewpage demo LayoutInflater 自定义控件轮播图demo
|
缓存
ViewPager源码分析(3):与PagerAdapter 交互
ViewPager源码分析(3):与PagerAdapter 交互
ViewPager源码分析(3):与PagerAdapter 交互
|
缓存
ViewPager懒加载的实现,理解setUserVisibleHint,而不只是会用就好
Viewpager默认会缓存临近操作的两个页面,也就是至少会缓存一个页面。
203 0
ViewPager懒加载的实现,理解setUserVisibleHint,而不只是会用就好
|
缓存
ViewPager的缓存机制和懒加载实现
ViewPager的缓存机制和懒加载实现
|
Android开发
listview加载性能优化之view的复用
listview加载性能优化之view的复用