关于android:深入Android细说Fragment

9次阅读

共计 13746 个字符,预计需要花费 35 分钟才能阅读完成。

工作这么多年了,始终在做笔记,没有公布什么货色,总感觉网上曾经有了,就懒得写。
最初想了想,还是从最根底的开始,逐渐刨析原理,测验本人的了解水平,心愿各路大神探讨领导,如有谬误欢送斧正,轻喷!
对了,本文波及到的源码都是基于 Android-28,和 AndroidX
回归正题

说到 fragment,那咱们必定从生命周期开始说起,咱们就一个经典的官网流程图来展现一下 fragment 的生命周期流程

简略阐明一下各个生命周期

onAttach(Context context):在 Fragment 和 Activity 关联上的时候调用,且仅调用一次。在该回调中咱们能够将 context 转化为 Activity 保留下来,从而防止前期频繁调用 getAtivity()获取 Activity 的场面,防止了在某些状况下 getAtivity()为空的异样(Activity 和 Fragment 拆散的状况下)。

onCreate:在最后创立 Fragment 的时候会调用,和 Activity 的 onCreate 相似。

onCreateView:在筹备绘制 Fragment 界面时调用,返回值为 Fragment 要绘制布局的根视图,当然也能够返回 null。留神应用 inflater 构建 View 时肯定要将 attachToRoot 指明 false,因为 Fragment 会主动将视图增加到 container 中,attachToRoot 为 true 会反复增加报错。onCreateView 并不是肯定会被调用,当增加的是没有界面的 Fragment 就不会调用,比方调用 FragmentTransaction 的 add(Fragment fragment, String tag)办法。

onActivityCreated:在 Activity 的 onCreated 执行完时会调用。

onStart:Fragment 对用户可见的时候调用,前提是 Activity 曾经 started。

onResume:Fragment 和用户之前可交互时会调用,前提是 Activity 曾经 resumed。

onPause:Fragment 和用户之前不可交互时会调用。

onStop:Fragment 不可见时会调用。

onDestroyView:在移除 Fragment 相干视图层级时调用。

onDestroy:最终分明 Fragment 状态时会调用。

onDetach:Fragment 和 Activity 解除关联时调用。

看着是不是有点眼生?没错!fragment 的生命周期和 activity 生命周期类似,其实 fragment 的生命周期和 activity 生命周期是有关联的,在来一张官网图阐明一下

这里补充一个,在启动阶段,即 onCreateonStartonResume 的时候,是先调用 activity 的生命周期在调用 fragment 的,而暂停敞开阶段,即 onpauseonstopondestory 的时候,是先调 fragment 生命周期在调用 activity 的。(集体了解是,activity 的生命周期包裹了整个 fragment 生命周期,所以创立的时候,必定是先 activity,而回收的时候,如果 fragment 晚于 activity,那不就内存透露了?)

下面曾经简略阐明了几个生命周期,咱们抛出几个问题一一解答
1.fragment 各个生命周期做了什么事?
2.fragment 是怎么进行销毁重建的?
3.activity 和 fragment 怎么进行交互?
4.fragment 怎么实现懒加载?

咱们就围绕这几个问题来理解一下 fragment 吧。(还有问题前面在编辑增加)

1.fragment 各个生命周期做了什么事?

这个问题白问了,回到下面去,曾经解答结束!

2.fragment 是怎么进行销毁重建的?

咱们晓得在某些场景下,咱们的 Activity 是会被回收重建的,Fragment 是依附于 Activity 的,当然也会销毁重建。
而 Activity 场景重建的办法,次要靠 onSaveInstanceState,对页面数据进行状态保留,重建的时候加载保留的数据进行重建。那 fragmentne 呢?和 Activity 是一样的吗?
这里列一下 fragment 常见的 2 种重建形式
办法 1. 应用 Fragment 的 setRetainInstance 办法保留 Fragment 实例,咱们看一下官网的文档

Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). If set, the fragment lifecycle will be slightly different when an activity is recreated:
{@link #onDestroy()} will not be called (but {@link #onDetach()} still will be, because the fragment is being detached from its current activity).
{@link #onCreate(Bundle)} will not be called since the fragment is not being re-created.
{@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} will still be called.

大略的意思是,管制 fragment 在 Activity 重建时是否被保留,如果设置了,对应的生命周期调用会有所不同。

翻译软件真是生涩难懂,简略来讲意思就是,在 Activity 被销毁的时候,Fragment 会被保留下来,在下次 Activity 重建的时候保留的 Fragment 实例会传递给新的 Activity。这里要留神的一点是,这个办法次要是用在设施配置变动,托管的 Activity 正在被销毁的时候,Fragment 的状态才会短暂的被保留,而利用因为内存不足被零碎回收的时候,对应的 Fragment 也会随之被销毁。(很好了解吧,原本内存就须要销毁,那不可能还保留 fragment 实例来占用内存)

具体原理流程是
当设施配置发生变化时,FragmentManager 首先销毁队列中 fragment 的视图
紧接着,FragmentManager 将查看每个 fragment 的 retainInstance 属性值;

如果 mRetainInstance 为 true,则保留 Fragment 自身实例。当新的 Activity 创立后,新的 FragmentManager 会找到保留的 Fragment,从新创立其视图;

如果 mRetainInstance 为 false,FragmentManager 会间接销毁 Fragment 实例,当新的 Activity 创立后,新的 FragmentManager 会创立一个新的 Fragment

这里扩大一下,因为咱们的 Fragment 实例保留了,所以咱们在 Activity 重建的时候不能从新创立实例,否则会有两个 Fragment 实例而呈现问题。这时候能够给 Fragment 设置一个 Tag,通过 Tag 获取 Fragment,不存在才进行创立。

办法 2.FragmentActivity 对 Fragment 的重建

咱们晓得,Activity 在被在被销毁的时候会调用 onSaveInstanceState(Bundle outState)进行状态的保留,FragmentActivity 继承于 Activity,咱们看看 FragmentActivity 的 onSaveInstanceState(Bundle outState)做了什么

    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {super.onSaveInstanceState(outState);
        markFragmentsCreated();
        mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
        // 保留所有状态,返回 FragmentManagerState 实例
        Parcelable p = mFragments.saveAllState();
        if (p != null) {outState.putParcelable(FRAGMENTS_TAG, p);
        }
        if (mPendingFragmentActivityResults.size() > 0) {outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);
    
            int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
            String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
            for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);
                fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);
            }
            outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);
            outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);
        }
    }

从代码能够看出,在销毁之前,FragmentActivity 会保留队列中所有 Fragment 的状态,具体在 Parcelable p = mFragments.saveAllState()外面,感兴趣能够点进去认真看看,我上面就说个大略,不想写太简短就不贴代码了

咱们看到保留的办法返回的 Parcelable 理论是 FragmentManagerState 实例,FragmentManagerState 蕴含了成员 FragmentState,FragmentState 蕴含形容 Fragment 的各个数据变量,足够从零从新创立一个被销毁的 Fragment

在保留了 Fragment 数据之后,Activity 实例以及没有调用 setRetainInstance(true)的 Fragment 实例都被销毁。

在 Activity 重建的时候,FragmentActivity 通过调用 FragmentManager 的 restoreAllState 办法,重建之前保留下来并被销毁的 Fragment

这样 Fragment 就重建实现了。

3.Activity 和 Fragment 怎么进行交互?

这里咱们分成几个局部,Activity 调用 Fragment,Fragment 调用 Activity,Fragment 调用 Fragment

1.Activity 调用 Fragment

这个其实有很多种形式,通过 Bundle 进行初始传值,通过 Fragment 的实例调用 Fragment 的办法

甚至还能够通过 Fragment 的构造函数间接传值(这里不倡议,因为 Fragment 重建时调用的是无参构造函数,传递的数据可能失落)

那应该怎么做呢?废话不多说,上个简略的 demo 代码

public class DemoFragment extends Fragment {public DemoFragment() { }
    // 采纳这种传参形式能够保障用户在横竖屏切换时传递的参数不会失落
    public static DemoFragment getInstance(String data){DemoFragment demoFragment = new DemoFragment();
        Bundle bundle = new Bundle();
        bundle.putString("data",data);
        demoFragment.setArguments(bundle);
        return demoFragment;
    }
}

咱们倡议初始化传值的时候,咱们创立一个办法用户接管参数,而后进行创立并且通过 bundle 传值。

2.Fragment 调用 Activity

这个调用办法就有很多种了,通过接口进行回调传值,通过强转获取 Activity 实例传值,通过播送 EventBus 进行传值

咱们重点说下接口回调传值吧,其余形式都有肯定弊病,代码不肯定优雅

接口交互次要时在 Fragment 中定义一个接口,让宿主 Activity 进行回调监听,上代码

public class DemoFragment extends Fragment implements View.OnClickListener{
    Button demoButton;
    public DemoFragment() {}
    // 采纳这种传参形式能够保障用户在横竖屏切换时传递的参数不会失落
    public static DemoFragment getInstance(String data){DemoFragment demoFragment = new DemoFragment();
        Bundle bundle = new Bundle();
        bundle.putString("data",data);
        demoFragment.setArguments(bundle);
        return demoFragment;
    }
    
    @Override
    public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
        // 通过 getActivity()获取用于回调批改文本办法的接口
        callBackListener= (CallBackListener) getActivity();}
  
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View view =inflater.inflate(R.layout.demo_layout,container,false);
        demoButton = (Button) view.findViewById(R.id.demoButton);
        demoButton.setOnClickListener(this);// 为按钮设置监听事件
        return view;
    }
    @Override
    public void onClick(View v) {switch (v.getId()){
          case R.id.panhouye:
              callBackListener.setData("data");
              break;
      }
        
    // 定义一个接口
    public static interface CallBackListener{public void setData(String data);
    }

}

public class MainActivity extends AppCompatActivity implements DemoFragment.CallBackListener{
    FragmentManager fragmentManager;
    FragmentTransaction fragmentTransaction;
    DemoFragment demoFragment;
    @Override
    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化主布局(次要目标是为主布局填充 fragments)initActivity();}
    private void initActivity() {fragmentManager = getFragmentManager();
        fragmentTransaction = fragmentManager.beginTransaction();
        demoFragment = new DemoFragment();
        fragmentTransaction.add(R.id.demo,DemoFragment);
        fragmentTransaction.commit();}
    // 接口实现办法,用于回调 RightFragment 类中定义的批改文本的办法
    @Override
    public void setData(String data) {// 这里接管到值进行解决}
}

3.Fragment 调用 Fragment

Fragment 和 Fragment 之间须要进行传值,要通过宿主 Activity,咱们能够联合下面两种,通过回调告诉 Activity,而后 Activity 再去调用 Fragment 的办法进行调用。

这里提一句,咱们还能够应用 JetPack 的 LiveData,多个 Fragment 获取宿主 Activity 的 ViewModel,拿到同一个 LiveData,而后就能够进行数值传递告诉了,具体的内容等讲到了 JetPack 的时候在细说。

4.Fragment 怎么实现懒加载?

个别提到这个问题,应用的是 ViewPager+Fragment 的页面架构。咱们晓得 ViewPager 默认会预加载以后 Fragment 前一个和后一个的数据。这会存在两个问题,咱们后面和前面的页面还没展现,然而就进行数据加载申请,造成了数据流量的节约;如果咱们做了数据埋点上报,这时候没有展现的数据就进行上报,不合乎需要。

解决办法有两种,一种是禁用 ViewPager 禁止预加载,这里不进行阐明了,简略也不是咱们次要想说的。

另外一种就是应用懒加载,在旧版本的包里,是通过 setUserVisibleHint()来实现懒加载的,网上的资源较多,感兴趣能够本人去看下。
在 androidX 中,咱们应用 setUserVisibleHint()会发现改办法曾经弃用了。那要怎么实现懒加载呢?咱们看下办法的备注,个别弃用会通知咱们代替形式的

        /**
         *
         * @deprecated Use {@link FragmentTransaction#setMaxLifecycle(Fragment, Lifecycle.State)}
         * instead.
         */
        @Deprecated
        public void setUserVisibleHint(boolean isVisibleToUser) {...}

能够看到,通知咱们能够应用 FragmentTransaction 的 setMaxLifecycle 办法来代替,那么 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;
        }

这里其实很简略,就是将操作封装成一个 op 对象,最初在 commit()的时候,依据 op 对象执行响应的操作,具体代码比较简单也不贴了,能够本人去看看

下面 setMaxLifecycle()正文大略的意思是设置 Fragment 生命周期的下限,如果超过上线会被强制降到相应的状态。具体该怎么去了解呢?咱们这里先去弄清楚两个货色 Fragment 状态和 LifeCycle 状态

Fragment 状态

咱们在 Fragment 类中,能够看到几个常量,用来示意了 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.

LifeCycle 状态

LifeCycle 是 JetPack 中的架构组件之一,次要是用于治理 Activity 和 Fragment 生命周期的,具体的一些具体介绍和应用前面在补一篇,网上也有很多教程,不理解的能够去理解一下

源码中也能够看到,LifeCycle 中应用枚举来定义状态

    public enum State {
            DESTROYED,
            INITIALIZED,
            CREATED,
            STARTED,
            RESUMED;
            public boolean isAtLeast(@NonNull State state) {return compareTo(state) >= 0;
            }
        }

这时候咱们回到 setMaxLifecycle(),办法须要传递两个参数 fragment 和 state。fragment 不必多说,就是要设置的指标 Fragment,不过须要留神的是此时 Fragment 必须曾经被增加到了 FragmentManager 中,也就是调用了 add()办法,否则会抛出异样。state 就是 LifeCycle 中的枚举类型 State,同样须要留神传入的 state 应该至多为 CREATED,换句话说就是只能传入 CREATED、STARTED 和 RESUMED,否则同样会抛出异样。

这里咱们应该先了解一下 Fragment 状态和生命周期的关系,上表格

Fragment 状态过程 生命周期
INITIALIZING –> CREATED onAttach, onCreate
CREATED –> INITIALIZING onDetach, onDestory
CREATED –> ACTIVITY_CREATED onCreateView, onActivityCreated
ACTIVITY_CREATED –> CREATED onDestroyView
ACTIVITY_CREATED –> STARTED onStart
STARTED –> ACTIVITY_CREATED onStop
STARTED –> RESUMED onResume
RESUMED –> STARTED onPause

下面表格展现的是,各个状态切换时会调用的生命周期
这时候应该疑难了,Fragment 和 Lifecycle 的状态是怎么关联的呢?因为 State 至多传入 CREATED,所以咱们就只有钻研 3 个状态就好了,同样来个表格

传入的 Lifecycle 状态 Fragment 状态
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,那么就什么都不做。

不晓得大家看懂了没有,没看懂就多看几次,或者本人手动写一下 demo,看看生命周期怎么打印的,多入手总不会错的。

当初回到问题,ViewPager 的懒加载该怎么做?

其实 Android 提出了应用 setMaxLifecycle 的新计划,那么必定会通知咱们该怎么做,那突破口在哪里呢?不晓得大家在应用 ViewPager 的时候,有没有发现 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);
    }

这里通知咱们,要应用新的构造函数,传入BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT

咱们持续看构造方法,这外面又默认将 BEHAVIOR_SET_USER_VISIBLE_HINT 赋值给了 mBehavior

    public FragmentPagerAdapter(@NonNull FragmentManager fm,
            @Behavior int behavior) {
        mFragmentManager = fm;
        mBehavior = behavior;
    }

咱们看看 mBehavior 在什么中央应用了呢?

全局查找了一下,找到两个中央 instantiateItem()setPrimaryItem()办法。

instantiateItem()次要用来初始化 Viewpager 每个 Item 的办法。

setPrimaryItem()咱们简略看一下源码,发现次要是用来设置行将显示的 item,ViewPager 切换的时候都会调用。源码上一下

   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;
        }
    }

办法的逻辑还是很简略的,如果 mBehavior 的值为 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,那么就调用 setMaxLifecycle() 办法将上一个 Fragment 的状态设置为 STARTED,将以后要显示的 Fragment 的状态设置为 RESUMED;反之如果 mBehavior 的值为 BEHAVIOR_SET_USER_VISIBLE_HINT,那么仍然应用 setUserVisibleHint() 办法设置 Fragment 的可见性,相应地能够依据 getUserVisibleHint()办法获取到 Fragment 是否可见,从而实现懒加载,具体做法我就不说了。

这里咱们联合下面的表格,其实咱们能够发现,每次 Fragment 由不可见变为可见都会执行 onResume()办法

同时如果咱们应用的是 FragmentPagerAdapter,切换导致 Fragment 被销毁时是不会执行 onDestory()和 onDetach()办法的,只会执行到 onDestroyView()办法。

那应该晓得怎么做了吧?代码来了

public abstract class BaseLazyFragment extends Fragment {
    // 懒加载调用只有第一次显示的时候才进行调用
    private boolean isFirstLoad = true;
    @Override
    public void onResume() {super.onResume();
        if (isFirstLoad) {
            isFirstLoad = false;
            lazyInit();}
    }
    @Override
    public void onDestroyView() {super.onDestroyView();
        isFirstLoad = true;
    }
    // 初始化办法
    public abstract void lazyInit();}

总结

这里咱们次要介绍了 Fragment 各个生命周期的作用,以及 Fragment 在应用过程中基本上必定会遇到的问题,其实还有基于 JetPack 的 LiveData 进行 Fragment 间的数据共享没有降讲,筹备放在 JetPack 文章中在进行细说

个别应用 Fragment,咱们关怀的也就这几个点,1. 数据传递,2. 销毁重建,3. 懒加载。本文就简略的讲了一下,个别是足够进行应用了,尽管很多细节的没有讲,后续会陆续补上。

最初说一句,自己技术不精,如有错漏,欢送斧正交换,轻喷!

正文完
 0