查看原文
其他

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

fundroid AndroidPub 2022-05-15

面试官: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年的今天还这么回答可能就不过关了。

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 的实现原理?

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列表,设置 Fragment 的 mMaxState 表明其被允许的最大生命周期。

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;
            //...
        }
}

mMaxState 的设置是通过在 FragmentManager 中的同名方法 setMaxLifeCycle 完成的

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

FragmentStateManager

调用 setMaxLifecycle 之后, FragmentManager 会通过 FragmentStateManager 对 Fragment 生命周期做出限制。

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

FragmentStateManager


看一下在 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
    //...
    
    // 判断setMaxLifecycle设置的mMaxState, 以限制Fragment的生命周期
    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;
}

整体流程图如下

setMaxLifecycle流程图

最后

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

(Fragment 1.3.0-rc01已经支持设置最大生命周期为INITIALIZED)



推荐阅读:


Kotlin 1.5 来了,Inline classes 了解一下?



当Jetpack Compose 遇到 Navigation


Jetpack Compose for Desktop 初体验


小心RxJava操作符中暗藏的危机




↓关注公众号↓↓添加微信交流↓



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存