💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[toc] # 1. 前言 官网地址:[https://developer.android.google.cn/topic/libraries/architecture/livedata](https://developer.android.google.cn/topic/libraries/architecture/livedata) 其实在使用`ViewModel`和`LifeCycle`的时候,我们已经看见了`LiveData`这个组件。在这篇博客中将继续来了解这个组件。其特点: - `LiveData`是一种**可观察的数据存储器类**; - 与常规的可观察类不同,`LiveData`**具有生命周期感知能力**。其感知能力遵循其他组件(如 `Activity`、`Fragment` 或 `Service`)的生命周期; - 感知能力可确保`LiveData`**仅更新处于活跃生命周期状态**的应用组件观察者;活跃的观察者对象声明周期处于`STARTED `或 `RESUMED` 状态; - `LiveData`只会将更新通知给**活跃的观察者**,非活跃观察者不会收到更改通知; - 当观察者组件的`Lifecycle`对象的状态变为 `DESTROYED` 时,便可移除此观察者,不必担心泄露(当 `Activity `和` Fragment `的生命周期被销毁时,系统会立即退订它们)。 不妨再次看下这个图: ![](https://img.kancloud.cn/6c/3b/6c3b2e3708be0b1edf21103dc9259633_1068x424.png) ## 1.1 关于观察者模式 我们知道观察者模式,在`Java`中提供了`Observable`和`Observer`来定义被观察者和观察者对象。然后对于可观察对象可以设置观察者,来监听其变化。而在`Android`的`Lifecycle`包中提供了对应的`LifecycleOwner`和`LifecycleObserver`,而在`Activity`、`Fragment` 或 `Service`等组件中已经实现了`LifecycleOwner`接口,我们在使用的时候,只需要实现`LifecycleObserver`来标识类是一个观察者,以注册观察。在之前的博客:[【Android Jetpack】LifeCycle](https://mengfou.blog.csdn.net/article/details/124254857)一文中也曾提到过通过: ```kotlin lifecycle.addObserver(myLocationObserver) ``` 来为`lifecycle`来设置一个观察者对象。类似的,前面提到了:`LiveData`也是一种可观察的数据存储器类。故而也可以设置观察对象,比如: ~~~kotlin // viewModel.get() 获取的为LiveData对象 viewModel.get().observe(this, object : androidx.lifecycle.Observer<Int>{ override fun onChanged(value: Int?) { livedataText.text = viewModel.getValue().toString() } }) ~~~ # 2. LiveData 的优势 - 确保界面符合数据状态;`LiveData` 遵循观察者模式。当底层数据发生变化时,`LiveData` 会通知 `Observer` 对象。您可以整合代码以在这些 `Observer` 对象中更新界面。 - 不会发生内存泄漏;观察者会绑定到 `Lifecycle` 对象,并在其关联的生命周期遭到销毁后进行自我清理。 - 不会因 `Activity` 停止而导致崩溃;如果观察者的生命周期处于非活跃状态,则它不会接收任何 `LiveData` 事件。 - 不再需要手动处理生命周期; - 共享资源;可以使用单例模式扩展 `LiveData` 对象以封装系统服务,以便在应用中共享它们。`LiveData` 对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察 `LiveData` 对象。 更加通俗来说,`LiveData`是一个持有`Activity`、`Fragment`生命周期的数据容器。当数据源发生改变的时候,可以通知观察者。 ## 2.1 案例一:计时器效果 在`Activity`中定义一个文本,在该控件上每秒自动加一操作。且屏蔽设备配置发生改变后不影响其状态。比如布局文件为`ConstraintLayout`下放置一个居中的`TextView`。 ### 2.1.1 实现方式一:ViewModel+LiveData - 因为需要屏蔽设备配置发生改变的影响,故而这里考虑直接使用`ViewModel`; - 考虑代码解耦,这里使用`LiveData`来设置观察者,也即是我们需要在`ViewModel`中设置数据类型为`LiveData`; 自定义的`ViewModel`如下: ~~~kotlin class MyNumberViewModel : ViewModel() { private var number: MutableLiveData<Int> = MutableLiveData(0) fun set(value: Int) { number.value = value } // 工作线程需要使用Post fun post(value: Int) { number.postValue(value) } fun get(): LiveData<Int> { return number } fun getValue(): Int{ return number.value?:0 } } ~~~ 因为我们需要使用`LiveData`对象来注册观察者对象,故而这里`get()`方法返回的是`LiveData`对象。且因为需要开启定时任务,故而可以在工作线程完成,所以提供了`post`方法,然后在`Activity`中进行设置: ~~~kotlin class LiveDataActivity : AppCompatActivity() { private lateinit var viewModel: MyNumberViewModel private val livedataText: TextView by lazy { findViewById(R.id.livedata_text)} override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_live_data) // 获取到ViewModel viewModel = ViewModelProvider( this, ViewModelProvider.NewInstanceFactory() ).get(MyNumberViewModel::class.java) // 为TextView设置数据 livedataText.text = viewModel.get().toString() // 开始定时任务 startTimer() // 注册一个观察者,监听到ViewModel中数据的变化,如果变化执行onChanged()方法 // viewModel.get() 获取的为LiveData对象 viewModel.get().observe(this, object : androidx.lifecycle.Observer<Int>{ override fun onChanged(value: Int?) { livedataText.text = viewModel.getValue().toString() } }) } // 定时任务 private fun startTimer(){ Timer().schedule( object : TimerTask() { override fun run() { viewModel.post(viewModel.getValue() + 1) } }, 1000, 1000, ) } } ~~~ 运行即可看见预想的效果。看完上述案例代码之后是否会有疑惑?肯定是有的,因为这里是`LiveData`的案例,却引入了`ViewModel`,且将`LiveData`放入了自定义的`ViewModel`类中,为什么需要这么做?`LiveData`对象本身就可以被观察,为什么还需要放置到`ViewModel`中? 答:其实在刚开始的要求部分我已经做了引导,需要做到**屏蔽设备配置发生改变后不影响其状态**,而很方便的`ViewModel`就可以轻松做到这一点。如果`LiveData`实例与特定的 `Activity` 或 `Fragment` 实例并没有分离开,那么在 `Activity` 或 `Fragment` 经历`onDestory`的时候,`LiveData`中的数据也就没了。 且上述程序存在一个致命`Bug`,由于每次屏幕旋转都会执行`onCreate`,故而会开启多个定时任务,故而这里是不符合题意的。当然,可以加上标志处理,这里不再解决。 ### 2.1.2 实现方式二:configChange 当然,上述使用`ViewModel`只是一种方式,也可以配置清单文件,设置`onConfigChange`属性,这里来尝试写一下: ~~~xml <activity android:name=".LiveDataActivity" android:configChanges="orientation|screenSize" android:exported="true"> ~~~ 然后将`LiveData`直接定义在`Activity`中: ~~~kotlin class LiveDataActivity : AppCompatActivity() { private val livedataText: TextView by lazy { findViewById(R.id.livedata_text) } private val liveData: MutableLiveData<Int> by lazy { MutableLiveData<Int>(0) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_live_data) // 为TextView设置数据 livedataText.text = liveData.value.toString() // 开始定时任务 startTimer() // 注册一个观察者,监听到ViewModel中数据的变化,如果变化执行onChanged()方法 // viewModel.get() 获取的为LiveData对象 liveData.observe(this) { livedataText.text = liveData.value.toString() } } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) Log.e("TAG", "onConfigurationChanged: ") } // 定时任务 private fun startTimer() { Timer().schedule( object : TimerTask() { override fun run() { liveData.postValue((liveData.value ?: 0) + 1) Log.e("TAG", "run: ${liveData.value ?: 0}", ) } }, 1000, 1000, ) } } ~~~ ## 2.2 案例二: