AI实践哪家强?来 AICon, 解锁技术前沿,探寻产业新机! 了解详情
写点什么

用 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:031608

评论

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

开发者问第四期|统一扫码服务、机器学习服务等问题解答

HarmonyOS SDK

腾讯云,DevOps 领导者!

CODING DevOps

腾讯云 DevOps IDC CODING

9月《中国数据库行业分析报告》重磅发布!关键词:软硬兼施,创新融合

墨天轮

数据库 oracle cpu 硬件 国产数据库

分享一个研发工作优先级的计算公式 | Liga译文

LigaAI

Scrum 产品经理 敏捷开发 产品优先级 企业号九月金秋榜

架构师成长之路——什么是架构师

小小怪下士

Java 程序员 架构 后端

一个代码仓库(免费)与技术点 的故事

八点半的Bruce.D

GitHub Linux 网络服务 GitHub仓库

高精度的“文件转换excel”背后藏着这些解题思路!

合合技术团队

人工智能 表格识别

为什么Java中有三种基础的类加载器?

小小怪下士

Java 编程 程序员 程序

元宇宙场景技术实践|虚拟直播间搭建教程

ZEGO即构

音视频开发 元宇宙 虚拟直播

作为一个菜鸟前端开发,面了20+公司之后整理的面试题

beifeng1996

前端 React

2.69分钟完成BERT训练!新发CANN 5.0加持

华为云开发者联盟

人工智能 企业号九月金秋榜

中国DevOps平台市场,华为云再次位居领导者位置

华为云开发者联盟

云计算 华为云 企业号九月金秋榜

ESP32-C3入门教程 基础篇(八、NVS — 非易失性存储库的使用)

矜辰所致

ESP32-C3 9月月更 NVS

爆肝整理5000字!HTAP的关键技术有哪些?| StoneDB学术分享会#3

StoneDB

数据库 HTAP StoneDB 企业号九月金秋榜 9月月更

智能电饭煲

OpenHarmony开发者

OpenHarmony

【存疑】爬虫学习中decode问题

Sher10ck

存疑

英特尔Wi-Fi 7速率提升5倍,为多应用场景带来改变

科技之家

2022最新腾讯面经分享:Java 面试刷题 PDF(17 大专题 )

Java-fenn

Java 编程 程序员 面试 java面试

模块一

早安

极客时间架构训练营

软件测试 | 测试开发 | 背熟这些 Docker 命令,面试再也不怕啦~

测吧(北京)科技有限公司

测试

Lua脚本在Redis事务中的应用实践

京东科技开发者

数据库 redis 事务 开发语言 Lua脚本

前端面试哪些是必须要掌握的

loveX001

JavaScript 前端

基于云原生技术打造全球融合通信网关

阿里云CloudImagine

云原生 网络 通信 通信云

从近期欧美法规看软件供应链安全趋势

墨菲安全

软件供应链安全 开源安全与治理

云图说丨DDoS防护解决方案:DDoS大流量攻击防得住

华为云开发者联盟

云计算 后端 华为云 企业号九月金秋榜

打破联接壁垒,华为云IoT到底强在哪?

华为云开发者联盟

云计算 后端 物联网 华为云 企业号九月金秋榜

EasyCV带你复现更好更快的自监督算法-FastConvMAE

阿里云大数据AI技术

深度学习 算法 计算机视觉

老生常谈!数据库如何存储时间?你真的知道吗?

小小怪下士

Java 数据库 编程 程序员

2022年面试复盘大全500道:Redis+ZK+Nginx+数据库+分布式+微服务

小小怪下士

数据库 redis 分布式 微服务 java面试

TiDB+TiSpark部署--安装,扩缩容及升级操作

TiDB 社区干货传送门

安装 & 部署

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