现在看来,EventBus给Android开发者世界带来了一种新的框架和思想,就是消息的发布和订阅。这种思想在其后很多框架中都得到了应用。
图片摘自EventBus GitHub主页
发布/订阅模式
订阅发布模式定义了一种“一对多”的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。
RxBus的出现
RxBus不是一个库,而是一个文件,实现只有短短30行代码。RxBus本身不需要过多分析,它的强大完全来自于它基于的RxJava技术。响应式编程(Reactive Programming)技术这几年特别火,RxJava是它在Java上的实作。RxJava天生就是发布/订阅模式,而且很容易处理线程切换。所以,RxBus凭借区区30行代码,就敢挑战EventBus江湖老大的地位。
RxBus原理
在RxJava中有个Subject类,它继承Observable类,同时实现了Observer接口,因此Subject可以同时担当订阅者和被订阅者的角色,我们使用Subject的子类PublishSubject来创建一个Subject对象(PublishSubject只有被订阅后才会把接收到的事件立刻发送给订阅者),在需要接收事件的地方,订阅该Subject对象,之后如果Subject对象接收到事件,则会发射给该订阅者,此时Subject对象充当被订阅者的角色。
完成了订阅,在需要发送事件的地方将事件发送给之前被订阅的Subject对象,则此时Subject对象作为订阅者接收事件,然后会立刻将事件转发给订阅该Subject对象的订阅者,以便订阅者处理相应事件,到这里就完成了事件的发送与处理。
最后就是取消订阅的操作了,RxJava中,订阅操作会返回一个Subscription对象,以便在合适的时机取消订阅,防止内存泄漏,如果一个类产生多个Subscription对象,我们可以用一个CompositeSubscription存储起来,以进行批量的取消订阅。
RxBus有很多实现,如:
AndroidKnife/RxBus(https://github.com/AndroidKnife/RxBus) Blankj/RxBus(https://github.com/Blankj/RxBus)
其实正如前面所说的,RxBus的原理是如此简单,我们自己都可以写出一个RxBus的实现:
基于RxJava1的RxBus实现:
public final class RxBus {
private final Subject<Object, Object> bus;
private RxBus() {
bus = new SerializedSubject<>(PublishSubject.create());
}
private static class SingletonHolder {
private static final RxBus defaultRxBus = new RxBus();
}
public static RxBus getInstance() {
return SingletonHolder.defaultRxBus;
}
/*
- 发送
*/
public void post(Object o) {
bus.onNext(o);
}
/*
- 是否有Observable订阅
*/
public boolean hasObservable() {
return bus.hasObservers();
}
/*
- 转换为特定类型的Obserbale
*/
public Observable toObservable(Class type) {
return bus.ofType(type);
}
}
基于RxJava2的RxBus实现:
public final class RxBus2 {
private final Subject bus;
private RxBus2() {
// toSerialized method made bus thread safe
bus = PublishSubject.create().toSerialized();
}
public static RxBus2 getInstance() {
return Holder.BUS;
}
private static class Holder {
private static final RxBus2 BUS = new RxBus2();
}
public void post(Object obj) {
bus.onNext(obj);
}
public Observable toObservable(Class tClass) {
return bus.ofType(tClass);
}
public Observable toObservable() {
return bus;
}
public boolean hasObservers() {
return bus.hasObservers();
}
}
引入LiveDataBus的想法
从LiveData谈起
LiveData是Android Architecture Components提出的框架。LiveData是一个可以被观察的数据持有类,它可以感知并遵循Activity、Fragment或Service等组件的生命周期。正是由于LiveData对组件生命周期可感知特点,因此可以做到仅在组件处于生命周期的激活状态时才更新UI数据。
LiveData需要一个观察者对象,一般是Observer类的具体实现。当观察者的生命周期处于STARTED或RESUMED状态时,LiveData会通知观察者数据变化;在观察者处于其他状态时,即使LiveData的数据变化了,也不会通知。
LiveData的优点
- UI和实时数据保持一致 因为LiveData采用的是观察者模式,这样一来就可以在数据发生改变时获得通知,更新UI。
- 避免内存泄漏 观察者被绑定到组件的生命周期上,当被绑定的组件销毁(destroy)时,观察者会立刻自动清理自身的数据。
- 不会再产生由于Activity处于stop状态而引起的崩溃
例如:当Activity处于后台状态时,是不会收到LiveData的任何事件的。
- 不需要再解决生命周期带来的问题 LiveData可以感知被绑定的组件的生命周期,只有在活跃状态才会通知数据变化。
- 实时数据刷新 当组件处于活跃状态或者从不活跃状态到活跃状态时总是能收到最新的数据。
- 解决Configuration Change问题 在屏幕发生旋转或者被回收再次启动,立刻就能收到最新的数据。
谈一谈Android Architecture Components
Android Architecture Components的核心是Lifecycle、LiveData、ViewModel 以及 Room,通过它可以非常优雅的让数据与界面进行交互,并做一些持久化的操作,高度解耦,自动管理生命周期,而且不用担心内存泄漏的问题。
- Room 一个强大的SQLite对象映射库。
- ViewModel 一类对象,它用于为UI组件提供数据,在设备配置发生变更时依旧可以存活。
- LiveData 一个可感知生命周期、可被观察的数据容器,它可以存储数据,还会在数据发生改变时进行提醒。
- Lifecycle 包含LifeCycleOwer和LifecycleObserver,分别是生命周期所有者和生命周期感知者。
Android Architecture Components的特点
- 数据驱动型编程 变化的永远是数据,界面无需更改。
- 感知生命周期,防止内存泄漏。
- 高度解耦 数据,界面高度分离。
- 数据持久化 数据、ViewModel不与UI的生命周期挂钩,不会因为界面的重建而销毁。
重点:为什么使用LiveData构建数据通信总线LiveDataBus
使用LiveData的理由
- LiveData具有的这种可观察性和生命周期感知的能力,使其非常适合作为Android通信总线的基础构件。
- 使用者不用显示调用反注册方法。
由于LiveData具有生命周期感知能力,所以LiveDataBus只需要调用注册回调方法,而不需要显示的调用反注册方法。这样带来的好处不仅可以编写更少的代码,而且可以完全杜绝其他通信总线类框架(如EventBus、RxBus)忘记调用反注册所带来的内存泄漏的风险。
为什么要用LiveDataBus替代EventBus和RxBus
- LiveDataBus的实现及其简单 相对EventBus复杂的实现,LiveDataBus只需要一个类就可以实现。
- LiveDataBus可以减小APK包的大小 由于LiveDataBus只依赖Android官方Android Architecture Components组件的LiveData,没有其他依赖,本身实现只有一个类。作为比较,EventBus JAR包大小为57kb,RxBus依赖RxJava和RxAndroid,其中RxJava2包大小2.2MB,RxJava1包大小1.1MB,RxAndroid包大小9kb。使用LiveDataBus可以大大减小APK包的大小。
- LiveDataBus依赖方支持更好 LiveDataBus只依赖Android官方Android Architecture Components组件的LiveData,相比RxBus依赖的RxJava和RxAndroid,依赖方支持更好。
- LiveDataBus具有生命周期感知 LiveDataBus具有生命周期感知,在Android系统中使用调用者不需要调用反注册,相比EventBus和RxBus使用更为方便,并且没有内存泄漏风险。
LiveDataBus的设计和架构
LiveDataBus的组成
- 消息 消息可以是任何的Object,可以定义不同类型的消息,如Boolean、String。也可以定义自定义类型的消息。
- 消息通道 LiveData扮演了消息通道的角色,不同的消息通道用不同的名字区分,名字是String类型的,可以通过名字获取到一个LiveData消息通道。
- 消息总线 消息总线通过单例实现,不同的消息通道存放在一个HashMap中。
- 订阅 订阅者通过getChannel获取消息通道,然后调用observe订阅这个通道的消息。
- 发布 发布者通过getChannel获取消息通道,然后调用setValue或者postValue发布消息。
LiveDataBus原理图
LiveDataBus原理图
LiveDataBus的实现
第一个实现:
public final class LiveDataBus {
private final Map<String, MutableLiveData> bus;
private LiveDataBus() {
bus = new HashMap<>();
}
private static class SingletonHolder {
private static final LiveDataBus DATA_BUS = new LiveDataBus();
}
public static LiveDataBus get() {
return SingletonHolder.DATA_BUS;
}
public MutableLiveData getChannel(String target, Class type) {
if (!bus.containsKey(target)) {
bus.put(target, new MutableLiveData<>());
}
return (MutableLiveData) bus.get(target);
}
public MutableLiveData getChannel(String target) {
return getChannel(target, Object.class);
}
}
短短二十行代码,就实现了一个通信总线的全部功能,并且还具有生命周期感知功能,并且使用起来也及其简单:
注册订阅:
LiveDataBus.get().getChannel(“key_test”, Boolean.class)
.observe(this, new Observer() {
@Override
public void onChanged(@Nullable Boolean aBoolean) {
}
});
发送消息:
LiveDataBus.get().getChannel(“key_test”).setValue(true);
我们发送了一个名为”key_test”,值为true的事件。
这个时候订阅者就会收到消息,并作相应的处理,非常简单。
问题出现
对于LiveDataBus的第一版实现,我们发现,在使用这个LiveDataBus的过程中,订阅者会收到订阅之前发布的消息。对于一个消息总线来说,这是不可接受的。无论EventBus或者RxBus,订阅方都不会收到订阅之前发出的消息。对于一个消息总线,LiveDataBus必须要解决这个问题。
问题分析
怎么解决这个问题呢?先分析下原因:
当LifeCircleOwner的状态发生变化的时候,会调用LiveData.ObserverWrapper的activeStateChanged函数,如果这个时候ObserverWrapper的状态是active,就会调用LiveData的dispatchingValue。
在LiveData的dispatchingValue中,又会调用LiveData的considerNotify方法。
在LiveData的considerNotify方法中,红框中的逻辑是关键,如果ObserverWrapper的mLastVersion小于LiveData的mVersion,就会去回调mObserver的onChanged方法。而每个新的订阅者,其version都是-1,LiveData一旦设置过其version是大于-1的(每次LiveData设置值都会使其version加1),这样就会导致LiveDataBus每注册一个新的订阅者,这个订阅者立刻会收到一个回调,即使这个设置的动作发生在订阅之前。
问题原因总结
对于这个问题,总结一下发生的核心原因。对于LiveData,其初始的version是-1,当我们调用了其setValue或者postValue,其vesion会+1;对于每一个观察者的封装ObserverWrapper,其初始version也为-1,也就是说,每一个新注册的观察者,其version为-1;当LiveData设置这个ObserverWrapper的时候,如果LiveData的version大于ObserverWrapper的version,LiveData就会强制把当前value推送给Observer。
如何解决这个问题
明白了问题产生的原因之后,我们来看看怎么才能解决这个问题。很显然,根据之前的分析,只需要在注册一个新的订阅者的时候把Wrapper的version设置成跟LiveData的version一致即可。
那么怎么实现呢,看看LiveData的observe方法,他会在步骤1创建一个LifecycleBoundObserver,LifecycleBoundObserver是ObserverWrapper的派生类。然后会在步骤2把这个LifecycleBoundObserver放入一个私有Map容器mObservers中。无论ObserverWrapper还是LifecycleBoundObserver都是私有的或者包可见的,所以无法通过继承的方式更改LifecycleBoundObserver的version。
那么能不能从Map容器mObservers中取到LifecycleBoundObserver,然后再更改version呢?答案是肯定的,通过查看SafeIterableMap的源码我们发现有一个protected的get方法。因此,在调用observe的时候,我们可以通过反射拿到LifecycleBoundObserver,再把LifecycleBoundObserver的version设置成和LiveData一致即可。
对于非生命周期感知的observeForever方法来说,实现的思路是一致的,但是具体的实现略有不同。observeForever的时候,生成的wrapper不是LifecycleBoundObserver,而是AlwaysActiveObserver(步骤1),而且我们也没有机会在observeForever调用完成之后再去更改AlwaysActiveObserver的version,因为在observeForever方法体内,步骤3的语句,回调就发生了。
那么对于observeForever,如何解决这个问题呢?既然是在调用内回调的,那么我们可以写一个ObserverWrapper,把真正的回调给包装起来。把ObserverWrapper传给observeForever,那么在回调的时候我们去检查调用栈,如果回调是observeForever方法引起的,那么就不回调真正的订阅者。
LiveDataBus最终实现
public final class LiveDataBus {
private final Map<String, BusMutableLiveData> bus;
private LiveDataBus() {
bus = new HashMap<>();
}
private static class SingletonHolder {
private static final LiveDataBus DEFAULT_BUS = new LiveDataBus();
}
public static LiveDataBus get() {
return SingletonHolder.DEFAULT_BUS;
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/90515dc64579e097af9371cb9d1ab2cf.jpeg)
最后,面试前该准备哪些资源复习?
其实客户端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
这里再分享一下我面试期间的复习路线:(以下体系的复习资料是我从各路大佬收集整理好的)
《Android开发七大模块核心知识笔记》
《960全网最全Android开发笔记》
《379页Android开发面试宝典》
历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!