写点什么

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

评论

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

大画 Spark :: 网络(1)-如何构建起基础的网络模型

dclar

大数据 spark 源代码 框架原理

一次ATDD的团队实践

Bruce Talk

敏捷 Agile User Story Product Owner Coach/Facilitate

构建云端智慧厨电 呵护人间烟火之智慧云厨房的那些事儿

坚果

华为云 1月月更

记EVO 使用error

Ayosh

EVO

人类视觉计算理论经典著作,豆瓣评分9.7,中文版惊鸿面世!

博文视点Broadview

VuePress 博客优化之开启 HTTPS

冴羽

Vue 前端 博客 vuepress 博客搭建

网络安全——内网渗透完整流程

网络安全学海

黑客 网络安全 信息安全 渗透测试 安全漏洞

万字长文带你漫游数据结构世界

秦怀杂货店

Java 数据结构 算法

3 【精】TOGAF认证报考指南(一文讲透)

企业架构知识体系

企业架构 架构师 培训 TOGAF

LabVIEW实现PCB电路板坐标定位(实战篇—2)

不脱发的程序猿

机器视觉 图像处理 LabVIEW PCB电路板坐标定位

疫情下的口罩生活,演绎出了哪些心理剧场?

脑极体

被忽视的NTP安全

喀拉峻

网络安全 安全 信息安全

字节码增强技术之 Java Agent 入门

zuozewei

性能分析 埋点 javaagent 性能监控 1月月更

记录一下童慧琦正念练习

wood

300天创作

不优雅的 React Hooks

CRMEB

Go语言gorm框架MySQL实践

FunTester

Go MySQL gorm FunTester

重庆华美:用宜搭实现全流程管理上云,节约超百万研发成本

一只大光圈

前端 阿里 低代码 数字化转型 钉钉宜搭

模块九作业-设计电商秒杀系统

deng

架构实战营

Tengine + BabaSSL ,让国密更易用!

SOFAStack

密码学 tengine 国密 BABASSL

一个cpp协程库的前世今生(十八)空闲与等待

SkyFire

c++ cocpp

1月月更|推荐学Java——Maven初识

逆锋起笔

maven javase Java后端 java 编程

华为云VSS漏洞扫描服务之开源组件漏洞检测能力

华为云开发者联盟

安全 华为云 漏洞 漏洞扫描 VSS漏洞扫描服务

当时间管理碰上大数据,从此,悠悠时光也终不再那么漫长

华为云开发者联盟

MySQL 数据库 云原生 App 云数据库RDS for MySQL

ReactNative进阶(十二):本地存储 AsyncStorage 及 Realm 使用

No Silver Bullet

React Native 1月月更 realm AsyncStorage

关于A股投资--《香帅中国财富报告》摘录(3/100)

hackstoic

投资

用 SwiftUI 实现一个开源的 App Store

37手游iOS技术运营团队

swift appstore SwiftUI App榜单 App免费榜

虎年前迎来脑科学新锐:脑虎科技的创生故事

脑极体

11 Prometheus之日志及探针监控

穿过生命散发芬芳

Prometheus 1月月更

高并发下如何实现对象的共享?

JavaEdge

1月月更

云计算服务滥用安全防范

明亮安全观

云计算 网络安全 信息安全 云安全 安全加固

一文了解数据库事务和隔离级别

Ayue、

MySQL 事务 1月月更

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