添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

在进入正文之前要强调一下,本文的分析基于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() {

本文主要介绍了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
				
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-54ROfsN3-1647696809581)(//upload-images.jianshu.io/upload_images/2824145-1567d443a5150d6e.png?imageMogr2/auto-orient/strip|imageView2/2/w/954/format/webp)] 观察上图我们可以发现,同级的Fragment_1、Fragment_2、Fragment_3 都调用了 onAttach.
List<LazyFragment> fragmentList=new ArrayList<>(); fragmentList.add(new MainFragment1(0)); fragmentList.add(new MainFragment1(1)); fragmentList.add(new MainFragment1(2)); fragmentList.add(new MainFragment1(3)); fragmentList.add(ne 所谓懒加载,就是当fragment完全可见的时候我们再去加载数据,我们在做应用开发的时候,一个Activity里面可能会以viewpager(或其他容器)与多个Fragment来组合使用,而如果每个fragment都需要去加载数据,或从本地加载,或从网络加载,那么在这个activity刚创建的时候就变成需要初始化大量资源。这样的结果,我们当然不会满意。那么,能不能做到当切换到这个fragment的时候,它才去初始化呢? 答案就在Fragment里的setUserVisibleHint这个方法里。 请看关于Fragment里这个方法的API文档: Set a hint to the sy 关于Fragment,Google提出的一种碎片化方案,也是目前Android开发者使用最频繁的控件之一 代码下载(还没有上传) 现在Fragment最常用的就是ViewPager + Fragment的用法,所以这次也是针对这种方式来进行懒加载的方式 目前有2种方案 一个是通过FragmentsetUserVisibleHint的继承方法,来进行判断,这个目前在Androidx已经弃用了,但是Androidx之前的开发者仍然可以使用;弊端就是,这个继承方法是在Fragment.o 然后在JNI的另外一个方法,通过调用jni中的 获取java 类名的函数 实现类名的匹配 static void android_hardware_Camera_setPreviewSurface(JNIEnv *env, jobject thiz, jobject jSurface) const char* cls = "xxx.xxx"; if(strcmp(cls , jniCallJava()) == 0){
面试题-100盏灯问题 很清晰,膜大佬 Java-int和Integer的区别 Stone_sage: 引用「等同于Integer a = new Integer(1);」 这个注解与后文的总结,有些许矛盾,会让新手产生误解,