LiveData实现消息总线(零反射)

app开发中,我们常遇见不同页面之间要保持数据同步。送最初的onActivityResult,到后面的第三方库EventBus,RxBus,LiveEventBus。现在我们需要利用livedata自己写一个简单的消息总线。

对比过去:

onActivityResult:使用较为繁琐,并且在多页面下(比如栈:A,B,C。C的事件要传递给A就使用不方便了)。
EventBus:时代的眼泪,使用方便,但性能内存体积都是缺陷。
RxBus:不是库,是个文件,实现只有短短30行代码。结合Rxjava可以很方便的使用。
LiveEventBus:利用系统的livedata。体积小。且可以感知生命周期。

消息总线 延迟发送 有序接收消息 Sticky 生命周期感知 跨进程/APP 线程分发
EventBus
RxBus
LiveEventBus

它们都很优秀。但我们不用它们。自己写个简单的实现方案。主要参考LiveEventBus的核心逻辑。

代码:

LiveData做消息,需要解决的是它的粘性问题(会接收注册前的消息)。网上有很多利用反射的方式修改lastVersion的方法。这里提供一个不用反射的方式。

自己写一个,也叫LiveEventBus。😄

public class LiveEventBus {

    private LiveEventBus() {
    }

    private static volatile LiveEventBus INSTANCE = null;

    public static LiveEventBus getInstance() {
        if (INSTANCE == null) {
            synchronized (LiveEventBus.class) {
                if (INSTANCE == null) {
                    INSTANCE = new LiveEventBus();
                }
            }
        }
        return INSTANCE;
    }

    private final Map<String, EventLiveData<?>> map = new HashMap<>();

    @SuppressWarnings("unchecked")
    public <T> MutableLiveData<T> get(String key, Class<T> clz) {
        if (!map.containsKey(key)) {
            EventLiveData<T> liveData = new EventLiveData<>();
            map.put(key, liveData);
            return liveData;
        }
        return (MutableLiveData<T>) map.get(key);
    }

    private static class EventLiveData<T> extends MutableLiveData<T> {

        private int version = 0;
        private final Map<Observer<?>, EventObserver<T>> observerMap = new HashMap<>();

        /**
         * postValue 最终也会调 setValue ,所以只需要在这里统计 version。
         */
        @Override
        public void setValue(T value) {
            super.setValue(value);
            version++;
        }

        @Override
        public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
            super.observe(owner, getEventObserver(observer));
        }

        @Override
        public void observeForever(@NonNull Observer<? super T> observer) {
            super.observeForever(getEventObserver(observer));
        }

        @Override
        public void removeObserver(@NonNull Observer<? super T> observer) {
            EventObserver<T> eventObserver = observerMap.remove(observer);
            if (eventObserver != null) super.removeObserver(eventObserver);
        }

        private EventObserver<T> getEventObserver(Observer<? super T> observer) {
            if (observerMap.get(observer) != null) throw new RuntimeException("observer重复添加了!");
            EventObserver<T> eventObserver = new EventObserver<>(observer, version);
            observerMap.put(observer, eventObserver);
            return eventObserver;
        }

        // 装饰者
        private class EventObserver<E> implements Observer<E> {
            private int observerVersion;
            private final Observer<? super E> observer;

            public EventObserver(Observer<? super E> observer, int version) {
                this.observer = observer;
                this.observerVersion = version;
            }

            @Override
            public void onChanged(E t) {
                if (observerVersion < EventLiveData.this.version) {
                    observerVersion = EventLiveData.this.version;
                    observer.onChanged(t);
                }
            }
        }
    }
}

使用姿势:

// 获取:(一般会把这步封装起来)
LiveEventBus .with<Event>(Key) //or 
LiveEventBus .with(Key,Event::class.java)
// 订阅:
observe(owner,observer)// 绑定生命周期。
observeForever(observer) // 永久有效。
// 发布:
setValue(event) // 主线程。
postValue(event) // 子线程。
// 移除:
同liveData。

实战:

  1. 封装:同一模块的事件放在一起。key 与 event 对应。
  2. key 命名规范:模块名+子模块名+具体事件。
  3. event类上加上ID:例如:ChangeGroupNameEvent加上groupCode明确是那个群名称变了。
// 示例
object IMEventHelper {
    private const val CHANGE_GROUP_NAME = "im/group/changeGroupName"
    private const val CHANGE_NOTIFICATION = "im/group/changeNotification"
    private const val CHANGE_MEMBER_LIST = "im/group/changeMemberList"
    private const val CHANGE_GROUP_LIST = "im/group/changeGroupList"
    private const val QUIT_GROUP = "im/group/quitGroup"
    private const val CHOOSE_AT = "im/group/chooseAt"
    /**
     * 群名称修改。
     */
    fun changeGroupName() = LiveEventBus .with<ChangeGroupNameEvent>(CHANGE_GROUP_NAME)
    /**
     * 公告修改。
     */
    fun changeNotification() = LiveEventBus .with<ChangeNotificationEvent>(CHANGE_NOTIFICATION)
    /**
     * 成员列表变换。
     */
    fun changeMemberList() = LiveEventBus .with<ChangeMemberListEvent>(CHANGE_MEMBER_LIST)
    /**
     * 群聊列表变换。
     */
    fun changeGroupList() = LiveEventBus .with<ChangeGroupListEvent>(CHANGE_GROUP_LIST)
    /**
     * 退出群聊。
     */
    fun quitGroup() = LiveEventBus .with<QuitGroupEvent>(QUIT_GROUP)
    /**
     * 选择AT 成员。
     */
    fun chooseAt() = LiveEventBus .with<AtMemberEvent>(CHOOSE_AT)
}

event类:

class ChangeGroupNameEvent(val groupCode: String, val newName: String)

使用:

// 在 fragment 初始化里订阅事件。
IMEventHelper
    .changeGroupName()
    .observeEvent(this) { vm.changeName(it) }
// 在 其它地方发布事件。
IMEventHelper
    .changeGroupName()
    .postValue(ChangeGroupNameEvent(groupCode, newName))

总结:

要点:

  1. 利用LiveData,感知生命周期的特性。
  2. 不接收注册观察之前的消息。(装饰者模式)
配一张图
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,873评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,483评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,525评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,595评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,018评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,958评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,118评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,873评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,643评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,813评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,293评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,615评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,306评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,170评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,968评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,107评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,894评论 2 278

推荐阅读更多精彩内容