我正在参与「启航方案」

前言

系列文章:

Android Activity 与View 的互动考虑
Android Activity 生命周期详解及监听
Android onSaveInstanceState/onRestoreInstanceState 本来要这么了解
Android Fragment 要你何用?
Android Fragment 要你何用?2.0版别
Android Activity/View/Window/Dialog/Fragment 深层次相关(白话解析)

在之前的文章里有剖析过Activity、Fragment、View之间的相关,也简略剖析了Fragment的原理。
本篇将对Fragment被高频运用的场景以及一些坑点作剖析,通过本篇文章,你将了解到:

  1. 陈词滥调:为什么需求Fragment?
  2. Fragment 的生命周期与重建流程
  3. Fragment add/remove/replace/hide/show的快速了解
  4. Fragment与ViewModel的结合
  5. DialogFragment(Dialog和Fragment的结晶)
  6. ViewPager2(Fragment与RecyclerView的结晶)

1. 陈词滥调:为什么需求Fragment?

先看Activity、Fragment、View三者的联系:

Android Fragment 要你何用?2.0版本

Activity 具有生命周期,可是需求和AMS通讯(跨进程),比较臃肿。
View 不需求和AMS通讯,但没有生命周期,不好处理杂乱的逻辑(如网络恳求数据烘托到View上)。
而Fragment介于两者之间,它具有生命周期(借助于Activity),无需与AMS通讯,速度快。

Fragment更多时分充当着中介的作用:

将Activity里杂乱的UI逻辑分离出来放到Fragment里,将对View杂乱的操作分离到Fragment里。

一言蔽之,运用Fragment的理由:

  1. 无需跨进程,轻量级
  2. 具有生命周期

2. Fragment 的生命周期与重建流程

与Activity生命周期对比

Fragment创立分为动态和静态,以动态创立Fragment为例剖析:

Android Fragment 要你何用?2.0版本

这么看Fragment生命周期的回调办法比Activity更多,还好大部分是成对出现有迹可循。
通常情况下,咱们只需求重写onCreateView(),供给自定义界面就好。

Fragment 重建流程

现象

先看简略Demo:在Activity的onCreate里增加Fragment:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.layout_fragment )
        val fragment = FishPureFragment()
        fragment.arguments = Bundle().apply {
            putString("hello", "fragment:${count++}")
        }
        supportFragmentManager.beginTransaction().add(R.id.root, fragment).commitNow()
    }

Activity 显现的同时Fragment也显现了,一切看起来很正常,此刻咱们将手机反正屏切换一下:

Android Fragment 要你何用?2.0版本

能够看出,跟着反正屏的切换,创立的Fragment越来越多。
此种重建现象在许多场景下并不符合需求,比方进入Activity后拉取接口并显现弹窗,若运用DialogFragment展现弹窗,则Activity重建后会出现多个弹窗。

原理

咱们知道,Fragment的生命周期依靠于Activity,当反正屏切换时分Activity进行了重建,同时会查看相关该Activity的一切Fragment是否需求重建,若是则进行重建。
第一次显现Fragment后,反正屏切换导致Activity重建,此刻也会重建Fragment,而在Activity.onCreate()里又新建了Fragment,因而此刻Activity里相关了2个Fragment。

Android Fragment 要你何用?2.0版本

核心点在于赤色部分:

  1. Activity 毁掉时保存Fragment状况
  2. Activity 重建时根据状况康复并展现Fragment

除了反正屏会导致Activity重建,其它装备项改变、体系内存紧张kill掉Activity等也会引起Activity重建
更具体请移步:Android onSaveInstanceState/onRestoreInstanceState 本来要这么了解

Fragment重建引发的Crash

和Activity等四大组件不一样的是:咱们能够直接创立Fragment实例。
若是咱们重写了Fragment默许结构函数:

class FishPureFragment(val str:String):Fragment()

在Fragment重建的时分会Crash:

    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.fish.kotlindemo/com.fish.kotlindemo.fragment.dialogFragment.FishFragmentActivity}: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment com.fish.kotlindemo.fragment.dialogFragment.FishPureFragment: could not find Fragment constructor

原因是Fragment重建时会去寻觅默许结构函数结构新的实例,而咱们重写了结构函数,此刻现已不存在默许的无参结构函数,当然会Crash。
因而,通常来说无需重写Fragment结构函数,若是想要传递参数给Fragment,可运用Fragment.setArguments()。

如何制止Fragment重建

有以下几种办法:
第一种:
既然是Activity重建引发的Fragment重建,那么釜底抽薪,制止Activity重建。
比方制止屏幕旋转时重建Activity,能够在AndroidManifest.xml里装备:

android:configChanges="orientation|screenSize"

第二种:
不想改装备,也能够从Activity保存的状况下手,Frament重建依靠于康复状况:

    //FragmentActivity.java
    private void init() {
        addOnContextAvailableListener(new OnContextAvailableListener() {
            @Override
            public void onContextAvailable(@NonNull Context context) {
                //Activity.onCreate里会调用此办法
                mFragments.attachHost(null /*parent*/);
                //找到需求康复的Fragment状况
                Bundle savedInstanceState = getSavedStateRegistry()
                        .consumeRestoredStateForKey(FRAGMENTS_TAG);
                if (savedInstanceState != null) {
                    //不为空则进行Fragment康复,重建
                    Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
                    mFragments.restoreSaveState(p);
                }
            }
        });
    }

也便是说只要康复状况为空,那么Fragment就不会进行重建,而该状况是从Activity.onCreate(Bundle)里传递过来的,因而只需求在Activity里进行如下设置:

    override fun onCreate(savedInstanceState: Bundle?) {
//        super.onCreate(savedInstanceState)
        //状况置空
        super.onCreate(null)
        setContentView(R.layout.layout_fragment )
        val fragment = FishPureFragment()
        fragment.arguments = Bundle().apply {
            putString("hello", "fragment:${count++}")
        }
        supportFragmentManager.beginTransaction().add(R.id.root, fragment).commitNow()
    }

从super.onCreate(savedInstanceState)替换为super.onCreate(null)。

第三种:
将Activity下的一切康复状况置空有点粗犷了,会影响到其它组件如View的康复,而咱们只是只需求制止Fragment重建,运用如下办法:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        savedInstanceState?.let {
            it.getBundle("androidx.lifecycle.BundleSavedStateRegistry.key")?.remove("android:support:fragments");
        }
    }

注:此种办法与体系实现版别有关,有些版别取不到value

3. Fragment add/remove/replace/hide/show的快速了解

add

增加一个Fragment实例到Fragment办理栈里,此间Fragment生命周期阅历了从onAttach()逐步到onActivityCreated()。
此刻Fragment所绑定的布局(View)现已增加到指定的父布局里了。

当办理栈里已有一个Fragment实例:Fragment1,此刻再往里边增加Fragment2,Fragment2入栈并将其绑定的布局增加到父布局里。
根据父布局属性不同,有不同的展现办法:

  1. 若父布局是FrameLayout,则Fragment2绑定的布局将会掩盖Fragment1绑定的布局。
  2. 若父布局是LinearLayout,则Fragment2绑定的布局将会增加到Fragment1绑定的布局左边/右边。

remove

将一个Fragment实例从Fragment办理栈里移除,此间Fragment生命周期阅历了从onDestroyView()逐步到onDetach()。
此刻Fragment所绑定的布局(View)现已从指定的父布局里移除了。

replace

当Fragment办理栈里没有Fragment实例时,replace与add作用一致。
当Fragment办理栈里有Fragment1实例时,replace(Fragment2)将触发Fragment1的生命周期从onDestroyView()逐步到onDetach(),而Fragment2生命周期从onAttach()逐步到onActivityCreated(),终究展现Fragment2。
replace 可简略了解为remove+add。

hide

不会触发Fragment生命周期,只是只是把Fragment绑定的布局隐藏(GONE)。

show

hide的反向操作,不会触发Fragment生命周期,只是只是把Fragment绑定的布局显现(VISIBLE)。

4. Fragment与ViewModel的结合

Activity ViewModel与FragmentManagerViewModel

Android Fragment 要你何用?2.0版本

如上图,Activity ViewModelStore里存储着不同的ViewModel实例,其间FragmentManagerViewModel是与Fragment有关的。
当Activity毁掉重建时,在Activity毁掉阶段ViewModelStore并没有被开释,而是被保存下来,等到Activity重建时继续运用,而ViewModelStore实例不变,其内部的ViewModel也不变,这便是ViewModel的原理。

那么FragmentManagerViewModel 是什么时分增加到ViewModelStore里的呢?
入口在这:

    #FragmentActivity.java
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        //绑定FragmentManager
        mFragments.attachHost(null /*parent*/);
    }

终究调用到:

    #FragmentManager.java
    void attachController(@NonNull FragmentHostCallback<?> host,
                          @NonNull FragmentContainer container, @Nullable final Fragment parent) {
        //...
        if (parent != null) {
            mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
        } else if (host instanceof ViewModelStoreOwner) {
            //host 即为承载的Activity
            //取出Activity ViewModelStore
            ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
            mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
        } else {
            mNonConfig = new FragmentManagerViewModel(false);
        }
    }
    #FragmentManagerViewModel.java
    static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
        ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore,
                FACTORY);
        //从Activity的ViewModelStore 取出FragmentManagerViewModel
        //若没有,则创立FragmentManagerViewModel实例
        return viewModelProvider.get(FragmentManagerViewModel.class);
    }

答案是:Activity.onCreate()将FragmentManagerViewModel增加到Activity ViewModelStore里。

FragmentManagerViewModel与Fragment的ViewModel

现在FragmentManagerViewModel能够在Activity重建时康复,那么它和Fragment里的ViewModel又是如何相关的呢?

FragmentManagerViewModel 里有个成员变量:

private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();

在Fragment里声明ViewModel:

private val vm by viewModels<MyViewModel2>()

查看viewModels调用链,要害要找到对应的ViewModelStore,而Fragment的ViewModelStore获取办法如下:

#Fragment.java
    public ViewModelStore getViewModelStore() {
        if (mFragmentManager == null) {
            throw new IllegalStateException("Can't access ViewModels from detached fragment");
        }
        return mFragmentManager.getViewModelStore(this);
    }
    #FragmentManagerViewModel.java
    ViewModelStore getViewModelStore(@NonNull Fragment f) {
        //mViewModelStores 是Map,key 是Fragment唯一标识符 value 为Fragment对应的ViewModelStore
        ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
        if (viewModelStore == null) {
            //不存在则创立
            viewModelStore = new ViewModelStore();
            //放入map
            mViewModelStores.put(f.mWho, viewModelStore);
        }
        return viewModelStore;
    }

到此,流程就比较明晰了:

FragmentManagerViewModel里的mViewModelStores里存储着一切Fragment的ViewModelStore。也便是Fragment的ViewModel实践上是存储在FragmentManagerViewModel里的。

接着来整体捋一下Fragment的ViewModel是如何在重建时保持的。

Android Fragment 要你何用?2.0版本

清楚明了,Fragment ViewModel实践上是直接依靠Activity ViewModeStore。
更具体的请移步:Jetpack ViewModel 抽丝剥茧

Fragment 相关的lifecycleScope

lifecycleScope 监听着Fragment生命周期,若是Fragment被毁掉,则lifecycleScope也会被撤销。

Fragment ViewModel相关的viewModelScope

viewModelScope 与ViewModel 生命周期保持一致,若是ViewModel被毁掉(Fragment被毁掉而非重建),则viewModelScope也会被撤销。

5. DialogFragment(Dialog和Fragment的结晶)

DialogFragment 运用

一般的Dialog并没有生命周期,而不相关生命周期的Dialog处理异步恳求比较费事,此刻DialogFragment出现了。

class MyDialogFragment : DialogFragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val tv = TextView(context).apply {
            text = "hello world"
            setTextColor(Color.RED)
            textSize = 30f
        }
        return tv
    }
}

显现DialogFragment:

       MyDialogFragment().show(supportFragmentManager, "dd")

咱们只需求定义Fragment所绑定的布局,终究布局将会显现在Dialog里。

DialogFragment 原理

Dialog 显现Fragment绑定的布局

你或许比较好奇:之前增加的Fragment都是指定父布局,将Fragment所绑定的布局增加到父布局里,那此刻的DialogFragment所指定的父布局在哪呢?
从show办法下手:

    #DialogFragment.java
    public void show(@NonNull FragmentManager manager, @Nullable String tag) {
        //便是增加一般的Fragment流程
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(this, tag);
        ft.commit();
    }
    #FragmentTransaction.java
    public FragmentTransaction add(@NonNull Fragment fragment, @Nullable String tag)  {
        //没有指定父布局
        doAddOp(0, fragment, tag, OP_ADD);
        return this;
    }

与增加一般Fragment不同的是此刻并没有指定Fragment的父布局。

Dialog会监听Fragment生命周期:

    #DialogFragment.java
    private Observer<LifecycleOwner> mObserver = new Observer<LifecycleOwner>() {
        public void onChanged(LifecycleOwner lifecycleOwner) {
            if (lifecycleOwner != null && mShowsDialog) {
                //拿到Fragment绑定的View
                View view = requireView();
                if (mDialog != null) {
                    //将View增加到Dialog里
                    mDialog.setContentView(view);
                }
            }
        }
    };

当生命周期回调后拿到Fragment绑定的View增加到Dialog里。
如此一来,Fragment就完成了和Dialog的合作显现界面。

Dialog的创立时机

    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        return super.onCreateDialog(savedInstanceState);
    }

重写该办法能够自定义Dialog或是监听默许Dialog的创立。
值得注意的是:在onAttach()/onCreate()里是拿不到Dialog对象的,因为那时分还没有创立Dialog,它在onCreateView()之前创立的。

6. ViewPager2(Fragment与RecyclerView的结晶)

ViewPager2运用

除了DialogFragment,Fragment也与RecyclerView合作,形成了ViewPager2。
创立Adapter:

public class VPAdapter extends FragmentStateAdapter {
    private List<FishPureFragment> list = new ArrayList<>();
    public VPAdapter(@NonNull FragmentActivity fragmentActivity, List<FishPureFragment> list) {
        super(fragmentActivity);
        this.list = list;
    }
    @NonNull
    @Override
    public Fragment createFragment(int position) {
        return list.get(position);
    }
    @Override
    public int getItemCount() {
        return list.size();
    }
}

ViewPager2绑定Adapter:

        VPAdapter vpAdapter = new VPAdapter(this, list);
        viewPager2.setAdapter(vpAdapter);

能够看出,运用起来很简洁,ViewPager2的显现依靠着Fragment。

ViewPager2原理

RecyclerView缓存

在此之前先简略介绍一下RecyclerView(简称RV)的缓存规划。
RV缓存的是什么呢?
缓存的是ViewHolder(简称VH),VH里持有待显现的子布局(View)。

RecyclerView里有个内部类:Recycler,里边有5个缓存变量,归为3种缓存,分别为:
一级缓存(默许2个+1个预拉取)、二级缓存、三级缓存(默许5个,区别itemType)。
当RecyclerView烘托布局显现item时,先分别从一、二、三级缓存寻觅可用的VH,若没有找到则重新创立子布局及其所属的VH,终究烘托。

VH有两种状况:

  1. VH保持着当时的数据状况,此种状况下当VH复用时可直接运用,对应一级缓存。
  2. VH只是保持着View,没有绑定数据,此种状况下当VH复用时需求重新绑定数据,也便是走onBindViewHolder()办法,对应三级缓存。

二级缓存是暴露给调用者设置自定义缓存的,此处先忽略。
先看看一、三级缓存是如何填充数据的。

Android Fragment 要你何用?2.0版本

再看RV是如何从缓存取数据的:

Android Fragment 要你何用?2.0版本

RecyclerView与Fragment联动

核心代码在FragmentStateAdapter.java里。
RecyclerView要害动作与Fragment的联动:

Android Fragment 要你何用?2.0版本

本质上还是将Fragment的View增加到RV提早结构的ItemView内。

咱们来整理一下ViewPager2(简称VP2)的滑动场景。

  1. VP2展现第一个元素,实践展现的是RV的第一个元素,此刻RV回调了onCreateViewHolder、onBindViewHolder等回调,Fragment也走了生命周期,终究Fragment绑定的View增加到了RV的子Item里
  2. 当滑动VP2到第二个元素(下标为1)时,RV的第一个元素被放到一级缓存,此刻RV触发移除子Item,注意这个时分并没有毁掉Fragment
  3. 当滑动VP2回到第一个元素时,RV只是只需求addView即可,不会触发Fragment生命周期
  4. 当滑动VP2到后边几个元素时,此刻一级缓存已满,将放到三级缓存里,然后触发子Item的回收,这个时分会移除对应的Fragment,Frament走生命周期里的毁掉流程
  5. RV的预取元素时,会走onCreateViewHolder、onBindViewHolder回调,但不会触发addView,也便是不会触发Fragment的生命周期

VP2的缓存

public void setOffscreenPageLimit(@OffscreenPageLimit int limit)

设置VP2左右缓存的数量,默许是没有缓存的,也不能将缓存设置为0,只能是>=1。

VP2的缓存和RV的缓存是什么联系呢?
假设VP2缓存limit=1,再来整理一下ViewPager2(简称VP2)的滑动场景。

  1. VP2展现第一个元素,实践展现的是RV的第一个元素,此刻RV回调了onCreateViewHolder、onBindViewHolder等回调,Fragment也走了生命周期,终究Fragment绑定的View增加到了RV的子Item里。与此同时,VP2继续烘托第二个元素,与第一个元素过程一致
  2. 当VP2滑动到第二个元素时,因为之前现已烘托过,此刻是直接展现(RV无需addView)。于此同时会继续提早缓存第三个元素。
  3. 当VP2滑动到第一个元素时,,因为之前现已烘托过,此刻是直接展现。

能够看出VP2的缓存实践上是提早将RV的元素烘托了,若设置了limit=1,那么此刻RV活跃的Item有三个:当时1个+左右各一个。
当设置了VP2的缓存后,意味着多缓存了Fragment实例。

以上是Fragment实践使用与原理的相关内容。
本文根据Android 10.0

您若喜爱,请点赞、重视、收藏,您的鼓舞是我前进的动力

继续更新中,和我一起稳扎稳打体系、深化学习Android/Kotlin

1、Android各种Context的前世今生
2、Android DecorView 必知必会
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分发全套服务
6、Android invalidate/postInvalidate/requestLayout 完全厘清
7、Android Window 如何确认大小/onMeasure()多次履行原因
8、Android事件驱动Handler-Message-Looper解析
9、Android 键盘一招搞定
10、Android 各种坐标完全明了
11、Android Activity/Window/View 的background
12、Android Activity创立到View的显现过
13、Android IPC 系列
14、Android 存储系列
15、Java 并发系列不再疑问
16、Java 线程池系列
17、Android Jetpack 前置基础系列
18、Android Jetpack 易学易懂系列
19、Kotlin 轻松入门系列
20、Kotlin 协程系列全面解读