When and why to use Android LiveData
heiyulong,公众号:Android进化之路Jetpack架构组件库-介绍与基本用法
前言
==本次主要讲解的内容:==
1、LiveData是什么
2、使用LiveData的优势&工作机制
3、LiveData如何使用它,以及setValue和postValue的区别
一、LiveData是什么
LiveData 组件是 Jetpack 新推出的基于观察者的消息订阅/分发组件,具有宿主(Activity、Fragment或 Service)生命周期感知能力,这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者,即只有处于活跃状态的观察者才能收到消息。
LiveData可以分解成如下三种特性:
基于观察者模式
LiveData是一种持有可被观察数据的类。LiveData需要一个观察者对象,一般是Observer类的具体实现。当观察者的生命周期处于STARTED或RESUMED状态时,LiveData会通知观察者数据变化。
感知生命周期
和其他可被观察的类不同的是,LiveData是有生命周期感知能力的,这意味着它可以在activities, fragments, 或者 services生命周期是活跃状态时更新这些组件。也就是说只有在生命周期处于 STARTED 或 RESUMED 这两个状态下LiveData是会通知数据变化的。
自动解除数据订阅
要想使用LiveData(或者这种有可被观察数据能力的类)就必须配合实现了LifecycleOwner的对象使用。在这种情况下,当对应的生命周期对象DESTORY时,才能移除观察者。这对Activity或者Fragment来说显得尤为重要,因为他们可以在生命周期结束的时候立刻解除对数据的订阅,从而避免内存泄漏等问题。
活跃状态:通常情况下等于 Observer 所在宿主处于 started,resumed 状态,如果使用 observeForever 注册的,则一直处于活跃状态。
说到数据的订阅分发,很容易联想到handler、EventBus等,它们是不管当前页面是否可见,只要有消息就分发,导致应用在后台页面不可见的情况下还在做一些无用的工作抢占资源,甚至引发内存泄露。
而LiveData的出现就可以很好的解决以往使用 callback 回调可能带来的空指针异常,生命周期越界导致内存泄露,后台任务抢占资源等问题。
从代码的角度看 LiveData 与传统消息分发组件的不同:
创建 LiveData 实例以存储某种类型的数据,这通常在 ViewModel 类中完成,为了便于参考,可忽略代码中的LiveData的实现形式。
public class MainActivity extends AppcompactActivity{
public void onCreate(Bundle bundle){
Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
//无论页面可见不可见,都会去执行页面刷新或IO操作等。
}
};
//1.无论当前页面是否可见,这条消息都会被分发。-消耗资源
//2.无论当前宿主是否还存活,这条消息都会被分发。-内存泄漏
handler.sendMessage(msg)
liveData.observer(this,new Observer<User>){
void onChanged(User user){
}
}
//1.减少资源占用-页面不可见时不会派发消息
//2.确保页面始终保持最新状态-页面可见时,会立刻派发最新的一条消息给所有观察者--保证页面最新状态
//3.不再需要手动处理生命周期-避免空指针
liveData.postValue(data);
}
}
二、使用LiveData的优势
确保界面符合数据状态
LiveData 遵循观察者模式。当生命周期状态发生变化时,LiveData 会通知 Observer 对象。您可以整合代码以在这些 Observer 对象中更新界面。观察者可以在每次发生更改时更新界面,而不是在每次应用数据发生更改时更新界面。
不会发生内存泄露
观察者被绑定到组件的生命周期上(Lifecycle对象),当被绑定的组件销毁(onDestroy)时,观察者会立刻自动清理自身的数据。
不因为Activity处于止stop状态而引起的崩溃
例如:当Activity处于后台状态时,是不会收到LiveData的任何事件的。
不再需要手动处理生命周期
界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,LiveData可以感知被绑定的组件的生命周期,只有在活跃状态才会通知数据变化。
实时数据刷新
当组件处于活跃状态或者从不活跃状态到活跃状态时总是能收到最新的数据,例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。
解决设备旋转Configuration Change问题
在屏幕发生旋转或者被回收再次启动而重新创建了 Activity 或 Fragment,立刻就能收到最新的数据。
数据共享
如果对应的LiveData是单例的话,就能在app的组件间分享数据。这部分详细的信息可以参考继承LiveData
三、LiveData是如何工作的?
当我们有一个LiveData对象(例如用户列表)时,我们可以以Activity或Fragment(即LifecycleOwner)作为此数据更新的观察者。与观察者模式完全相同,但要考虑生命周期状态。由于此:
我们(作为Activity或Fragment)每次数据更改时都会收到通知,而不是每次都从ViewModel请求数据(因此,UI始终会更新!)
所有这些都与生命周期有关,因此,只有在Activity(或任何其他LifecycleOwner)处于STARTED或RESUMED处于状态时,我们才会进行注册以观察这些更改,然后LiveData才会发出任何项(因此,由于视图不存在,不会再发生内存泄漏和NullPointerException!)
四、LiveData如何使用它?
使用 LiveDaata 之前需要先添加依赖:
dependencies {
// ViewModel依赖(不用可不引入)
api "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
// LiveData依赖
api "androidx.lifecycle:lifecycle-livedata:2.2.0"
1、MutableLiveData
使用 LiveData 的做消息分发的时候,需要使用MutableLiveData这个子类。该类用到设计模式中的单一开闭原则,只有拿到 MutableLiveData 对象才可以发送消息,LiveData 对象只能接收消息,避免拿到 LiveData 对象时既能发消息也能收消息导致使用混乱。
public class MutableLiveData<T> extends LiveData<T> {
//如果在子线程发送消息必须用postValue
@Override
public void postValue(T value) {
super.postValue(value);
}
//主线程发送消息
@Override
public void setValue(T value) {
super.setValue(value);
}
}
使用 LiveData 的四个基本步骤
为了在项目中使用 LiveData,您只需要按照以下四个步骤操作:
首先,要持有数据,您需要在 ViewModel 中创建 LiveData 实例。
创建 LiveData 的实例后,需要使用 和 等方法在 LiveData 中设置数据。setValuepostValue
之后,您需要返回 LiveData,以便某些观察者可以在"Activity"或"Fragment"等视图中观察它。
最后,您需要借助方法观察视图中的数据。在观察点中,您需要定义要对数据更改执行的 UI 中的所有更改。observe
按照上述四个步骤执行后,每当 LiveData 中存储的数据发生更改时,如果与 LiveData 关联的所有观察者处于STARTED或RESUMED状态时,都会收到通知。
按照上面四个步骤编写代码:
第 1 步:创建 LiveData 的实例
在这里,我们将使用将用于更新 的 。MutableLiveData name
private MutableLiveData<String> name;
第 2 步:在实时数据中设置数据
在这里,我们需要更改/更新 LiveData 中的数据。MutableLiveData 公开公开两种方法,即setValue & postValue 并在 LiveData 中设置数据
因此,以下是在使用setValue & postValue之前必须思考的一些他们的区别:
下面是使用setValue 和postValue 更新 LiveData 的代码:
viewModel.name.setValue(/** updated data **/);
或者
viewModel.name.postValue(/** updated data **/);
第 3 步:返回实时数据
设置数据后,您需要返回 LiveData,以便在某些视图中可以观察它。
public LiveData<String> getName(){
return name;
}
第 4 步:观察某些视图中的数据
最后,需要使用观察者观察某些视图中的数据。这里需要指出的是,当观察者从非活动状态到活动状态时,更新的数据将发送给它。
viewModel.getName().observe(this, name -> {
//do some UI updation or other tasks
}
LiveData setValue和postValue的区别
上面提到需要更新数据的时候,LiveData提供了两种更新数据的方式:
setValue(T value)
postValue(T value)
setValue()只能在主线程中调用,postValue()可以在任何线程中调用。
setValue()
setValeu()源码如下:
/**
* Sets the value. If there are active observers, the value will be dispatched to them.
* <p>
* This method must be called from the main thread. If you need set a value from a background
* thread, you can use {@link #postValue(Object)}
*
* @param value The new value
*/
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
看注释可以看出:
setValue()这个方法必须在主线程中调用,如果你需要在后台线程中设置value,请参考 #postValue(Object)
查看里面assertMainThread("setValue")方法:
static void assertMainThread(String methodName) {
if (!ArchTaskExecutor.getInstance().isMainThread()) {
throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
+ " thread");
}
}
首先调用 assertMainThread() 方法来判断当前线程是否为主线程(这里他通过一个ArchTaskExecutor的单例类来实现),如果不是主线程,直接抛异常提醒程序员。
如果是在主线程中调用该方法,自加加一个version,来说明值发生了变化。
通过mData = value;再把新的值保存起来。
通过dispatchingValue(null);更新value
注意:setValue()执行几次,观察者将收到有关更新数据的通知也是同样的次数
postValue
postValue()源码如下:
/**
* Posts a task to a main thread to set the given value. So if you have a following code
* executed in the main thread:
* <pre class="prettyprint">
* liveData.postValue("a");
* liveData.setValue("b");
* </pre>
* The value "b" would be set at first and later the main thread would override it with
* the value "a".
* <p>
* If you called this method multiple times before a main thread executed a posted task, only
* the last value would be dispatched.
*
* @param value The new value
*/
protected void postValue(T value) {
//第一步:定义一个 postTask 的布尔值,判断是否要更新。
boolean postTask;
//第二步:加个同步锁,因为可能存在多个子线程同时调用 .postValue() 的情况。
synchronized (mDataLock) {
//第三步:通过判断更新的值是否发生变化来对postTask赋值,并且将value赋值给 mPendingData
postTask = mPendingData == NOT_SET;//
mPendingData = value;
}
if (!postTask) {
return;
}
//第四步:通过ArchTaskExecutor进行更新
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
1、如果在主线程,注意前提在主线程通过setValue()和postValue()同时给liveData设置值
liveData.postValue("a");
liveData.setValue("b");
首先b值先被设置成功,然后a值就会覆盖b值,值b被值a覆盖。
2、在主线程多次执行postValue ,观察者接收通知的时间取决于主线程的执行。
例如,如果在主线程执行调用postValue 4 次,则观察者将只收到一次通知,并且最新值将调度到主线程,其余值将被丢弃,只有最后一个值能够被分发(onChanged()被调用)。
liveData.postValue("a");
liveData.postValue("b");
liveData.postValue("c");
liveData.postValue("d");
如上代码, onChanged()回调中,只有d值被监听到。参看源码中注释第三步:
在同步锁内,通过判断更新的值是否发生变化来对postTask赋值,并且将value赋值给 mPendingData
mPendingData == NOT_SET第一次一定是返回true,之后都是返回false,然后到这个值更新完毕之前的一瞬间会调用mPendingData=NOT_SET,这也是为什么多次调用 postValue()只有最后一个值才有效的原因
2、Transformations.map()
如果我们想在LiveData上进行一些更改,然后再将其从ViewModel中公开,则可以使用Transformations来实现:
如果我们不需要整个User对象,而只需要它们的名称,则可以使用map():
MutableLiveData<User> userLiveData = new MutableLiveData<>();
//转换
LiveData<String> userNamesLiveData = Transformations
.map(userLiveData, user -> {user.name});
userNamesLiveData.observe(this,observer)
Observer observer = new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
//update UI
textName.setText(s);
}
3、MediatorLiveData
我们还可以使用MediatorLiveData将多个LiveData可观察对象合并到一个源中。例如,如果我们想合并数据库和网络中的数据,我们可以这样进行:
LiveData<List<User>> usersFromDatabase = new MutableLiveData();
LiveData<List<User>> usersFromNetwork = new MutableLiveData();
MediatorLiveData<List<User>> usersLiveData = new MediatorLiveData<>();
usersLiveData.addSource(usersFromDatabase, observer);
usersLiveData.addSource(usersFromNetwork, observer);
Observer observer = new Observer<List<User>>() {
@Override
public void onChanged(@Nullable List<User> s) {
//update UI
//一旦usersFromDatabase或usersFromNetwork发送了新的数据 ,observer便能观察的到,以便 统一处理更新UI
}
< END >
【Android进化之路】
微信扫描二维码,关注我的公众号。