Fragment 官方懒加载源码分析
Posted 进击的包籽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Fragment 官方懒加载源码分析相关的知识,希望对你有一定的参考价值。
文章目录
- 本次分析Fragment 1.3.4版本,不同版本源码会不同!
- Fragment官方文档
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不执行了。
- 再滑到第三个页面,可以看到第一个销毁,第二个页面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生命周期执行
- commit之后具体怎么,执行可以看Fragment 生命周期源码分析。
- 设置了MaxLifecycle ,带着这个行为去看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如何更简单的实现懒加载?
以上是关于Fragment 官方懒加载源码分析的主要内容,如果未能解决你的问题,请参考以下文章
安卓中viewpager+tablayout+fragment懒加载怎么做