Jetpack-LifeCycle相关组件使用

586 阅读8分钟

首先介绍一下Jetpeck,Jetpack是Google在2017年以来提供的库的集合,从刚开始的Architecture Component, ORM库Room

到现在的androidx,以及各大组件的完善。可以说,Google自2017年以来,一直致力于Android库的完善,以收回开发库杂乱且不受约束的局面。

扯得有点多,今天呢,介绍下Jetpack 组件中的一部分,LifeCycle相关组件。LifeCycle相关组件包括ViewModel、LiveData以及LifeCycleOwner。

依赖导入

使用Jetpack组件进行代码开发,需要像引入其他开源库一样,在项目中指定

在根build.gradle中,需要确保有google相关依赖

allprojects {
        repositories {
            google()
...
        }
    }

随后在模块中添加具体依赖

dependencies {
				//当前版本仅供参考,具体可查看官网
        def lifecycle_version = "2.2.0"
        // 用于ViewModel
        implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
        // 用于LiveData
        implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
        // 为ViewModel存储状态
        implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
    }

ViewModel

官方介绍

ViewModel是负责为Activity、Fragment准备和管理数据的类。

背景

ViewModel始终与(Activity or Fragment)生命周期关联的创建,并且一直会被保留,直到生命周期终结(Finished)。

在最新的Android生命周期介绍中,引入了一个Finished(终结)的概念,数据最终的生命周期。

什么意思呢,就是一个处于destroy之后的生命周期。也就是说,屏幕旋转这种重新创建Activity的行为,也不会使得ViewModel被销毁。

我们就可以理解,ViewModel可以帮助我们在屏幕旋转时做一些帮助。

在代码中应用

我们在Activity中使用一个Chronometer(计时器)控件来验证ViewModel的功能。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val chronometer = findViewById<Chronometer>(R.id.chronometer)
        chronometer.start()
    }
}

在运行完之后,我们发现开始计时,

然后点击一下屏幕旋转

我们会发现屏幕重新开始计时。

这个时候我们使用ViewModel来对计时器开始时间数据的存储

第一步:创建我们的ViewModel,使之继承于androidx.lifecycle.ViewModel

class ChronometerViewModel : ViewModel() {
    var startTime: Long? = null
}

在其中添加一个变量,用来存储开始时间

第二步:修改Activity中的逻辑

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //新建一个ViewModelProvider,并且传入一个LifeCycleOwner
        //通过调用其中的get方法,并且传入我们的ViewModel对应的Class
        val chronometerViewModel = ViewModelProvider(this).get(ChronometerViewModel::class.java)

        val chronometer = findViewById<Chronometer>(R.id.chronometer)
        
        //判断获取到的ViewModel中的参数是否为空
        //为空说明获取到的ViewModel中数据并未存储
        if (chronometerViewModel.startTime == null) {
            val startTime = SystemClock.elapsedRealtime()
            chronometerViewModel.startTime = startTime
            chronometer.base = startTime
        } else {
            //反之,ViewModel中依然有数据,说明之前已经做过存储,可以取出设置给计时器
            val startTimeFromViewModel: Long = chronometerViewModel.startTime!!
            chronometer.base = startTimeFromViewModel
        }

        //开始这个计时器
        chronometer.start()
    }

运行到设备上,并且点击旋转,我们会发现计时器在旋转之后并未从头开始,也就意味着在Activity销毁重新创建后,其中的ViewModel并未被销毁,准确的来说是Finished(终结)!

  • 注意:这里的ViewModel并不是持久化存储,是存储在内存中,如果对应的Activity-终结(Finished),其也会被终结(Finished).

这样的话,我们的ViewModel的基础使用就完成了。

LiveData

官方介绍

LiveData是一种可观察的数据存储器类。

设计理念

如果你用过EventBus,RxJava。你就会知道对应的观察者模式。但是LiveData与普通的观察类不同,它是一种具有生命周期感知能力的,也就是说,它的生命周期遵从于组件(Activity, Fragment, Service)的生命周期。

代码实现

LiveData的实现也很简单,分为以下几个步骤:

1、 创建LiveData实例来指定存储某种类型的数据,一般在ViewModel中。

2、 创建Observer对象来定义onChange()方法。在onChange方法中,控制LiveData对象存储数据更改时会进行回调。

3、 使用Observe()方法,来将Observer对象附加到LiveData上。这样会使 Observer 对象订阅 LiveData 对象,以使其收到有关更改的通知。

具体实现

在进行ViewModel进行代码演示时,我们使用的是chronometer这个控件,现在我们使用简单的TextView和Timer来演示LiveData的数据观察。

1、创建LiveData和其对应的ViewModel,并且实现消息通知逻辑

在所有情况下,LiveData调用 setValue() 或 postValue() 都会触发观察者。

class LiveDataViewModel : ViewModel() {

    private val ONE_SECOND = 1000L

    //创建LiveData
    var mElaspsedTime: MutableLiveData<Long> = MutableLiveData()

    private val mInitialTime: Long = SystemClock.elapsedRealtime()

    private val mTimer: Timer = Timer()

    init {

        //每一秒回调的计时器
        mTimer.scheduleAtFixedRate(object : TimerTask() {
            override fun run() {
                val newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000
                //每秒将新计算的时间,更新变化给观察者
                mElaspsedTime.postValue(newValue)
            }

        }, ONE_SECOND, ONE_SECOND)
    }

    //ViewModel的回调,进行资源回收
    //在ViewModel 被 Finished时回调
    override fun onCleared() {
        super.onCleared()
        //关闭Timer
        mTimer.cancel()
    }

}

2、在需要观测的组件(Activity、Fragment)中取得LiveData对应的ViewModel

private lateinit var mLiveDataViewModel: LiveDataViewModel
private fun initLiveDataViewModel() {
        //和第ViewModel使用方式类似,从Provider中获取ViewModel
        mLiveDataViewModel = ViewModelProvider(this).get(LiveDataViewModel::class.java)
    }

3、创建Observer来观测LiveData的变化

private fun subscribe() {
        //创建一个Observer来观察LiveData的变化
        val liveDataObserver = object : Observer<Long> {
            override fun onChanged(newTime: Long?) {
                //onChange 中回调的Long类型,就是LiveData的变化
                val newText = resources.getString(R.string.seconds, newTime)
                //将变化的时间应用到textView,就实现了简单的定时器
                findViewById<TextView>(R.id.tv_elapsed_time).text = newText
            }
        }
        //还有最重要的一步,
        //将我们的LiveData与LifeCycleOwner和Observer绑定
        mLiveDataViewModel.mElaspsedTime.observe(this, liveDataObserver)
    }

其中,LiveData的Observe()函数需要传入两个参数,LifeCycleOwner和Observer

我们查看androidx的AppCompatActivity,会发现实现了LifeCycleOwner接口,在组件中我们只用传入Activity引用即可。再次运行程序,会发现页面中的计时器每秒都在更新,而且方向旋转并不会导致数据丢失重新开始。

  • 注意:LiveData只会当LifeCycleOwner(Acttivity、Fragment)处于活动状态时被更新。当切换到其他App时,会停止更新直到切回到当前应用。所以LiveData应该只用于订阅各自LifeCycleOwner处于Started或者Resumed

其他LiveData的使用可以参考Google官方文章

Fragment间共享ViewModel

通过上面的实例,我们知道如何订阅一个LiveData,如何利用ViewModel的特性。那么我们可以尝试下,Activity既然继承于LifeCycleOwner,我们能否用ViewModel来在Fragment间传递。

初始化Fragment

我们在Activity的视图中定义两个Fragment

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <fragment
        android:id="@+id/fragment_1"
        android:name="com.helper.lifecycledemo.shareviewmodel.ViewModelShareFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
    <fragment
        android:id="@+id/fragment_2"
        android:name="com.helper.lifecycledemo.shareviewmodel.ViewModelShareFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
</LinearLayout>

创建我们的Fragment对象,并在Fragment视图中添加一个SeekBar,我们试图在Fragment之中来共享Seekbar的进度(Progress)。

class ViewModelShareFragment : Fragment() {
		private lateinit var mSeekBar: SeekBar
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 初始化Fragment视图,
				//视图中有一个Seekbar,xml代码省略
        val view = inflater.inflate(R.layout.fragment_view_model_share, container, false)
        mSeekBar = view.findViewById(R.id.sb_seek)
        return view
    }
}

这样运行起来之后,两个Fragment中的SeekBar彼此是没有任何关系的。拖动其中一个SeekBar,另外一个也不会有改变,我们的目标是通过ViewModel,将两个SeekBar建立关系,一个SeekBar拖动,另一个SeekBar也跟着数据更新。

Let's do it!

获取共有Activity的ViewModel

我们接着创建一个ViewModel,内含一个Long类型的LiveData

class SeekBarViewModel : ViewModel() {
		//Long类型的LiveData
    var progress: MutableLiveData<Int> = MutableLiveData()
}

在Fragment初始化时,从ViewModelProvider中获取对应的ViewModel.

前面有个点我们没有提到,那就是ViewModelProvider初始化时传入的参数,它是一个ViewModelStoreOwner接口,默认的Fragment、Activity都实现了这个接口。在我们创建一个Provider时,传入的StoreOwner引用意味着我们后续的ViewModel存储是由哪个组件决定的。如果我们传入的是Fragment的引用,那么我们取出的ViewModel就是该Fragment提供。如果我们传入的是Activity的引用,那么Activity来负责ViewModel的提供。

我们前面所使用的两个Fragment都是共用的一个Activity,那初始化构造参数我们就不能传入Fragment.this。我们应该传入Activity的引用!

private lateinit var seekBarViewModel: SeekBarViewModel
//尤其重要,ViewModelProvider的参数不再是this,而是绑定Activity的引用。
seekBarViewModel = **ViewModelProvider**(requireActivity()).get(SeekBarViewModel::class.java)

随后添加Seekbar的监听:

mSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                //当SeekBar的进度改变是来自于用户,我们就通过LiveData传递数据改变
								//避免重复更新
                if (fromUser) {
                    seekBarViewModel.progress.postValue(progress)
                }
            }
...
})

最后添加LiveData的监听:

seekBarViewModel.progress.observe(requireActivity(), object : Observer<Int> {
    override fun onChanged(progress: Int?) {
       if (progress != null) {
					 //监听到LiveData的数据变化后,对SeekBar进行更新
		       mSeekBar.progress = progress
       }
    }
})

运行效果

运行到设备上拖动进度条,会发现两个进度条的进度保持一致

总结

本文介绍了在Jetpack中遇到的LiveData、ViewModel和LifeCycleOwner的使用方式的介绍。

  1. 我们需要记住ViewModel是作为数据容纳的工具,用来与LifeCycleOwner来绑定,并且在Cleared之前都将可以被我们取出使用。
  2. LiveData用于数据的监听改变,也就是实现观察者模式。
  3. LifeCycleOwner是ViewModel和LiveData的载体,我们可以自定义LifeCycleOwner,也可以使用Android官方组件(Activity、Fragment)中对LifeCycleOwner的实现。