感知生命周期的数据 -- LiveData
感知生命周期的数据 -- LiveData
零. 前言
上篇文章《万物基于Lifecycle》 介绍了整个Lifecycle体系的基石,今天这篇文章咱们来看看Jetpack给我们带来的活着的数据——LiveData。
大纲
- LiveData 是什么?
- 为什么要用LiveData?
- How to use LiveData?
- LiveData的生命感知能力从何而来,是如何与Lifecycle结合的?
一. LiveData 是什么?
LiveData 简单来说,就是普通数据对象的一个包装类,这个包装类中帮助你主动管理了数据的版本号,基于观察者模式,让普通的数据对象能够感知所属宿主(Activity、Fragment)的生命周期。这种感知能力就能够保证只有宿主活跃(Resumed、Started)时,数据的观察者才能受到数据变化的消息。
上面这短短的一段话,却有着重大的意义,举一个case:
有一个页面需要加载一个列表,我们需要在后台线程去服务器请求对应的数据,请求数据成功并经过解析后post消息通知UI,UI再渲染请求来的数据。等等!假若此时的网络很慢,而刚好用户此时按home键退出了应用,那么在此时UI是不应该被渲染的,而是应该等用户重新进入应用时才开始渲染。
从下面的演示代码就诠释了上面的说所的case
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.不再需要手动处理生命周期---避免NPE
//4.可以打造一款不用反注册,不会内存泄漏的消息总线---取代eventbus
liveData.postValue(data);
}
}
有人说,我可以在处理消息时,根据当前页面时是否可见来具体处理对应逻辑。是的,没错,确实可以这样,就像下面这样。
Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
if (isActivityValid()) {
// updateUI...
} else {
// dosomething...
}
}
};
我再拿上面的例子说一下这种问题:
- 需要自行判断宿主活跃状态,防止生命周期越界。
- 如果Activity不可见,此时不更新UI,那么就需要复写onResume方法去更新UI,手工管理生命周期,增加了代码的复杂性。
二. 为什么要使用LiveData?
上面的例子已经很好地诠释了LiveData的强大,当然不仅限于此,它的优势如下:
确保界面符合数据状态
LiveData 遵循观察者模式。当生命周期状态发生变化时,LiveData 会通知
Observer
对象。观察者可以在onChanged事件时更新界面,而不是在每次数据发生更改时立即更新界面。不会发生内存泄漏
观察者会绑定到
Lifecycle
对象,并在其关联的生命周期遭到销毁后进行自我清理。不会因 Activity 停止而导致崩溃
如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。
不再需要手动处理生命周期
界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。
数据始终保持最新状态
如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。
适当的配置更改
如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。
共享资源
可以使用单一实例模式扩展
LiveData
对象以封装系统服务,以便在应用中共享它们。LiveData
对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察LiveData
对象支持黏性事件的分发
即先发送一条数据,后注册一个观察者,默认是能够收到之前发送的那条数据的
三. How to use LiveData ?
step1: 添加依赖:
step2: 在ViewModel中创建 LiveData
实例 (ViewModel组件会在下期讲到,将LiveData存储至viewModel中,是为了符合MVVM架构思想,V层仅负责展示,而VM层负责数据逻辑)
public class ViewModelTest extends AndroidViewModel {
public final MutableLiveData<String> name = new MutableLiveData<>();
...
}
step3: 在Activity或Fragment中对LiveData进行添加观察者进行监听
public class ActivityTest extends AppCompatActivity {
...
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
viewModel.name.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String name) {
// dosomething...
}
});
}
}
我们可以根据LiveData值的变化来做对应的事情,且不用担心生命周期越界的问题。
LiveData核心方法
方法名 | 作用 |
---|---|
observe(LifecycleOwner owner,Observer observer) | 注册和宿主生命周期关联的观察者 |
observeForever(Observer observer) | 注册观察者,不会反注册,需自行维护 |
setValue(T data) | 发送数据,没有活跃的观察者时不分发。只能在主线程。 |
postValue(T data) | 和setValue一样。不受线程环境限制, |
onActive | 当且仅当有一个活跃的观察者时会触发 |
inActive | 不存在活跃的观察者时会触发 |
LiveData的衍生类及功能
MutableLiveData
该类十分简单,主要开放了LiveData的发消息接口
public class MutableLiveData<T> extends LiveData<T> {
@Override
public void postValue(T value) {
super.postValue(value);
} @Override
public void setValue(T value) {
super.setValue(value);
}
}
设计初衷:考虑单一开闭原则,LiveData只能接受消息,避免拿到LiveData对象既能发消息也能收消息的混乱使用。
MediatorLiveData
合并多个LiveData, 即一对多统一观察,一个经典的场景是:在向服务器请求数据时,优先展示本地数据库的数据,然后由请求的响应决定是否要更新数据库,如下图所示:
// ResultType: Type for the Resource data.
// RequestType: Type for the API response.
public abstract class NetworkBoundResource<ResultType, RequestType> {
// MediatorLiveData 数据组合者
private final MediatorLiveData<Resource<ResultType>> result = new MediatorLiveData<>();
private Executor executor;
@MainThread
protected NetworkBoundResource(Executor mExecutor) {
this.executor = mExecutor;
// 首先初始化一个Loading的status 空result
result.setValue(Resource.loading(null));
// 然后从数据库中获取持久化数据
LiveData<ResultType> dbSource = loadFromDb();
// 数据组合者监听数据库中的数据
result.addSource(dbSource, data -> {
// dbSource第一次回调,用来判断数据有效期,此时取消监听
result.removeSource(dbSource);
// 业务自行定义是否需要fetch最新的数据
if (shouldFetch(data)) {
fetchFromNetwork(dbSource);
} else {
// 数据有效,重新观察一次,观察者会立马收到一次回调(LiveData粘性事件机制)
result.addSource(dbSource, newData -> result.setValue(Resource.success(newData)));
}
});
} private void fetchFromNetwork(final LiveData<ResultType> dbSource) {
LiveData<ApiResponse<RequestType>> apiResponse = createCall();
// 这里数据虽无效,但是可以先给UI展示
result.addSource(dbSource, newData -> setValue(Resource.loading(newData)));
result.addSource(apiResponse, response -> {
result.removeSource(apiResponse);
result.removeSource(dbSource); if (response != null) {
if (response.isSuccessful()) {
executor.execute(() -> {
saveCallResult(processResponse(response));
executor.execute(() ->
// 这里我们拿到的最新的数据需要主动通知监听者,以拿到从服务端拿到的最新数据
result.addSource(loadFromDb(),
newData -> setValue(Resource.success(newData)))
);
});
} else {
onFetchFailed();
result.addSource(dbSource,
newData -> setValue(Resource.error(response.errorMessage, newData)));
}
} else {
result.addSource(dbSource,
newData -> setValue(Resource.error("Request failed, server didn't response", newData)));
}
});
}
}
上面的例子MediatorLiveData同时监听了数据库中的LiveData和服务端的LiveData
Transformations
这是一个数据转化工具类,共两个主要方法:
静态转化 -- map(@NonNull LiveData source, @NonNull final Function<X, Y> mapFunction)
MutableLiveData<Integer> data = new MutableLiveData<>(); //数据转换
LiveData<String> transformData = Transformations.map(data, input -> String.valueOf(input));
//使用转换后生成的transformData去观察数据
transformData.observe( this, output -> { }); //使用原始的livedata发送数据
data.setValue(10);
动态转化 -- LiveData switchMap(
@NonNull LiveData source,
@NonNull final Function<X, LiveData> switchMapFunction)
MutableLiveData userIdLiveData = ...;
// 用户数据和用户id紧密相关,当我们改变userId的liveData的同时还会主动通知userLiveData更新
LiveData userLiveData = Transformations.switchMap(userIdLiveData, id ->
repository.getUserById(id)); void setUserId(String userId) {
this.userIdLiveData.setValue(userId);
} // 不要像下面这么做
private LiveData getUserLiveData(String userId) {
// DON'T DO THIS
return repository.getUserById(userId);
}
四. LiveData的实现机制
LiveData注册观察者触发消息分发流程:
- observe 注册时,可以主动跟宿主生命周期绑定,不用反注册:
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
//1. 首先来个断言,这个方法只能在主线程调用,observeForever也是。
assertMainThread("observe");
//2.其次把注册进来的observer包装成 一个具有生命周边边界的观察者
//它能监听宿主被销毁的事件,从而主动的把自己反注册,避免内存泄漏
//此时观察者是否处于活跃状态就等于宿主是否可见
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
//3.接着会判断该观察是否已经注册过了,如果是则抛异常,所以要注意,不允许重复注册
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
//4.这一步才是关键
//利用Lifecycle,把观察者注册到进去,才能监听到宿主生命周期状态的变化,对不对?
//根据Lifecycle文章中的分析,一旦一个新的观察者被添加,Lifecycle也会同步它的状态和宿主一致对不对?此时会触发观察者的onStateChanged方法
owner.getLifecycle().addObserver(wrapper);
}
- LifecycleBoundObserver 监听宿主的生命周期(这里我们还记得之前在Lifecycle解析中提到addObserver时也会对observer进行包装,这时一样的),并且宿主不可见时不分发任何数据:
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
super(observer);
}
@Override
boolean shouldBeActive() {
//使用observer方法注册的观察者都会被包装成LifecycleBoundObserver
//观察者是否活跃就等于宿主 的状态是否大于等于STARTED,
//如果页面当前不可见,你发送了一条消息,此时是不会被分发的,可以避免后台任务抢占资源,当页面恢复可见才会分发。
//注意:如果使用observerForever注册的观察者,
//会被包装成AlwaysActiveObserver,它的shouldBeActive一致返回true.即便在页面不可见也能收到数据
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
//在这里如果监听到宿主被销毁了,则主动地把自己从livedata的观察者中移除掉
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
//否则说明宿主的状态发生了变化,此时会判断宿主是否处于活跃状态
activeStateChanged(shouldBeActive());
}
}
- ObserverWrapper 状态变更后,如果观察者处于活跃状态会触发数据的分发流程:
abstract class ObserverWrapper{
final Observer<? super T> mObserver;
boolean mActive;
int mLastVersion = START_VERSION//这里就等于-1,没有主动和LiveData的mVersion对齐,为黏性事件埋下了伏笔
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
//更改观察者的状态
mActive = newActive;
boolean wasInactive = LiveData.this.mActiveCount == 0;
//如果此时有且只有一个活跃的观察者则触发onActive
LiveData.this.mActiveCount += mActive ? 1 : -1;
if (wasInactive && mActive) {
onActive();
}
//没有任何一个活跃的观察者则触发onInactive
//利用这个方法被触发的时机,可以做很多事,比如懒加载,资源释放等
if (LiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
//如果此时观察者处于活跃状态,下面就开始分发数据了
//请注意,这里传递了this = observer
if (mActive) {
dispatchingValue(this);
}
}
}
- dispatchingValue 数据分发流程控制:
void dispatchingValue(@Nullable ObserverWrapper initiator) {
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
//如果传递的观察者不为空,则把数据分发给他自己。这个流程是新注册观察者的时候会被触发
considerNotify(initiator);
initiator = null;
} else {
//否则遍历集合中所有已注册的的观察者,逐个调用considerNotify,分发数据
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
- considerNotify 数据真正分发的地方,需要满足三个套件:
private void considerNotify(ObserverWrapper observer) {
//观察者当前状态不活跃不分发
if (!observer.mActive) {
return;
}
//观察者所在宿主是否处于活跃状态,否则不分发,并且更改观察者的状态为false
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
//此处判断观察者接收消息的次数是否大于等于 发送消息的次数
//但是observer被创建之初verison=-1
//如果此时LiveData已经发送过数据了。这里就不满足了,就出现黏性事件了,后注册的观察者收到了前面发送的消息。
if (observer.mLastVersion >= mVersion) {
return;
}
//每分发一次消息,则把观察者和LiveData的version对齐,防止重复发送
observer.mLastVersion = mVersion;
//最后的数据传递
observer.mObserver.onChanged((T) mData);
}
普通消息分发流程。即调用 postValue,setValue 才会触发消息的分发:
五. 总结
Android开发大部分主要的工作就是从服务器获取数据,将其转为渲染为UI展示给用户。本质上我们所做的逻辑都是“数据驱动”,所有的View都是数据的一种状态,数据映射的过程中我们需要去考虑生命周期的限制--即数据的活跃性。LiveData结合Lifecycle帮我们规范了这个流程,完美诠释了observer、lifecycle-aware、data holder 这个铁三角,开发者在遵循这个开发流程的过程中,便完成了UI -> ViewModel -> Data的单项依赖。
感知生命周期的数据 -- LiveData的更多相关文章
- "过期不候"--具备生命周期的数据的技术实现方案
"过期不候"--具备生命周期的数据的技术实现方案 1 引言 本文可以作为之前的一个 原理性文章 对应的 技术实现部分 . 此处给出其上文的直达电梯: http://www.cn ...
- Vuejs——Vue生命周期,数据,手动挂载,指令,过滤器
版权声明:出处http://blog.csdn.net/qq20004604 目录(?)[+] 原教程: http://cn.vuejs.org/guide/instance.html htt ...
- 【基础篇】activity生命周期及数据保存
常见的Android 的界面,均采用Activity+view的形式显示的,一提到Activity,立即就能联想到Activity的生命周期与状态的保存. 下面先从Activity的生命周期开始说起 ...
- Vuejs——(2)Vue生命周期,数据,手动挂载,指令,过滤器
(八)传入的数据绑定 先创建一个对象(假如是obj),然后将他传入Vue实例中,作为data属性的值,那么 ①obj的值的变化,将影响Vue实例中的值的变化: ②相反一样: ③可以在Vue实例外面操纵 ...
- 生命周期感知 Lifecycle
奉上翻译原文地址: 处理生命周期 :翻译过程中加上了自己的一点理解.理解不对的地方直接评论就好. 生命周期感知组件可以感知其他组件的生命周期,例如 Activity,Fragment等,以便于在组件的 ...
- Android全面解析之Activity生命周期
前言 很高兴遇见你~ 欢迎阅读我的文章. 关于Activity生命周期的文章,网络上真的很多,有很多的博客也都讲得相当不错,可见Activity的重要性是非常高的.事实上,我猜测每个android开发 ...
- Activity组件的生命周期
一.Activiy组件的三个状态: 1.前台状态(active) : 在屏幕的最上层,页面获得焦点,可以响应用户的操作2.可视状态(paused) : 不能与用户交互,但是还存在于可视区域内,它依然存 ...
- 「Vue」vue生命周期
Vue的生命周期 beforeCreate---created---beforeMount---mounted---(beforeupdate---updated :数据有更新时才会执行)---bef ...
- android 学习随笔十五(Activity的生命周期与摧毁时返回数据 )
1.Activity的生命周期 onCreate:创建时调用 onStart:在屏幕上可见,但是还没有获得焦点 onResume:可见并且获得焦点 onPause:可见,但是失去焦点 onStop:不 ...
随机推荐
- C#LeetCode刷题之#559-N叉树的最大深度(Maximum Depth of N-ary Tree)
问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4088 访问. 给定一个 N 叉树,找到其最大深度. 最大深度是指 ...
- Js~对键值对操作
键值对主要是面向对象语言里的字典,或者叫哈希表,它通过键(key)可以直接访问到值(value),所以它查找的时间复杂度是O(1),即一次查找即可找到目标:在.net里有Dictionary,而在ja ...
- python设计模式之原型模式
python设计模式之原型模式 对于原型模式而言,其中最主要的部分就是关于一个对象的复制,其中就包含两个方面:1.浅复制:2.深复制.具体的区别请看我相关的随笔.这里简略的说明一下,浅复制就等于对 ...
- 导出Excel文件(项目中会遇到很多将一些数据导出Excel或者et)
最近在项目中,遇到一些需求,就是将数据导出来,以Excel文件为主:就自己简单的做一些demo:供初学者来学习: // 定义一个保存文件的路径位置 SaveFileDialog dlgPath = n ...
- 根据pid获得路径 2006-10-25 19:28
这是编写kill process时用到的 BOOL GetProcessModule(DWORD dwPID, DWORD dwModuleID, LPMODULEENTRY32 lpMe32, DW ...
- 聊一聊mycat数据库集群系列之双主双重实现
最近在梳理数据库集群的相关操作,现在花点时间整理一下关于mysql数据库集群的操作总结,恰好你又在看这一块,供一份参考.本次系列终结大概包括以下内容:多数据库安装.mycat部署安装.数据库之读写分离 ...
- JavaScript学习系列博客_38_JavaScript 事件
事件(Event) - 事件指的是用户和浏览器之间的交互行为.比如:点击按钮.关闭窗口.鼠标移动.... - 我们可以为事件来绑定回调函数来响应事件. - 绑定事件的方式: 1.可以在标签的事件属性中 ...
- spss如何把多个指标合并成一个变量?
把多个指标合并成一个变量,通常有两种做法: 一.计算平均值 针对问卷量表数据,同时几个题表示一个维度.比如想要将“我在工作中能获得成就感”.“我可以在工作中发挥个人的才能”这两题合并成一个维度(影响因 ...
- Hadoop的SecondaryNameNode的作用是什么?
为节省篇幅,将SecondaryNameNode简称SNN,NameNode简称NN. NN与fsimage.edits文件 NN负责管理HDFS中所有的元数据,包括但不限于文件/目录结构.文件权限. ...
- CAOZ:百度搜索引擎的人工干预的看法
http://www.wocaoseo.com/thread-247-1-1.html 百度确有人工干预的机制,但是这个机制与互联网上的传闻相差很大,人工干预的范围其实是非常小的,特别恶性的搜索结果, ...