添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《 阿里云开发者社区用户服务协议 》和 《 阿里云开发者社区知识产权保护指引 》。如果您发现本社区中有涉嫌抄袭的内容,填写 侵权投诉表单 进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

问: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,这将带来如下好处:

  • 基于 Lifecycle 的懒加载更加科学,可以配合 Livedata 等组件在MVVM架构中使用
  • 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 的耦合, 职责更加单一。

    看一下在 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;
    

    整体流程图如下

    除了使用默认的 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,我们甚至可以在自定义 Adapter 的instantiateItem 中为将 Fragment的 MaxLifecycle 设置为 CREATED, 这样可以让 Fragment 只走到onCreate 从而延迟更多操作,比如在 onCreateView 中的 inflate 以及 onViewCreated 中的一些操作。 Fragment 1.3.0-rc01 已经支持设置最大生命周期为 INITIALIZED