消息总线

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

对比过去:

onActivityResult:使用较为繁琐,并且在多页面下(比如栈:A,B,C。C的事件要传递给A就使用不方便了)。
EventBus:时代的眼泪,使用方便,但性能内存体积都是缺陷。
RxBus:不是库,是个文件,实现只有短短30行代码。结合Rxjava可以很方便的使用。
LiveEventBus:利用系统的livedata。体积小。且可以感知生命周期。
消息总线 | 延迟发送 | 有序接收消息 | Sticky | 生命周期感知 | 跨进程/APP | 线程分发
-|-|-|-|-|-|-
EventBus | ❌ | ✅ | ✅ | ❌ | ❌ | ✅
RxBus | ❌ | ❌ | ✅ | ❌ | ❌ | ✅
LiveEventBus | ✅ | ✅ | ✅ | ✅ | ✅ | ❌

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

代码:

短短50多行代码(去掉注释和空行也就30行左右)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
object EventHelper {
private val map = HashMap<String, EventLiveData<*>>()
@Suppress("UNCHECKED_CAST")
fun <T> with(key: String, type: Class<T>? = null): EventLiveData<T> {
if (!map.containsKey(key)) {
map[key] = EventLiveData<T>()
}
return map[key] as? EventLiveData<T>
?: throw RuntimeException("cannot cast EventLiveData of " + type.toString())
}
/**
* 记录版本的 MutableLiveData。
*/
class EventLiveData<T> : MutableLiveData<T>() {
var version: Int = 0
private set
/**
* postValue 最终也会调 setValue ,所以只需要在这里统计 version。
*/
override fun setValue(value: T) {
version++
super.setValue(value)
}
/**
* 订阅事件。绑定生命周期。返回 Observer 方便主动移除观察。
*/
fun observeEvent(owner: LifecycleOwner, onEvent: (T) -> Unit): Observer<T> {
return EventObserver(onEvent).apply { super.observe(owner, this) }
}
/**
* 订阅事件。永久有效。返回 Observer 方便主动移除观察。
*/
fun observeForeverEvent(onEvent: (T) -> Unit): Observer<T> {
return EventObserver(onEvent).apply { super.observeForever(this) }
}
/**
* 不接收创建之前的消息。
*/
inner class EventObserver<T>(val onEvent: (T) -> Unit) : Observer<T> {
private var observerVersion = this@EventLiveData.version
override fun onChanged(t: T) {
if (observerVersion < this@EventLiveData.version) {
observerVersion = this@EventLiveData.version
onEvent(t)
}
}
}
}
}

使用姿势:

1
2
3
4
5
6
7
8
9
10
11
// 获取:(一般会把这步封装起来)
EventHelper.with<Event>(Key) //or
EventHelper.with(Key,Event::class.java)
// 订阅:
observeEvent(owner){ event -> do something } // 绑定生命周期。
observeForeverEvent{ event -> do something } // 永久有效。
// 发布:
setValue(event) // 主线程。
postValue(event) // 子线程。
// 移除:
同liveData。

实战:

  1. 封装:同一模块的事件放在一起。key 与 event 对应。
  2. key 命名规范:模块名+子模块名+具体事件。
  3. event类上加上ID:例如:ChangeGroupNameEvent加上groupCode明确是那个群名称变了。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // 示例
    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() = EventHelper.with<ChangeGroupNameEvent>(CHANGE_GROUP_NAME)
    /**
    * 公告修改。
    */
    fun changeNotification() = EventHelper.with<ChangeNotificationEvent>(CHANGE_NOTIFICATION)
    /**
    * 成员列表变换。
    */
    fun changeMemberList() = EventHelper.with<ChangeMemberListEvent>(CHANGE_MEMBER_LIST)
    /**
    * 群聊列表变换。
    */
    fun changeGroupList() = EventHelper.with<ChangeGroupListEvent>(CHANGE_GROUP_LIST)
    /**
    * 退出群聊。
    */
    fun quitGroup() = EventHelper.with<QuitGroupEvent>(QUIT_GROUP)
    /**
    * 选择AT 成员。
    */
    fun chooseAt() = EventHelper.with<AtMemberEvent>(CHOOSE_AT)
    }

event类:

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

使用:

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

总结:

要点:

  1. 利用LiveData,感知生命周期的特性。
  2. 不接收注册观察之前的消息。

缺点:

  1. Livedata只能保证接收到最新的数据。中间的数据可不管哦。😯

配一张图