首先介绍一下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的使用方式的介绍。
- 我们需要记住ViewModel是作为数据容纳的工具,用来与LifeCycleOwner来绑定,并且在Cleared之前都将可以被我们取出使用。
- LiveData用于数据的监听改变,也就是实现观察者模式。
- LifeCycleOwner是ViewModel和LiveData的载体,我们可以自定义LifeCycleOwner,也可以使用Android官方组件(Activity、Fragment)中对LifeCycleOwner的实现。