dependencies {
val fragment_version = "1.3.4"
implementation ( "androidx.fragment:fragment:$fragment_version" )
implementation ( "androidx.fragment:fragment-ktx:$fragment_version" )
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) {
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 ,就可实现懒加载了,先看看效果。
private fun initView ( ) {
viewPager = root. findViewById ( R . id. viewpager)
testViewPagerAdapter = TestFragmentAdapter ( childFragmentManager , FragmentPagerAdapter . BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, list)
viewPager. adapter = testViewPagerAdapter
viewPager. offscreenPageLimit = 1
}
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 方法。
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 ) ;
if ( mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
fragment. setUserVisibleHint ( false ) ;
}
mFragments. set ( position, fragment) ;
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 。
void populate ( int newCurrentItem) {
. . .
mAdapter. destroyItem ( this , pos, ii. object) ;
}
@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)。
void populate ( int newCurrentItem) {
. . .
mAdapter. setPrimaryItem ( this , mCurItem, curItem. object) ;
}
@Override
@SuppressWarnings ( { "ReferenceEquality" , "deprecation" } )
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) {
if ( mCurTransaction == null ) {
mCurTransaction = mFragmentManager. beginTransaction ( ) ;
}
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 ( ) ;
}
mCurTransaction. setMaxLifecycle ( fragment, Lifecycle. State . RESUMED) ;
} else {
fragment. setUserVisibleHint ( true ) ;
}
mCurrentPrimaryItem = fragment;
}
}
2.6 finishUpdate 提交事务
最后提交事务就会执行Fragment 的各个生命周期了。
void populate ( int newCurrentItem) {
. . .
mAdapter. finishUpdate ( this ) ;
}
@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生命周期执行
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) {
mManager. moveToState ( mManager. mCurState, true ) ;
}
}
moveToState,这里面的 FragmentStateManager 才会是真正控制Fragment 的方法执行哪一步。 根据MaxLifecycle , 得到maxState ,返回后限制Fragment生命周期。
FragmentManger
void moveToState ( @NonNull Fragment f, int newState) {
newState = Math . min ( newState, fragmentStateManager. computeExpectedState ( ) ) ;
. . .
}
int computeExpectedState ( ) {
if ( mFragment. mFragmentManager == null ) {
return mFragment. mState;
}
int maxState = mFragmentManagerState;
switch ( mFragment. mMaxState) {
case RESUMED:
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如何更简单的实现懒加载?