在进入正文之前要强调一下,本文的分析基于androidx 1.1.0版本,文中提到的setMaxLifecycle()方法是1.1.0-alpha07版本才引入的。
最近把Android Studio更新到了3.5版本,新建项目时发现竟然已经强制使用androidx包了。
于是想着把以前项目中的一些公共类,像BaseActivity、BaseFragment等等都迁移到androidx方便今后的开发,要做的也很简单,就是重新导包,将原来的“support系列”替换为“androidx系列”。简单的迁移完成后我发现此前实现懒加载Fragment中重写的setUserVisibleHint()方法已经过时了。关于setUserVisibleHint()方法我此前写过一篇Fragment懒加载的探究和实现进行了详细介绍,网上也有很多相关的资料,我这里就不说了。
setUserVisibleHint()方法已过时
我们点进setUserVisibleHint()方法的源码来看一下,对于过时的方法源码中都会注明使用哪个方法来替代:
/**
* Set a hint to the system about whether this fragment's UI is currently visible
* to the user. This hint defaults to true and is persistent across fragment instance
* state save and restore.
*
* <p>An app may set this to false to indicate that the fragment's UI is
* scrolled out of visibility or is otherwise not directly visible to the user.
* This may be used by the system to prioritize operations such as fragment lifecycle updates
* or loader ordering behavior.</p>
*
* <p><strong>Note:</strong> This method may be called outside of the fragment lifecycle.
* and thus has no ordering guarantees with regard to fragment lifecycle method calls.</p>
*
* @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
* false if it is not.
*
* @deprecated Use {@link FragmentTransaction#setMaxLifecycle(Fragment, Lifecycle.State)}
* instead.
*/
@Deprecated
public void setUserVisibleHint(boolean isVisibleToUser) {
if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
&& mFragmentManager != null && isAdded() && mIsCreated) {
mFragmentManager.performPendingDeferredStart(this);
}
mUserVisibleHint = isVisibleToUser;
mDeferStart = mState < STARTED && !isVisibleToUser;
if (mSavedFragmentState != null) {
// Ensure that if the user visible hint is set before the Fragment has
// restored its state that we don't lose the new value
mSavedUserVisibleHint = isVisibleToUser;
}
}
可以看到注释中写明了使用FragmentTransaction的setMaxLifecycle()方法来替代setUserVisibleHint()方法,这个setMaxLifecycle()方法是什么呢,下面我们就来具体看一下。
1、setMaxLifecycle()方法
/**
* Set a ceiling for the state of an active fragment in this FragmentManager. If fragment is
* already above the received state, it will be forced down to the correct state.
*
* <p>The fragment provided must currently be added to the FragmentManager to have it's
* Lifecycle state capped, or previously added as part of this transaction. The
* {@link Lifecycle.State} passed in must at least be {@link Lifecycle.State#CREATED}, otherwise
* an {@link IllegalArgumentException} will be thrown.</p>
*
* @param fragment the fragment to have it's state capped.
* @param state the ceiling state for the fragment.
* @return the same FragmentTransaction instance
*/
@NonNull
public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
@NonNull Lifecycle.State state) {
addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
return this;
}
setMaxLifecycle() 方法定义在FragmentTransaction类中,它的内部逻辑很简单,其实我们经常使用的add()、remove()、show()、hide()等方法也是类似的逻辑,将操作封装为一个Op对象,最后调用commit()方法时再根据Op对象执行对应的操作。
注释中提到setMaxLifecycle()方法的作用是为Fragment的状态设置上限,如果当前Fragment的状态已经超过了设置的上限,就会强制被降到相应状态。在弄清楚上面这段文字的意义之前我首先要介绍两个相关概念:Fragment的状态和Lifecycle的状态。
1.1、Fragment的状态
在Fragment类中定义了5个int常量,表示Fragment的状态值:
static final int INITIALIZING = 0; // Not yet created.
static final int CREATED = 1; // Created.
static final int ACTIVITY_CREATED = 2; // Fully created, not started.
static final int STARTED = 3; // Created and started, not resumed.
static final int RESUMED = 4; // Created started and resumed.
1.2、Lifecycle的状态
Lifecycle是Android Jetpack中的架构组件之一,用于帮助我们方便地管理Activity和Fragment的生命周期,关于Lifecycle的详细介绍和使用网上有很多文章,我这里就不说了,如果此前没有接触过可以自行了解一下哈。
在Lifecycle定义了一个枚举类State:
public enum State {
DESTROYED,
INITIALIZED,
CREATED,
STARTED,
RESUMED;
public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
}
可以看出Lifecycle中同样定义了5个状态,不过这里的状态和Fragment中定义的状态还是有一些区别的。
回到setMaxLifecycle()方法,需要传入的参数有两个:fragment和state。fragment不用多说,就是要设置的目标Fragment,不过需要注意的是此时Fragment必须已经被添加到了FragmentManager中,也就是调用了add()方法,否则会抛出异常。state就是Lifecycle中定义的枚举类型,同样需要注意传入的state应该至少为CREATED,换句话说就是只能传入CREATED、STARTED和RESUMED,否则同样会抛出异常。
看到这里可能会产生一个疑问,传入的state是Lifecycle中定义的,怎样转换为对Fragment状态的限制呢,上面也看到了,这两个状态还是有区别的。而且状态这个概念平时可能接触得不多,下面就以我们最熟悉的生命周期方法来说明这个状态的限制,先上一张图总结一下结论:
图中展示了Fragment状态间切换会执行的生命周期方法以及Lifecycle.State对应的Fragment状态,由于setMaxLifecycle()方法要求传入的state至少为CREATED,因此我们只需研究CREATED、STARTED和RESUMED这三个状态,结合上图解释一下setMaxLifecycle()方法的作用。
-
参数传入Lifecycle.State.CREATED
Lifecycle.State.CREATED对应Fragment的CREATED状态,如果当前Fragment状态低于CREATED,也就是INITIALIZING,那么Fragment的状态会变为CREATED,依次执行onAttach()、onCreate()方法;如果当前Fragment状态高于CREATED,那么Fragment的状态会被强制降为CREATED,以当前Fragment状态为RESUMED为例,接下来会依次执行onPause()、onStop()和onDestoryView()方法。如果当前Fragment的状态恰好为CREATED,那么就什么都不做。 -
参数传入Lifecycle.State.STARTED
Lifecycle.State.STARTED对应Fragment的STARTED状态,如果当前Fragment状态低于STARTED,那么Fragment的状态会变为STARTED,以当前Fragment状态为CREATED为例,接下来会依次执行onCreateView()、onActivityCreate()和onStart()方法;如果当前Fragment状态高于STARTED,也就是RESUMED,那么Fragment的状态会被强制降为STARTED,接下来会执行onPause()方法。如果当前Fragment的状态恰好为STARTED,那么就什么都不做。 -
参数传入Lifecycle.State.RESUMED
Lifecycle.State.RESUMED对应Fragment的RESUMED状态,如果当前Fragment状态低于RESUMED,那么Fragment的状态会变为RESUMED,以当前Fragment状态为STARTED为例,接下来会执行onResume()方法。如果当前Fragment的状态恰好为RESUMED,那么就什么都不做。
光介绍结论可能不是很直观,下面就以几个常见的例子来验证一下上图的结论。 -
传入Lifecycle.State.CREATED
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
homefragment = new HomeFragment();
ft.add(R.id.fl_container, homefragment);
ft.setMaxLifecycle(homefragment, Lifecycle.State.CREATED);
ft.commit();
Fragment生命周期方法执行如下:
可以看出,在调用setMaxLifecycle()方法传入了Lifecycle.State.CREATED后,会限制Fragment的状态为CREATED,因此只会执行到onCreate()方法,而不会执行后面的onCreateView()-onResume()方法。
再来看“降级”的例子,调用setMaxLifecycle()之前已经调用过add()方法将Fragment添加到了FragmentManager中,此时Fragment的状态为RESUMED。
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setMaxLifecycle(homefragment, Lifecycle.State.CREATED);
ft.commit();
Fragment生命周期方法执行如下:
可以看出此时Fragment的状态会降为CREATED,依次执行onPause()、onStop()和onDestoryView()方法。
- 传入Lifecycle.State.STARTED
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
homefragment = new HomeFragment();
ft.add(R.id.fl_container, homefragment);
ft.setMaxLifecycle(homefragment, Lifecycle.State.STARTED);
ft.commit();
Fragment生命周期方法执行如下:
可以看出,在调用setMaxLifecycle()方法传入了Lifecycle.State.STARTED后,会限制Fragment的状态为STARTED,因此只会执行到onStart()方法,而不会执行后面的onResume()方法。
同样看一下“降级”的例子,调用setMaxLifecycle()之前已经调用过add()方法将Fragment添加到了FragmentManager中,此时Fragment的状态为RESUMED。
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setMaxLifecycle(homefragment, Lifecycle.State.STARTED);
ft.commit();
Fragment生命周期方法执行如下:
可以看出此时Fragment的状态会降为STARTED,执行onPause()方法。
- 传入Lifecycle.State.RESUMED
在上一个例子的基础上执行下面的代码:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setMaxLifecycle(homefragment, Lifecycle.State.RESUMED);
ft.commit();
Fragment生命周期方法执行如下:
可以看出,在调用setMaxLifecycle()方法传入了Lifecycle.State.RESUMED后,会限制Fragment的状态为RESUMED,由于当前Fragment的状态为STARTED,接下来会执行onResume()方法。
2、懒加载的新方案
此前Fragment的懒加载都是通过setUserVisibleHint()方法来实现,既然官方提出了setMaxLifecycle()方法来替代setUserVisibleHint()方法,那么肯定也提供了懒加载的新方案,这次的切入点在FragmentPagerAdapter中,我们会发现之前继承自FragmentPagerAdapter的构造方法同样过时了。
同样地,我们点进这个构造方法看一下:
/**
* Constructor for {@link FragmentPagerAdapter} that sets the fragment manager for the adapter.
* This is the equivalent of calling {@link #FragmentPagerAdapter(FragmentManager, int)} and
* passing in {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.
*
* <p>Fragments will have {@link Fragment#setUserVisibleHint(boolean)} called whenever the
* current Fragment changes.</p>
*
* @param fm fragment manager that will interact with this adapter
* @deprecated use {@link #FragmentPagerAdapter(FragmentManager, int)} with
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
*/
@Deprecated
public FragmentPagerAdapter(@NonNull FragmentManager fm) {
this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}
可以看到方法内部又调用了两个参数的构造方法,注释中也写明了要使用两个参数的构造方法,我们来看一下这个新的构造方法:
public FragmentPagerAdapter(@NonNull FragmentManager fm,
@Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
多了一个int类型的参数behavior,可选的值有以下两个:
@Deprecated
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
可以看出一个参数的构造方法默认传入BEHAVIOR_SET_USER_VISIBLE_HINT,将其赋值给mBehavior,那么这个mBehavior在什么地方用到了呢。在FragmentPagerAdapter.java文件中全局搜索一下,发现只有两个地方用到了mBehavior:instantiateItem()方法和setPrimaryItem()方法。instantiateItem()方法我们很熟悉,是初始化ViewPager中每个Item的方法,setPrimaryItem()方法我此前没有接触过,简单地看了一下源码发现它的作用是设置ViewPager将要显示的Item,在ViewPager切换时会调用该方法,我们来看一下FragmentPagerAdapter中的setPrimaryItem()方法:
@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) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
// 如果mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,则将上一个Fragment的状态设置为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();
}
// 如果mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,则将当前Fragment的状态设置为RESUMED
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
} else {
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
方法的逻辑还是很简单的,如果mBehavior的值为BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,那么就调用setMaxLifecycle()方法将上一个Fragment的状态设置为STARTED,将当前要显示的Fragment的状态设置为RESUMED;反之如果mBehavior的值为BEHAVIOR_SET_USER_VISIBLE_HINT,那么依然使用setUserVisibleHint()方法设置Fragment的可见性,相应地可以根据getUserVisibleHint()方法获取到Fragment是否可见,从而实现懒加载,具体做法我就不说了。
下面我们来具体看一下mBehavior为BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT的情况,首先在ViewPager的Adapter中调用两个参数的构造方法:
public MyPagerAdapter(@NonNull FragmentManager fm, int behavior, List<Fragment> fragments) {
super(fm, behavior);
this.fragments = fragments;
}
然后在构造Adapter对象的时候behavior参数传入FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT:
mAdapter = new MyPagerAdapter(getSupportFragmentManager(),FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,mFragments);
其他的代码就不展示了,这里我给ViewPager添加了三个Fragment,并调用了setOffscreenPageLimit(3)来预加载所有的Fragment,下面我们就来看一下几种情况下三个Fragment生命周期方法的执行情况。
2.1、默认情况下显示第一个Fragment
可以看出第一个Fragment此时可见,执行到了onResume()方法,即RESUMED状态,第二个和第三个Fragment此时不可见,均执行到onStart()方法,即STARTED状态。
2.2、从第一个Fragment切换到第二个Fragment
可以看出第一个Fragment此时变为不可见,由于调用setMaxLifecycle()方法传入了Lifecycle.State.STARTED,因此会从RESUMED状态变为STARTED状态,执行onPause()方法;第二个Fragment由不可见变为可见,由于调用setMaxLifecycle()方法传入了Lifecycle.State.RESUMED,因此会从STARTED状态变为RESUMED状态,执行onResume()方法。
2.3、从第二个Fragment切换到第三个Fragment
和上一个情况相同,第二个Fragment变为不可见,执行onPause()方法,状态变为STARTED;第三个Fragment变为可见,执行onResume()方法,状态变为RESUMED。
之后在几个Fragment之间切换的情况也是一样的,变为不可见的Fragment执行onPause()方法,变为可见的Fragment执行onResume()方法。
从以上几种情况中我们不难发现,在Fragment变为可见时都会执行onResume()方法,我们可以利用这一点来实现懒加载,基本思路有两点:
- 将Fragment加载数据的逻辑放到onResume()方法中,这样就保证了Fragment可见时才会加载数据。
- 声明一个变量标记是否是首次执行onResume()方法,因为每次Fragment由不可见变为可见都会执行onResume()方法,需要防止数据的重复加载。此外,如果我们使用的是FragmentPagerAdapter,切换导致Fragment被销毁时是不会执行onDestory()和onDetach()方法的,只会执行到onDestroyView()方法,因此在onDestroyView()方法中我们还需要将这个变量重置,否则当Fragment再次可见时就不会重新加载数据了。
按照以上两点我们就可以封装出新的懒加载Fragment了,完整代码如下:
public abstract class NewLazyFragment extends Fragment {
private Context mContext;
private boolean isFirstLoad = true; // 是否第一次加载
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getActivity();
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = LayoutInflater.from(mContext).inflate(getContentViewId(), null);
initView(view);
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
isFirstLoad = true;
}
@Override
public void onResume() {
super.onResume();
if (isFirstLoad) {
// 将数据加载逻辑放到onResume()方法中
initData();
initEvent();
isFirstLoad = false;
}
}
/**
* 设置布局资源id
*
* @return
*/
protected abstract int getContentViewId();
/**
* 初始化视图
*
* @param view
*/
protected void initView(View view) {
}
/**
* 初始化数据
*/
protected void initData() {
}
/**
* 初始化事件
*/
protected void initEvent() {
}
}
3、总结
本文主要介绍了androidx包中setMaxLifecycle()方法的使用和作用,它用于替代setUserVisibleHint()方法,可以设置Fragment的状态,由此引出了实现Fragment懒加载的新方案,在构造FragmentPagerAdapter时传入BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,将加载数据的逻辑放到Fragment的onResume()方法中即可。
最后还是要感叹一下,此前我一直忌惮androidx迁移的坑,这次由于Android Studio的限制我才第一次真正使用androidx,虽然在迁移的过程中难免会有一些问题,但这毕竟是早晚要解决的,而且androidx不仅仅是统一了support包,还引入了一些新的内容,为开发者提供了很多便利,今后还是有必要好好学习一下的。
本文的相关代码我已经上传到了github,可以参考一下,此外如果我有什么分析得不对的地方欢迎大家提出指正。
github地址:https://github.com/StephenZKCurry/ViewPagerFragment