携手创作,共同成长!这是我参与「日新计划 8 月更文挑战」的第12天,点击查看活动详情

Navigation保存实例的几种计划分析

关于单Activity+多Fragment的系列文章我已经出了几期了,关于单Activity+多Fragment的结构完成,现在比较引荐的做法是根据 Navigation 来完成此功能。

之前的文章咱们讲过 单Activity+多Fragment的结构选型 。评论并完成了Navigation的封装。解决了Navigation中通信问题。咱们假如有爱好能够回顾一下。

那么 Navigation 的运用是不是只要这一种计划呢? 保存/康复 Fragment 的实例的办法有没有别的计划呢?假如有总共有哪些门户?咱们运用都是根据哪一种门户完成的呢?有什么优缺陷呢?

一、原生的运用

咱们在讲 Navigation 的实例保存/康复的示例之前咱们先用原生的 Navigation 完成一次,而且打印生命周期。

假如你能看到此文,那我相信各位高工们应该不是初步接触 Navigation,或多或少应该都会根底的运用的,这儿关于基本的运用我就快速过了,假如实在是想了解 Navigation 的根底运用与源码解析,引荐去看 Navigation运用与源码分析。

Activity的xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:viewBindingIgnore="true">
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        app:navGraph="@navigation/nav2"
        app:defaultNavHost="true" />
</FrameLayout>

Navigation装备文件-导航图

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    app:startDestination="@id/page1Fragment">
    <fragment
        android:id="@+id/page1Fragment"
        android:name="com.guadou.kt_demo.demo.demo11_fragment_navigation.nav2.Nav2Fragment1"
        android:label="fragment_page1"
        tools:layout="@layout/nav2_fragment1">
        <action
            android:id="@+id/action_page2"
            app:destination="@id/page2Fragment" />
    </fragment>
    <fragment
        android:id="@+id/page2Fragment"
        android:name="com.guadou.kt_demo.demo.demo11_fragment_navigation.nav2.Nav2Fragment2"
        android:label="fragment_page2"
        tools:layout="@layout/nav2_fragment2">
        <action
            android:id="@+id/action_page3"
            app:destination="@id/page3Fragment" />
    </fragment>
    <fragment
        android:id="@+id/page3Fragment"
        android:name="com.guadou.kt_demo.demo.demo11_fragment_navigation.nav2.Nav2Fragment3"
        android:label="fragment_page3"
        tools:layout="@layout/nav2_fragment3" >
    </fragment>
</navigation>

然后就能够运用了,以 Fragment2 为例:

    override fun initViews(view: View) {
        view.findViewById<TextView>(R.id.btn_gotopage1).click {
            Navigation.findNavController(getView() ?: view).navigateUp()
        }
        view.findViewById<TextView>(R.id.btn_gotoPage3).click {
            Navigation.findNavController(getView() ?: view).navigate(R.id.action_page3, null)
        }
    }

跳转咱们运用action 回来咱们运用 navigateUp 。咱们操作从 Fragment1 到 Fragement2 到 Fragment3 然后回来到 Fragment2 再回来到 Fragment1 。

再讲Navigation-基于Navigation实现单Activity+多Fragment保存/恢复实例的几种流派分析

打印的生命周期Log如下:

再讲Navigation-基于Navigation实现单Activity+多Fragment保存/恢复实例的几种流派分析

能够看到默许是无法保存实例的,EditText的值都没了,内部的View都从头创立了,那么现在就开始咱们的操作了,怎么保存实例。

二、运用ViewModel康复数据

看生命周期,虽然是走的OnCreateView onDetstoryView ,说明此 Fragment 也仅仅销毁了View,康复的时分再创立View。可是 Fragment 仍是那个 Fragment 。它内部持有的ViewModel没变,它内部持有的成员变量值也没有变。

咱们是不是能够直接经过成员变量康复值呢?

    <EditText
        android:id="@+id/et_one"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/d_40dp" />
class Nav2Fragment1 : BaseVDBFragment() {
   var oneTextValue: String = ""   //赋值成员变量办理
   override fun init() {
        if (!TextUtils.isEmpty(oneTextValue)) {
            mBinding.etOne.setText(oneTextValue)
        }
        //监听ET的值
        mBinding.etOne.addTextChangedListenerDsl {
            onTextChanged { charSequence, i, i2, i3 ->
                oneTextValue = charSequence.toString()
            }
        }
    }
}

作用:

再讲Navigation-基于Navigation实现单Activity+多Fragment保存/恢复实例的几种流派分析

那为什么谷歌引荐咱们运用ViewModel来保存这些数据?咱们挑选一下屏幕就知道,这些数据会被清掉了,假如内存不足,相同会重走生命周期导致成员变量会收回,所以引荐咱们运用ViewModel来保存数据。

那咱们现在进阶一下,假如咱们运用 ViewModel 搭配 DataBinding 那… 主动赋值,主动保存,主动康复 ,岂不是完美?

试试?

@HiltViewModel
class Nav2ViewModel  @Inject constructor():BaseViewModel() {
    var mEtOneText = MutableLiveData<String>()
    var mEtTwoText = MutableLiveData<String>()
}

咱们保存前两个页面的EditText的值。在Fragment 中咱们直接绑定 DataBinding 装备

class Nav2Fragment1 : BaseVDBFragment<EmptyViewModel, Nav2Fragment1Binding>() {
    private val activityViewModel by activityViewModels<Nav2ViewModel>()
    override fun getDataBindingConfig(): DataBindingConfig {
        return DataBindingConfig(R.layout.nav2_fragment1, BR.viewModel, activityViewModel)
    }
}

在页面中便是很普通的 DataBinding xml 文件。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:binding="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:ignore="RtlHardcoded">
    <data>
        <variable
            name="viewModel"
            type="com.guadou.kt_demo.demo.demo11_fragment_navigation.nav2.Nav2ViewModel" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:gravity="center_horizontal"
        android:orientation="vertical">
        <EditText
            android:text="@={viewModel.mEtOneText}"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/d_40dp" />
        <Button
            android:id="@+id/btn_gotoPage2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/d_40dp"
            android:text="去第二个页面" />
    </LinearLayout>
</layout>

没有做任何别的操作,看看作用

再讲Navigation-基于Navigation实现单Activity+多Fragment保存/恢复实例的几种流派分析

这???ViewModel + DataBinding 就主动就适配 Navigation 的实例保存问题了?

是的,这也是谷歌引荐 MVVM 结构的原因,这也是 DataBinding 的魅力地点,真是YYDS!

当然咱们这儿Demo是最简略的例子,假如有比较杂乱的数据需求康复,咱们能够自行在ViewModel中康复,或许自界说 DataBindingAdapter 的办法来完成。这么讲下去就脱离话题了,有机会讲DataBinding的时分再细讲。

三、运用View的缓存来康复

MVVM 结构是好用,可是我不会,或许我觉得太麻烦不想用,有没有更简略的办法?

有!试试这种办法。

之前咱们有讲到既然是走的 OnCreateView onDetstoryView ,说明此 Fragment 也仅仅销毁了View, 康复的时分再创立View。那咱们把此 Fragment 的视图View保存不就行了吗?

咱们保存一份根视图,当重建的时分走 OnCreateView ,咱们再还给他不就行了?

其他的代码不需求修改,咱们直接改 Fragment 的基类。

代码如下:

abstract class AbsFragment : Fragment(), ConnectivityReceiver.ConnectivityReceiverListener {
    private var isRootViewInit = false
    private var rootView: View? = null
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        if (rootView == null) {
            rootView = transformRootView(setContentView(container))
        }
        return rootView
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        if (!isRootViewInit) {//初始化过视图则不再进行view和data初始化
            super.onViewCreated(view, savedInstanceState)
            initViews(view)
            isRootViewInit = true
        }
        initViews(view)
    }
    override fun onDestroy() {
        super.onDestroy()
        isRootViewInit = false
        rootView = null
    }

那咱们 Fragment 的代码如第一步的代码,最原始的Fragment,也不必ViewModel,也不必DataBinding。

作用如下

再讲Navigation-基于Navigation实现单Activity+多Fragment保存/恢复实例的几种流派分析

相同的能够到达作用,比较第一步,第二步的优势是,无需再次inflate,解析Xml了,节省了解析布局,填充布局的耗时,可是把View缓存了,更耗费内存了,归于空间换时间的一种。

缺陷便是 RootView 在成员变量中保存的,咱们无法保障页面康复的时分能必定康复到 RootView 。那么这种情况下,咱们能够运用 ViewModel 的办法来缓存,或许运用一个大局的或Activity等级的静态容器来缓存,在创立Fragment和销毁Fragment的时分办理静态容器,相同的能够到达咱们的作用。

这种计划也是能够用于实战,很好用的一种计划,简略暴力。

四、魔改Navigation

之前咱们有讲到既然是走的 OnCreateView onDetstoryView ,说明此 Fragment 也仅仅销毁了View, 康复的时分再创立View。咱们追根查询为什么会这样?

咱们应该都知道 Navigation 的原理便是 FragmentTransaction 的 replace 办法。这就引出了这一种计划,改造 Navigation 的计划,修改为 show / hide 的计划。

之前的文章我已经分享过,怎么改造 Navigation,怎么封装 Navigation 的路由表,封装跳转,回来等处理。

其实改造 Navigation 咱们都改造程度不同,有的计划仅仅修改 show / hide ,跳转仍是运用原生的。

有的改造就把路由表的生成改了,觉妥当项目大了,保护一个巨大的路由表太麻烦了,有些人经过界说跳转的办法手动的创立路由表,有些人经过Fragment上加注解的办法,经过注解生成器来生成对应的路由表,生成的办法不同,可是异曲同工,都是为了去掉 Navigation 的 xml 装备文件。

再进一步的改造,便是把跳转和回来等事情封装,再进一步就增加路由能够外部直接跳转,等等等等。

我的计划也仅仅到跳转和回来的封装,经过自界说跳转办法,手动的增加导航图。无需装备杂乱的路由表。

关于改造的作用,假如咱们有爱好能够看看我之前的文章。这儿不过多赘述。

总结

现在主流的便是这三种门户来完成的,其间如有一些变化,基本不脱离这三个方向。

到底用哪一种门户比较好?

假如你就想用原生的 Navigation 我引荐你运用 DataBinding + ViewModel 的办法,这一种计划哪都好,便是会销毁View 和 创立View,假如你的 Fragment 布局很杂乱,那么或许康复的时分会耗时会卡顿。

假如你不会或不想运用 DataBinding ,那也能够运用第二种计划,运用一个容器缓存当时 Fragment 的跟视图,在 onCreateView 的时分康复。可是缺陷是相对占用空间,内存不足或旋转屏幕页面重建的时分会丢失,我引荐运用Activity 等级的静态容器来办理这些跟视图。

假如觉得自己的项目太大,不想保护巨大的路由表,也能够运用魔改 Navigation 的办法,经过 FragmentManager 来主动的帮咱们办理Fragment的实例。

能不能一起用,或结合起来运用?

当然不是挑选一种计划就放弃另一种了,咱们能够三种计划自由挑选与组合都是能够的,比方我不改 show / hide。我仅仅不想写路由表,我经过注解的办法主动生成路由表或许自界说发动办法在内部手动创立路由图,然后我运用第二种计划,运用静态容器来自行办理 Fragment 的 rootView 。或许我经过 DataBinding + ViewModel 来办理实例,都是能够的。

你是哪一挂的?

啊,你问我挑选哪一种门户?我是魔改派的。魔改派最方便最简略了 !

哈哈,开个玩笑,各计划各有利弊,咱们按实例项目与需求,自行挑选即可。

好了,仍是那句话,手机红外功能,我或许一辈子都用不到,可是我要有。关于一些功能的完成计划,我能够不必,可是我要会!

本期内容如讲的不到位或讹夺的地方,期望各位高工能够指出交流。

假如感觉本文对你有一点点点的启发,还望你能点赞支持一下,你的支持是我最大的动力。

Ok,这一期就此完结。

再讲Navigation-基于Navigation实现单Activity+多Fragment保存/恢复实例的几种流派分析