基于 RxJava 的 Android 事件总线实现——RxBus

简介

RxBus 是基于 RxJava 实现的事件总线方式,他的强大之处是代码非常的少但是功能却并不简单。最简单的 RxBus 实现仅仅需要三十几行代码,相较之下 EventBus 显得有些臃肿。正因为强大的 RxBus ,Android 开源界的良心企业 Square 公司停止了维护他们的事件总线库 Otto并且在 github 项目主页上为 RxBus 投了一张推荐票:

Deprecated!
This project is deprecated in favor of RxJava and RxAndroid. These projects permit the same event-driven programming model as Otto, but they’re more capable and offer better control of threading.
If you’re looking for guidance on migrating from Otto to Rx, this post is a good start.
该项目已被RxJava和RxAndroid取代。Rx类项目允许与Otto类似的事件驱动编程模型,而且能力更强,操作线程更方便。
如果你正在寻找从Otto迁移到Rx的指导,这个帖子是个很好的开始。

在引入 RxJava 和 RxAndroid 库的前提下 RxBus 的实现仅仅需要几十行代码的一个类,在越来越多的开发者倾向于使用 RxAndroid + Retrofit 实现网络请求框架的现在,RxBus 成为对应的事件总线实现方式也顺理成章,没必要再为了时间总线单独再引入一个库从而增加应用体积,当然如果项目已经介入 EventBus 作为事件总线库,那就得自己衡量切换成本了。

实践

RxJava 与 RxJava2 在操作符和方法上有了一些修改,两个版本的实现方式下面都会讲到.

最简实现

RxJava 1

public class RxBus {
    private static volatile RxBus defaultInstance;

    private final Subject<Object, Object> bus;
    // PublishSubject只会把在订阅发生的时间点之后来自原始Observable的数据发射给观察者
    public RxBus() {
        bus = new SerializedSubject<>(PublishSubject.create());
    }
    public static RxBus getDefault() {
        if (defaultInstance == null) {
            synchronized (RxBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new RxBus();
                }
            }
        }
        return defaultInstance ;
    }
    // 发送一个新的事件
    public void post (Object o) {
        bus.onNext(o);
    }
    // 根据传递的 eventType 类型返回特定类型(eventType)的 被观察者
    public <T> Observable<T> toObservable (Class<T> eventType) {
        return bus.ofType(eventType);
    }
}

RxJava 2

public class RxBus {
    private static volatile RxBus defaultInstance;
    private final Subject<Object> bus;

    // PublishSubject只会把在订阅发生的时间点之后来自原始Observable的数据发射给观察者
    private RxBus() {
        bus = PublishSubject.create().toSerialized();
    }

    // 单例RxBus
    public static RxBus getDefault() {
        if (defaultInstance == null) {
            synchronized (RxBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new RxBus();
                }
            }
        }
        return defaultInstance;
    }

    // 发送一个新的事件,所有订阅此事件的订阅者都会收到
    public void post(Object action) {
        bus.onNext(action);
    }


    // 根据传递的 eventType 类型返回特定类型(eventType)的 被观察者
    public <T> Observable<T> toObservable(Class<T> eventType) {
        return bus.ofType(eventType);
    }
}

RxJava1 升级到 RxJava2 只是初始化方式发生了变化,其他地方保持不变。

简单使用

RxJava 1

// 数据发送端
// data 为任意数据类型,以 Data 类型代表
RxBus.getDefault().post(data);
    .
    .
    .
// 数据接收端
    mSubscription = RxBus
        .getDefault()
        .toObservable(Data.class)
        .subscribe(new Action1<Data>()){
            @Override
            public void call(Data data){
                    // do something with data ...
            }
        }
    .
    .
    .
// 监听使用离开之后(如关闭监听所在界面)时记得解绑监听,避免引起内存泄漏
if (mSubscription != null && !mSubscription.isUnsubscribed()) {
    mSubscription.unsubscribe();
 }

RxJava 2

// 数据发送端
// data 为任意数据类型,以 Data 类型代表
RxBus.getDefault().post(data);
private Disposable mDisposable;
    .
    .
    .
RxBus.getDefault()
        .toObservable(Data.class)
        //使用 subscribeWith 和 subscribe 都可以
        .subscribeWith(new Observer<Data>){
            @Override
            public void onSubscribe(Disposable d) {
                mDisposable = d;
            }

            @Override
            public void onNext(Data data) {
                // do something with data ...
            }

            @Override
            public void onError(Throwable e) {
                // do something with e ...
            }

            @Override
            public void onComplete() {

            }
        };
    .
    .
    .
if (mDisposable != null && !mDisposable.isDisposed()) {
                mDisposable.dispose();
}

带 Code 封装

通过上面的简单实用会发现一个问题,每发送一个类都需要一个发送对象类,接收的时候也需要传入该对象类。假如存在观察者 A、B、C 都已经注册了对 D.class 消息接收。如果此时只想向 A 发送 D对象,那么就会造成多个观察者都收到这个消息。这也违背了只想传给 A 观察者的初衷。因此我的想法是为每个发送对象在添加一个发送 Code 当 Code 和 class 都满足时观察者才会接收处理消息,这样如果需要通知多个观察者那么观察者注册时使用相同的 Code 就行。

代码实现

实现带 Code 的消息发送需要先实现一个 Code 封装类(我命名为 Action)然后在 RxBus 内部将传入的 Code 和对象转换成 Action 封装对象:

/**
* 封装的消息对象
*/
public class Action 
 // 消息 Code
    public int code;
 // 消息对象
    public Object data;

    public Action(int code, Object data) {
        this.code = code;
        this.data = data;
    }
}

RxJava 1

public class RxBus {

    private static volatile RxBus defaultInstance;

    private final Subject<Object,Object> bus;

    // PublishSubject只会把在订阅发生的时间点之后来自原始Observable的数据发射给观察者
    private RxBus() {
        bus = new SerializedSubject<>(PublishSubject.create());
    }

    // 单例RxBus
    public static RxBus getDefault() {
        if (defaultInstance == null) {
            synchronized (RxBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new RxBus();
                }
            }
        }
        return defaultInstance;
    }

    // 发送一个新的事件
    public void postWithCode(int code, Object action) {
        bus.onNext(new Action(code,action));
    }

    // 根据传递的 eventType 类型返回特定类型(eventType)的 被观察者
    public <T> Observable<T> toObservableWithCode(final int code, Class<T> eventType) {
        return bus.ofType(Action.class)
                // 过滤掉非自己 code 对应的接收项
                .filter(new Func1<Action,Boolean>(){
                    @Override
                    public boolean call(Action action){
                        return action.code == code;
                    }
                })
                // 返回传递的数据对象
                .map(new Func1<Action,Object>){
                    @Override
                    public Object call(Action action){
                        return action.data;
                    }
                }
                .cast(eventType);
    }

RxJava 2

public class RxBus {

    private static volatile RxBus defaultInstance;

    private final Subject<Object> bus;

    // PublishSubject只会把在订阅发生的时间点之后来自原始Observable的数据发射给观察者
    private RxBus() {
        bus = PublishSubject.create().toSerialized();
    }

    // 单例RxBus
    public static RxBus getDefault() {
        if (defaultInstance == null) {
            synchronized (RxBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new RxBus();
                }
            }
        }
        return defaultInstance;
    }

    // 发送一个新的事件,所有订阅此事件的订阅者都会收到
    public void post(Object action) {
        bus.onNext(action);
    }

    // 用 code 指定订阅此事件的对应 code 的订阅者
    public void postWithCode(int code, Object action) {
        bus.onNext(new Action(code, action));
    }

    // 根据传递的 eventType 类型返回特定类型(eventType)的 被观察者
    public <T> Observable<T> toObservable(Class<T> eventType) {
        return bus.ofType(eventType);
    }

    // 根据传递的 eventType 类型返回特定类型(eventType)的 被观察者,
    public <T> Observable<T> toObservableWithCode(final int code, Class<T> eventType) {
        return bus.ofType(Action.class)
                .filter(new Predicate<Action>() {
                    @Override
                    public boolean test(Action action) throws Exception {
                        return action.code == code;
                    }
                })
                .map(new Function<Action, Object>() {
                    @Override
                    public Object apply(Action action) throws Exception {
                        return action.data;
                    }
                })
                .cast(eventType);
    }
}

与简单使用一样,封装上 RxJava1 与 RxJava2 仅用操作符上的一些差别,实现思路未改变都是在发送时将行为 code 和数据对象 object 封装成 Action 对象,在转换成 Observable 时返回 Action 对象的处理结果,通过 Action 的 code 进行数据过滤。

使用

除了发送和接收数据对象时会同时接收 code 参数外,带 Code 的 Rxbus 封装使用与不带 Code 完全一致

RxBus.getDefault().postWithCode(code, data);
    .
    .
    .
  RxBus.getDefault()
            .toObservableWithCode(RxConstants.BACK_PRESSED_CODE, String.class)
            .subscribeWith(...);
    .
    .
    .

总结

RxBus 普通使用的具体使用场景大家可以参考我的开源项目 熊猫眼
RxBus 在引入了 RxJava、RxAndroid 库后实现可以说非常简洁了,如果已经在使用 RxJava 了就大胆的尝试 RxBus 吧。有好处也必定有坏处,譬如上面提到的简单实现就有两个特别头疼的问题:

  • Sticky 事件时就是不适用的。
  • 订阅处理过程中一旦发生了异常,那么该处订阅就再也收不到了除非重新订阅。这是因为当出现异常时 onError() 被调用了,订阅者和被订阅者的关系被解除了。
    上面的两个问题我推荐大家看一下 YoKey[深入 RxBus :支持 Sticky 事件][深入 RxBus :异常处理]两篇文章。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容