感知生命周期的数据 -- 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...
}
}
};

我再拿上面的例子说一下这种问题:

  1. 需要自行判断宿主活跃状态,防止生命周期越界。
  2. 如果Activity不可见,此时不更新UI,那么就需要复写onResume方法去更新UI,手工管理生命周期,增加了代码的复杂性。

二. 为什么要使用LiveData?

上面的例子已经很好地诠释了LiveData的强大,当然不仅限于此,它的优势如下:

  1. 确保界面符合数据状态

    LiveData 遵循观察者模式。当生命周期状态发生变化时,LiveData 会通知 Observer 对象。观察者可以在onChanged事件时更新界面,而不是在每次数据发生更改时立即更新界面。

  2. 不会发生内存泄漏

    观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁后进行自我清理。

  3. 不会因 Activity 停止而导致崩溃

    如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。

  4. 不再需要手动处理生命周期

    界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。

  5. 数据始终保持最新状态

    如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。

  6. 适当的配置更改

    如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。

  7. 共享资源

    可以使用单一实例模式扩展 LiveData 对象以封装系统服务,以便在应用中共享它们。LiveData 对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察 LiveData 对象

  8. 支持黏性事件的分发

    即先发送一条数据,后注册一个观察者,默认是能够收到之前发送的那条数据的

三. 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

    这是一个数据转化工具类,共两个主要方法:

    1. 静态转化 -- 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);
    2. 动态转化 -- 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注册观察者触发消息分发流程:

  1. 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);
}
  1. 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());
}
}
  1. 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);
}
}
}
  1. 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;
}
  1. 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. "过期不候"--具备生命周期的数据的技术实现方案

    "过期不候"--具备生命周期的数据的技术实现方案 1   引言 本文可以作为之前的一个 原理性文章 对应的 技术实现部分 . 此处给出其上文的直达电梯: http://www.cn ...

  2. Vuejs——Vue生命周期,数据,手动挂载,指令,过滤器

    版权声明:出处http://blog.csdn.net/qq20004604   目录(?)[+]   原教程: http://cn.vuejs.org/guide/instance.html htt ...

  3. 【基础篇】activity生命周期及数据保存

    常见的Android 的界面,均采用Activity+view的形式显示的,一提到Activity,立即就能联想到Activity的生命周期与状态的保存. 下面先从Activity的生命周期开始说起  ...

  4. Vuejs——(2)Vue生命周期,数据,手动挂载,指令,过滤器

    (八)传入的数据绑定 先创建一个对象(假如是obj),然后将他传入Vue实例中,作为data属性的值,那么 ①obj的值的变化,将影响Vue实例中的值的变化: ②相反一样: ③可以在Vue实例外面操纵 ...

  5. 生命周期感知 Lifecycle

    奉上翻译原文地址: 处理生命周期 :翻译过程中加上了自己的一点理解.理解不对的地方直接评论就好. 生命周期感知组件可以感知其他组件的生命周期,例如 Activity,Fragment等,以便于在组件的 ...

  6. Android全面解析之Activity生命周期

    前言 很高兴遇见你~ 欢迎阅读我的文章. 关于Activity生命周期的文章,网络上真的很多,有很多的博客也都讲得相当不错,可见Activity的重要性是非常高的.事实上,我猜测每个android开发 ...

  7. Activity组件的生命周期

    一.Activiy组件的三个状态: 1.前台状态(active) : 在屏幕的最上层,页面获得焦点,可以响应用户的操作2.可视状态(paused) : 不能与用户交互,但是还存在于可视区域内,它依然存 ...

  8. 「Vue」vue生命周期

    Vue的生命周期 beforeCreate---created---beforeMount---mounted---(beforeupdate---updated :数据有更新时才会执行)---bef ...

  9. android 学习随笔十五(Activity的生命周期与摧毁时返回数据 )

    1.Activity的生命周期 onCreate:创建时调用 onStart:在屏幕上可见,但是还没有获得焦点 onResume:可见并且获得焦点 onPause:可见,但是失去焦点 onStop:不 ...

随机推荐

  1. C#LeetCode刷题之#559-N叉树的最大深度​​​​​​​(Maximum Depth of N-ary Tree)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4088 访问. 给定一个 N 叉树,找到其最大深度. 最大深度是指 ...

  2. Js~对键值对操作

    键值对主要是面向对象语言里的字典,或者叫哈希表,它通过键(key)可以直接访问到值(value),所以它查找的时间复杂度是O(1),即一次查找即可找到目标:在.net里有Dictionary,而在ja ...

  3. python设计模式之原型模式

    python设计模式之原型模式 ​ 对于原型模式而言,其中最主要的部分就是关于一个对象的复制,其中就包含两个方面:1.浅复制:2.深复制.具体的区别请看我相关的随笔.这里简略的说明一下,浅复制就等于对 ...

  4. 导出Excel文件(项目中会遇到很多将一些数据导出Excel或者et)

    最近在项目中,遇到一些需求,就是将数据导出来,以Excel文件为主:就自己简单的做一些demo:供初学者来学习: // 定义一个保存文件的路径位置 SaveFileDialog dlgPath = n ...

  5. 根据pid获得路径 2006-10-25 19:28

    这是编写kill process时用到的 BOOL GetProcessModule(DWORD dwPID, DWORD dwModuleID, LPMODULEENTRY32 lpMe32, DW ...

  6. 聊一聊mycat数据库集群系列之双主双重实现

    最近在梳理数据库集群的相关操作,现在花点时间整理一下关于mysql数据库集群的操作总结,恰好你又在看这一块,供一份参考.本次系列终结大概包括以下内容:多数据库安装.mycat部署安装.数据库之读写分离 ...

  7. JavaScript学习系列博客_38_JavaScript 事件

    事件(Event) - 事件指的是用户和浏览器之间的交互行为.比如:点击按钮.关闭窗口.鼠标移动.... - 我们可以为事件来绑定回调函数来响应事件. - 绑定事件的方式: 1.可以在标签的事件属性中 ...

  8. spss如何把多个指标合并成一个变量?

    把多个指标合并成一个变量,通常有两种做法: 一.计算平均值 针对问卷量表数据,同时几个题表示一个维度.比如想要将“我在工作中能获得成就感”.“我可以在工作中发挥个人的才能”这两题合并成一个维度(影响因 ...

  9. Hadoop的SecondaryNameNode的作用是什么?

    为节省篇幅,将SecondaryNameNode简称SNN,NameNode简称NN. NN与fsimage.edits文件 NN负责管理HDFS中所有的元数据,包括但不限于文件/目录结构.文件权限. ...

  10. CAOZ:百度搜索引擎的人工干预的看法

    http://www.wocaoseo.com/thread-247-1-1.html 百度确有人工干预的机制,但是这个机制与互联网上的传闻相差很大,人工干预的范围其实是非常小的,特别恶性的搜索结果, ...