写点什么

用 LiveData 实现新的事件总线

  • 2019-09-26
  • 本文字数:4858 字

    阅读完需:约 16 分钟

用LiveData实现新的事件总线

1 背景

在 Android 系统中,我们开发的时候不可避免的会用到消息传递,页面和组件之间都在进行消息传递,消息传递既可以用于 Android 四大组件之间的通信,也可用于主线程和子线程之间的通信。从一开始 Android 书本中学习的 Handler、BroadcastReceiver、接口回调等方式,到我们现在广为使用到的 greenrobot 家的 EventBus,Square 家的 Otto,还有依托响应式编程代表 RxJava 实现的 RxBus,最近在浏览美团技术博客时发现 @liaohailiang 基于 Android Architecture Components 实现了一个名为 LiveEventBus 的新的事件总线,依靠 LiveData 这个类可以实现一个无须手动解除注册,无内存泄漏问题的事件总线,下面一起来了解一下。

2 从其他的时间总线说起

2.1 EventBus

EventBus 是 Android 和 Java 的发布/订阅事件总线。在它出现之前,我们往往使用 Handler、Intent、BroadcastReceiver 等方式进行数据传递,但这些都不能满足我们高效的开发。自从 EventBus 出现后,简化了组件之间都通信,将事件发送者和接收者隔离,在 Activity 和 Fragment 还有后台线程中表现良好,避免了复杂而且容易出错的依赖关系和生命周期问题,立马受到开发者的欢迎,它的思想就是消息的发布和订阅,这种思想在很多其他框架中都有体现。



从图中可以看出订阅发布模式是一种一对多的关系,同一个事件可以被多个订阅者接收,当发布者的状态发生变化时,订阅者都能收到通知进行数据更新。

2.2 RxBus

RxBus 名字看起来像一个库,但它并不是一个库,而是一种模式,它的思想是使用 RxJava 来实现了 EventBus,而让你不再需要使用 Otto 或者 GreenRobot 的 EventBus。RxBus 就是基于 RxJava 封装实现的类。随着 RxJava 在更多 Android 项目中的使用, 我们完全可以使用 RxBus 代替 EventBus,减少库的引入,增加系统的稳定性。


EventBus 虽然使用方便,但是在事件的生命周期的处理上需要我们利用订阅者的生命周期去注册和取消注册,这个部分还是略有麻烦之处,如果忘记了在生命周期结束回调中取消订阅,就会导致内存泄漏的问题,而我们可以结合使用 RxLifecycle 来配置,简化这一步骤。


RxBus 的实现


在 RxJava 中有个 Subject 类,它继承 Observable 类,同时实现了 Observer 接口,因此 Subject 既可以作为被观察者发送事件,也可以作为观察者接收事件,而 RxJava 内部的响应式的支持实现了事件总线的功能。可以使用 PublishSubject.create().toSerialized();生成一个 Subject 对象。


剩下的动作和 EventBus 一样了,在需要发消息的地方发送事件,那么订阅了该 Subject 对象的订阅者就会收到事件进行处理。


最后当页面 finish 的时候,如果没有取消订阅,就会和 EventBus 一样,导致 Activity 或 Fragment 无法回收引发内存泄漏,EventBus 要求我们在页面结束时取消注册,所以 RxBus 也需要如此。在 RxJava 中,订阅操作会返回一个 Subscription 对象,以便在合适的时机取消订阅,防止内存泄漏,如果一个类产生多个 Subscription 对象,我们可以用一个 CompositeSubscription 存储起来,以进行批量的取消订阅。


网上 RxBus 有很多实现,例如 AndroidKnife/RxBus,大家可以点击查看。

2.3 otto

otto 是 square 推出的一款应用在 android 上的轻量级事件总线框架,目的是为了解决消息通信的问题,通过反射帮助你在不持有对方引用的情况下通知到对方,缓解了移动开发中会遇到的耦合问题。


那么它有什么不足呢?


1)在注册的时候,因为 otto 要用反射遍历 Object 的所有方法,所以时间会拉长,对性能会有一定的影响,如果你的项目对性能要求并不那么高,那完全可以使用 otto 来减少代码量。


2)从源码里看,在基类中注册事件是一件比较麻烦的事情。


3)订阅事件的参数问题,otto 只允许接收一个参数,不然会抛出 RuntimeException。


4)项目通信场景较多而且复杂的时候,otto 框架的拓展性就显得不够友好了。


目前,Square 推荐使用 RxJava 实现 RxBus 方式来代替 Otto,也就是说 otto 被 square 公司抛弃了。

2.4 小结

3LiveData 基础

3.1 什么是 LiveData?

LiveData 是 Android Architecture Components 提出的框架,LiveData 是一个可以在给定生命周期内被观察的数据持有者类,它可以感知并遵循 Activity、Fragment 或 Service 等组件的生命周期。正是由于 LiveData 对组件生命周期可感知特点,因此可以做到仅在组件处于生命周期的激活状态时才更新 UI 数据。如果观察者对应的生命周期变成了 destroyed 状态时,它会被自动的移除,这对 Activity 和 Fragment 来说是非常有用的,它们可以很安全的观察 LiveData 而不用担心泄漏问题,它们被销毁后将立即取消订阅。

3.2 两种注册监听方式

3.2.1 observe 模式


拥有生命周期,在 started 和 resumed 时触发回调,更新数据,无需手动解除注册,无内存泄漏问题。


3.2.2 observeForever 模式


一直监听,没有生命周期,跟 EventBus 和 RxBus 一样,需要手动解除注册,否则有内存泄漏问题。

3.3 LiveData 的优点

3.3.1 保证数据和 UI 的统一


因为 LiveData 采用了观察者模式,LiveData 是被观察者,当数据改变时通知观察者(UI)及时更新。当组件从后台到前台来时,LiveData 也能将最新数据通知组件进行更新。


3.3.2 避免内存泄漏


因为 LiveData 能够监听生命周期的变化,当生命周期变化为 DESTROYED 时,就会清除观察者对象,不仅可以编写更少的代码,而且可以避免其他事件总线忘记调用反注册带来的内存泄漏风险。


3.3.3 页面不可见时不会崩溃


因为只有当生命周期是 STARTED 或 RESUMED 时,LiveData 才会通知数据变化,所以不用担心页面不可见时收到通知刷新数据导致的崩溃。


3.3.4 不需要额外处理响应生命周期变化


这一点还是因为 LiveData 能感知组件的生命周期,所以不需要我们告诉它生命周期状态。


3.3.5 解决 configuration changed 问题


当组件被 recreate 时,数据还是存在 LiveData 中,所以在屏幕发生旋转或者被回收再次启动,立刻就能收到最新的数据。

3.4 为什么用 LiveData 实现的事件总线能替代 EventBus、RxBus 和 otto

3.4.1 减少 apk 包大小


因为只依赖 Android 官方的 Android Architecture Components 组件的 LiveData,没有其他依赖,实现只有一个类。而 EventBus jar 包大小为 57kb,RxBus 依赖 RxJava 和 RxAndroid,其中 RxJava2 包大小 2.2MB,RxJava1 包大小 1.1MB,RxAndroid 包大小 9kb,otto 就不说了,因为 Square 推荐使用 RxJava 实现 RxBus 方式来代替 Otto[捂脸]。


3.4.2 依赖方支持更好


LiveEventBus 只依赖 Android 官方 Android Architecture Components 组件的 LiveData,相比 RxBus 依赖的 RxJava 和 RxAndroid,依赖方支持更好。

4LiveData 代码实现

4.1 MutableLiveData

MutableLiveData 是 LiveData 的子类,其中只重写了两个方法,代码如下:



一个是 postValue(T value),一个是 setValue(T value),具体区别就是后台线程/主线程的调用,后面看代码继续了解。

4.2 LiveData

先看看 LiveData 的声明:


它是一个抽象类,所以才有了 MutableLiveData 这个实现类。


接下来我们先从 setValue 入手看看 LiveData 的源码。


4.2.1 setValue(T value)



从注释中知道,如果有处于活跃状态的观察者,value 将会通知到它们,setValue 方法必须在 UI 线程中调用,并且指出,如果需要从后台线程使用的话,可以使用 postValue 方法。


其中 assertMainThread 方法实际上是判断是否是主线程,不是则会抛出异常:



mVersion 初始值为-1,这里就会变成 0;


主要看看 dispatchingValue(null);方法:



参数 initiator 为 null,会走到红框中,接着看:



mLastVersion 是在 ObserverWrapper 这个类中定义的,初始值为-1,在 setValue 的时候 mVersion 已经变成了 0,所以红框中的逻辑不会进入,接着往下走,observer 就会通过 onChanged 回调方法将我们设置的值通知给订阅者。


4.2.2 postValue(T value)



从注释知道该方法是供后台线程使用的,并且多次赋值的话以最后一次赋值为准,来看看红框里的代码:



代码里最终还是调用了 setValue 方法,回到了 setValue 的逻辑,只是帮我们处理了后台线程到主线程的切换工作。


赋值部分看完了,现在该看看注册订阅的代码了。


4.2.3 observe



从注释中得知,订阅事件在主线程上调度,如果 LiveData 已经有了数据集,它将被传递给观察者。只有所有者处于 Link Lifecycle.State Started 或 Resumed 状态时,观察者才会接收事件,如果所有者移动到 Link Lifecycle.State Destroyed 状态,则观察者将被自动删除。当数据在 owner 不处于活动状态,它将不会接收任何更新,如果它再次处于活动状态,它将自动接收最后一个可用数据。只要给定的生命周期所有者没有 destroy,LiveData 将保持对观察者和所有者的强引用,当它被销毁时,LiveData 将删除对观察者和所有者的引用。



LifecycleBoundObserver 实现了 GenericLifecycleObserver,在生命周期发生改变时会回调 onStateChanged 方法,如果 Activity/Fragment 对生命周期时 destroyed 的时候,就会触发 removeObserver(mObserver)操作,否则就会执行 activeStateChanged(shouldBeActive())方法。


activeStageChanged(shouldBeActive())是父类 ObserverWrapper 中定义的方法:



当生命周期处于 STARTED 或 RESUMED 时,就会执行红框中的逻辑,其实咱们上面已经看过了 dispatchingValue 方法,再来看一次:




到这里就出现了一个问题:


如果之前有调用过 setValue 方法的话,这里 mVersion++每次会自增,所以肯定是>-1 的,而 observer 中定义的 mLastVersion 初始值是-1,所以红框中的逻辑不会执行,代码往下执行走到 observer.mObserver.onChanged((T) mData);这样就会导致 LiveData 每注册一个新的订阅者,这个订阅者立刻会收到一个回调得到之前发布的消息,即使这个设置的动作发生在订阅之前。


如何解决这个问题?


由于是因为 ObserverWrapper 的 mLastVersion 和 LiveData 的 mVersion 不同步的问题导致的,所以可以通过继承 MutableLiveData 然后重写 observe 方法,LiveData 提供 getVersion()方法返回 mVersion,我们只需把 ObserverWrapper 的 mLastVersion 设成 mVersion 就好了。


@liaohailiang 在 github 上开源的 LiveEventBus 就是这么干的:



这样就能保证新注册的订阅者在初始


if (observer.mLastVersion >= mVersion) { return; }


执行这个方法时就 return 了,只有当它 setValue 或 postValue 赋值后,才会执行通知逻辑。


4.2.4 public void observeForever(@NonNull Observerobserver)


接着看看注册永久观察者方法里做了什么操作:



首先用了一个 AlwaysActiveObserver 类:



这个类的 shouldBeActive 方法 return true;这个状态表明不受生命周期的影响了。


另外 wrapper.activeStateChanged(true);也是传入了一个 true,所以在



considerNotify(ObserverWrapper observer)中,订阅者永远会接收到最新的发布消息。


4.2.5 removeObserver(@NonNull final Observerobserver)


在 LifecycleBoundObserver 中,由于实现了 GenericLifecycleObserver,所以当订阅者通过 observe 方式订阅的时候,当生命周期发生变化时会回调该方法:



当监听到页面当生命周期为 DESTROYED 会通知移除对应的观察者:



从 mObservers 列表中移除生命周期所有者对应的观察者,解除绑定观察者,通知状态变化。




Activity 或 Fragment 则会移除观察者对象。


需要注意的是这对 observeForever 方式注册的观察者并不生效。

5 总结

Android 官方提供了 Android Architecture Components 组件的 LiveData,具有很多其他事件总线不具备的优点,能方便的感知到页面的生命周期变化从而进行数据通知,我们也能基于这个类自己实现出一套性能优异的事件总线,这里推荐一个 @liaohailiang 在 GitHub 开源的项目代码:LiveEventBus,工程包含了其他的示例代码的使用本文没有做演示,只是对 LiveData 的核心原理做一个剖析,感兴趣的同学可以自行查阅代码。


作者介绍:


乘风(企业代号名),目前负责贝壳装修项目 Android 研发工作。


本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。


原文链接:


https://mp.weixin.qq.com/s/glnc99O9f6WyoARh2v7zTA


2019-09-26 19:031450

评论

发布
暂无评论
发现更多内容

产品经理训练营第三周作业

铭白

产品经理第 0 期训练营第三周作业提交-krystal

Krystal

第三周作业

苏格图德

产品经理训练营

第二章:产品思维和产品意识(下) - 作业 - 为云 g

Weiyung

week11 安全稳定

杨斌

利益相关者问题挖掘

王一凡

产品经理训练营

解决方案的设计与积累

王一凡

产品经理训练营

【LeetCode】BFS解决二叉树的最小深度

Albert

算法 LeetCode 2月春节不断更

第三章作业

Kalman

产品经理 产品经理训练营

免费开源的代码审计工具Gosec入门使用

BigYoung

代码扫描 28天写作 2月春节不断更 代码审计 Go 语言

第三周作业

正午看星星

第三周作业-利益相关者的问题及排序

隋泽

产品经理训练

利益相关者问题排序

Geek_ce1551

《未来科技趋势白皮书》概述解读

李忠良

28天写作

新浪微博利益相关方分析(相关方问题)

🙈🙈🙈

极客大学产品经理训练营

最新Wordpress个人博客搭建教程:免费精美主题搭建个人博客

大佬sam

Wordpress 博客部署 2月春节不断更

第三次作业

z

利益相关者排序

赵志广

产品经理训练营 网络安全产品经理

产品中利益相关者面对的问题并排序

踏凌霄

作业3

YING꯭YING

week12 数据应用(一)

杨斌

开发质量提升系列:标准模板(下)

罗小龙

最佳实践 方法论 28天写作

week10 模块分解 作业和学习总结

杨斌

OpenCV入门--读图,展示,保存

IT蜗壳-Tango

Python OpenCV 七日更 2月春节不断更

第三周总结

Jove

第三周

Jove

第三章学习总结

Kalman

产品经理 产品经理训练营

创业失败启示录|8分钟,50万,欢乐收场

阿萌

28天写作 创业失败启示录 2月春节不断更

产品经理训练营第三周作业

克比

产品经理训练营第三次作业

Jobs

图解Mysql索引的数据结构!看不懂你来找我

Java鱼仔

Java MySQL 数据库

用LiveData实现新的事件总线_文化 & 方法_乘风_InfoQ精选文章