工作这么多年了,始终在做笔记,没有公布什么货色,总感觉网上曾经有了,就懒得写。
最初想了想,还是从最根底的开始,逐渐刨析原理,测验本人的了解水平,心愿各路大神探讨领导,如有谬误欢送斧正,轻喷!
对了,本文波及到的源码都是基于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 --> CREATEDonAttach, onCreate
CREATED --> INITIALIZINGonDetach, onDestory
CREATED --> ACTIVITY_CREATEDonCreateView, onActivityCreated
ACTIVITY_CREATED --> CREATEDonDestroyView
ACTIVITY_CREATED --> STARTEDonStart
STARTED --> ACTIVITY_CREATEDonStop
STARTED --> RESUMEDonResume
RESUMED --> STARTEDonPause

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

传入的Lifecycle状态Fragment状态
Lifecycle.State.CREATEDLifecycle.State.CREATED对应Fragment的CREATED状态,如果以后Fragment状态低于CREATED,也就是INITIALIZING,那么Fragment的状态会变为CREATED,顺次执行onAttach()、onCreate()办法;如果以后Fragment状态高于CREATED,那么Fragment的状态会被强制降为CREATED,以以后Fragment状态为RESUMED为例,接下来会顺次执行onPause()、onStop()和onDestoryView()办法。如果以后Fragment的状态恰好为CREATED,那么就什么都不做。
Lifecycle.State.STARTEDLifecycle.State.STARTED对应Fragment的STARTED状态,如果以后Fragment状态低于STARTED,那么Fragment的状态会变为STARTED,以以后Fragment状态为CREATED为例,接下来会顺次执行onCreateView()、onActivityCreate()和onStart()办法;如果以后Fragment状态高于STARTED,也就是RESUMED,那么Fragment的状态会被强制降为STARTED,接下来会执行onPause()办法。如果以后Fragment的状态恰好为STARTED,那么就什么都不做。
Lifecycle.State.RESUMEDLifecycle.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.懒加载。本文就简略的讲了一下,个别是足够进行应用了,尽管很多细节的没有讲,后续会陆续补上。

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