Fragment 官方懒加载源码分析

523 阅读4分钟
dependencies {
    val fragment_version = "1.3.4"

    // Java language implementation
    implementation("androidx.fragment:fragment:$fragment_version")
    // Kotlin
    implementation("androidx.fragment:fragment-ktx:$fragment_version")
    // Testing Fragments in Isolation
    debugImplementation("androidx.fragment:fragment-testing:$fragment_version")
}

1.Fragment懒加载

1.1 什么是预加载

  • ViewPager搭配Fragment使用时,Fragment 在没有显示的时候,其实就已经初始化操作了,这是为了用户更好的体验,在滑动ViewPager时,浏览当前页面,当左右其实已经初始化好了。
  • ViewPager默认是预加载一页,就是左右各一页,可以使用 setOffscreenPageLimit 设置,但最少一页,设置越多,预加载就越多,比如设置2,左右各预加载2页面。
	//默认一页
    private static final int DEFAULT_OFFSCREEN_PAGES = 1;
    
    public void setOffscreenPageLimit(int limit) {
    	//小于1,会设置为1
        if (limit < DEFAULT_OFFSCREEN_PAGES) {
            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
                    + DEFAULT_OFFSCREEN_PAGES);
            limit = DEFAULT_OFFSCREEN_PAGES;
        }
        if (limit != mOffscreenPageLimit) {
            mOffscreenPageLimit = limit;
            populate();
        }
    }
  • 我们通过打印日志就能看出除了看到的第一个页面,生命周期从 onAttach 执行到 onResume ,看不到的第二个页面也会 onResume ;那我们平时除了初始化控件,还有网络请求,都被加载完成了,这就是默认的预加载。
  • 这里注意一点,第一行 setUserVisibleHint 前面没有参数,那是因为 setUserVisibleHint比onAttach更早执行 ,所以拿不到bundle传递的数据。

第一个页面

  • 如果滑到第二个页面,那第三个页面也会预加载。

第二个页面

  • 滑到第三个页面,第四个页面预加载,而第一个页面销毁。

第三个界面

  • 而这个时候再从三滑到二,二页面只执行 setUserVisibleHint ,没有执行onResume了,到这应该大概了解什么是预加载。

回到第二个界面

  • 可以看到默认情况下,Fragment 的生命周期各个方法只会走一次,再次滑动回来也就是setUserVisibleHint 会回调信息。

1.2 什么是懒加载

  • 预加载看起来是挺好的,但是预加载的越多,可能就会越卡顿,暂用着更多的内存和消耗更多的流量。
  • 对于性能不错的机子,网络好的情况下,体验确实不错,但我们开发时需要考虑一下比较旧,或者是性能比较落后的机子,尽可能给他们也不错的体验。
  • 懒加载就是滑到哪个页面,初始化哪个页面,滑走的页面就可以做一些暂停操作。
  • 在使用 FragmentStatePagerAdapter 时使用两个参数的构造方法,第二个参数传入 FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,就可实现懒加载了,先看看效果。
	/*TestFragment*/
    private fun initView() {
        /* FragmentActivity */
        viewPager = root.findViewById(R.id.viewpager)
        // 传入FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
        // list是数据
        testViewPagerAdapter = TestFragmentAdapter(childFragmentManager ,FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,list)
        viewPager.adapter = testViewPagerAdapter
        viewPager.offscreenPageLimit = 1
    }

	/**
	 * 继承两个参数的FragmentStatePagerAdapter
	 */
	class TestFragmentAdapter(fragmentManager: FragmentManager,  behavior:Int, list: MutableList<String>) :
    	FragmentStatePagerAdapter(fragmentManager, behavior) {
	}
  • 这里可以看到跟没有懒加载的情况对比,只有第一个Fragment 走到 onResume,而第二个Fragment 只到 onStart。

懒加载第一个界面

  • 如果滑到第二个页面,第一个页面走onPause,只有第二个页面走到 onResume ,第三个页面走到onStart,还有就是 setUserVisibleHint不执行了。

滑到第二个 Fragment

  • 再滑到第三个页面,可以看到第一个销毁,第二个页面onPuase,第三个页面onResume , 第四个onStart。

懒加载第三个页面

  • 第三个页面再回到第二页面,创建第一个页面,第二个页面onResume,第三个页面onPause,销毁第四个页面。

回到第二个页面

  • 我们可以看到官方懒加载加了后,各个页面的生命周期onResume,onPause开始走起来了,而不是只走一次,setUserVisibleHint不再回调了。

2. 原理分析

2.1 populate 做了什么

  • 在预加载,设置 setOffscreenPageLimit,我们就看到这个方法了,其实在ViewPager 的 onMeasure 也是执行这个方法,那他做了什么呢。
  • 他其实就是做了初始化工作,启动事务,增加或者删除item,也就是Fragment,提交事务,设置了mBehavior ,一步一步看下去就是 FragmentTransaction.setMaxLifecycle 或者 Fragment.setUserVisibleHint
  • 提交事务,如果想要了解Fragment更具体的生命周期怎么走的,可以看:Fragment 生命周期源码分析

2.2 addNewItem 增加item

  • populate()首先就是增加新的节点,重点看 mAdapter.instantiateItem方法 ,这里 PagerAdapter 本身没做啥,调用的是子类的 instantiateItem 方法。
  	 /*ViewPager*/
     void populate(int newCurrentItem) {
     		...
        if (curItem == null && N > 0) {
            curItem = addNewItem(mCurItem, curIndex);
        }
     }

    ItemInfo addNewItem(int position, int index) {
        ItemInfo ii = new ItemInfo();
        ii.position = position;
        ii.object = mAdapter.instantiateItem(this, position);
        ii.widthFactor = mAdapter.getPageWidth(position);
        if (index < 0 || index >= mItems.size()) {
            mItems.add(ii);
        } else {
            mItems.add(index, ii);
        }
        return ii;
    }

2.2 instantiateItem 方法

  • 我们以FragmentStatePagerAdapter这子类分析一下。
  • 这段代码就是获取 position 对应的Fragment,Fragment 的添加操作是需要事务的,倒数第五行这里就添加,如果有设置懒加载,就 setMaxLifecycle。
    @SuppressWarnings("deprecation")
    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        if (mFragments.size() > position) {
            Fragment f = mFragments.get(position);
            if (f != null) {
                return f;
            }
        }

		//获取事务
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

       ...
       
        fragment.setMenuVisibility(false);
        //如果不是设置懒加载,就执行setUserVisibleHint
        if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
            fragment.setUserVisibleHint(false);
        }

        mFragments.set(position, fragment);
        //事务add操作
        mCurTransaction.add(container.getId(), fragment);

		//懒加载
        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
        }

        return fragment;
    }

2.3 setMaxLifecycle

  • 重点就是这里了,其实都是封装为Op对象,在后面Fragment生命周期限制就会用到。
    @NonNull
    public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
            @NonNull Lifecycle.State state) {
        addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
        return this;
    }

2.4 destroyItem 销毁item

  • 这里也很简单,ViewPager移除Item,就是Fragment 事务移除,调用子类的 destroyItem 。
  	 /*ViewPager*/
     void populate(int newCurrentItem) {
     	...
       	mAdapter.destroyItem(this, pos, ii.object);
     }

	/*FragmentStatePagerAdapter*/
	    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        
        ...
        while (mSavedState.size() <= position) {
            mSavedState.add(null);
        }
        mSavedState.set(position, fragment.isAdded()
                ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
        mFragments.set(position, null);
		
		//事务移除
        mCurTransaction.remove(fragment);
        if (fragment.equals(mCurrentPrimaryItem)) {
            mCurrentPrimaryItem = null;
        }
    }

2.5 setPrimaryItem 设置当前item

  • 这里设置当前显示主item生命周期最大为STARTED,或者是setUserVisibleHint(false),他马上就要被滑走了。
  • 即将成为主item的Fragment,设置为RESUMED,或者是setUserVisibleHint(true)。
  	 /*ViewPager*/
     void populate(int newCurrentItem) {
     	...
       	mAdapter.setPrimaryItem(this, mCurItem, curItem.object);
     }

	/*FragmentStatePagerAdapter*/
	    @Override
    @SuppressWarnings({"ReferenceEquality", "deprecation"})
    public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment)object;
        if (fragment != mCurrentPrimaryItem) {
        	//当前itemFragment,被划走
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
                if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                    if (mCurTransaction == null) {
                        mCurTransaction = mFragmentManager.beginTransaction();
                    }
                    //如果有设置懒加载,设置为STARTED
                    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();
                }
                //如果有设置懒加载,设置为RESUMED
                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
            } else {
                fragment.setUserVisibleHint(true);
            }

            mCurrentPrimaryItem = fragment;
        }
    }

2.6 finishUpdate 提交事务

  • 最后提交事务就会执行Fragment 的各个生命周期了。
  	 /*ViewPager*/
     void populate(int newCurrentItem) {
     	...
       	mAdapter.finishUpdate(this);
     }

	/*FragmentStatePagerAdapter*/
	    @Override
    public void finishUpdate(@NonNull ViewGroup container) {
        if (mCurTransaction != null) {
            if (!mExecutingFinishUpdate) {
                try {
                    mExecutingFinishUpdate = true;
                    //提交事务
                    mCurTransaction.commitNowAllowingStateLoss();
                } finally {
                    mExecutingFinishUpdate = false;
                }
            }
            mCurTransaction = null;
        }
    }

2.7 Fragment生命周期执行

	/*BackStackRecord*/
    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) {               
              .
              .
              .
              case OP_SET_MAX_LIFECYCLE:
                    mManager.setMaxLifecycle(f, op.mCurrentMaxState);
                    break;             
            }           
        }
        if (!mReorderingAllowed && !FragmentManager.USE_STATE_MANAGER) {
            // Added fragments are added at the end to comply with prior behavior.
            mManager.moveToState(mManager.mCurState, true);
        }
    }
  • moveToState,这里面的 FragmentStateManager 才会是真正控制Fragment 的方法执行哪一步。
  • 根据MaxLifecycle , 得到maxState ,返回后限制Fragment生命周期。
	/**/FragmentManger
  	void moveToState(@NonNull Fragment f, int newState) {   		
		//这里跟官方提供的懒加载有关,可以限制fragment执行到哪个阶段
        newState = Math.min(newState, fragmentStateManager.computeExpectedState());
        ...
    }

	/*FragmentStateManager*/
    int computeExpectedState() {
        // If the FragmentManager is null, disallow changing the state at all
        if (mFragment.mFragmentManager == null) {
            return mFragment.mState;
        }
        // Assume the Fragment can go as high as the FragmentManager's state
        int maxState = mFragmentManagerState;

        // Don't allow the Fragment to go above its max lifecycle state
        switch (mFragment.mMaxState) {
            case RESUMED:
                // maxState can't go any higher than RESUMED, so there's nothing to do here
                break;
            case STARTED:
                maxState = Math.min(maxState, Fragment.STARTED);
                break;
            case CREATED:
                maxState = Math.min(maxState, Fragment.CREATED);
                break;
            case INITIALIZED:
                maxState = Math.min(maxState, Fragment.ATTACHED);
                break;
            default:
                maxState = Math.min(maxState, Fragment.INITIALIZING);
        }
        ...
 }

2.8 简易流程图

流程图

3.参考

3.1 参考文章

androidx来袭,Fragment如何更简单的实现懒加载?