• Jetpack 之 LiveData 实现事件总线


    事件总线相信大家很多时候都会用到,那大家常用的也就是常青树 EventBus,以及 RxJava 流行起来的后起之秀 RxBus。它们的使用方式都差不多,思想也都是基于观察者模式,正好 LiveData 的核心思想也是观察者模式,因此我们完全可以使用 LiveData 来实现一个事件总线,如果项目使用 Jetpack 套件的话,可以减少库的依赖,并且如果使用 LiveData 的话,还可以将注销操作交给系统处理,在避免内存泄露方面又可以省心了。

    一、基本实现

    我们需要实现不同界面之间通信的话,必然是需要使用同一个 LiveData 对象,那么首先我们就需要一个 LiveData 的管理类,将 LiveData 对象用一个键值对集合存储起来,Key 为 LiveData 中 value 的权限类名,Value 就是 LiveData 对象,然后考虑到可以会使用带有泛型的对象作为事件传递,所以在注册 LiveData 的时候最好还支持泛型。有了这两个基本需求,我们就可以简单设计一下我们的管理类 LiveEventBus:

    1. package com.qinshou.jetpackdemo.liveeventbus;
    2. import androidx.lifecycle.MutableLiveData;
    3. import java.util.HashMap;
    4. import java.util.Map;
    5. public class LiveEventBus {
    6. private static final Map> sMap = new HashMap<>();
    7. public static MutableLiveData get(Class clazz) {
    8. String key = clazz.getName();
    9. MutableLiveData eventLiveData = (MutableLiveData) sMap.get(key);
    10. if (eventLiveData == null) {
    11. eventLiveData = new MutableLiveData<>();
    12. sMap.put(key, eventLiveData);
    13. }
    14. return eventLiveData;
    15. }
    16. public static MutableLiveData get(TypeToken typeToken) {
    17. String key = typeToken.getType().toString();
    18. MutableLiveData eventLiveData = (MutableLiveData) sMap.get(key);
    19. if (eventLiveData == null) {
    20. eventLiveData = new MutableLiveData<>();
    21. sMap.put(key, eventLiveData);
    22. }
    23. return eventLiveData;
    24. }
    25. }

    TypeToken 其实就是类似 Gson 中的 TypeToken 类,用于获取带泛型的对象中的真实泛型的,如果项目中 json 解析是用的 Gson 的话,那么也可以直接使用 Gson 的 TypeToken 类。由于功能简单,所以我是自己定义了 TypeToken 类:

    1. package com.qinshou.jetpackdemo.liveeventbus;
    2. import java.lang.reflect.ParameterizedType;
    3. import java.lang.reflect.Type;
    4. public abstract class TypeToken {
    5. private Type mType;
    6. public TypeToken() {
    7. mType = new Type() {
    8. @Override
    9. public String getTypeName() {
    10. Type genericSuperclass = TypeToken.this.getClass().getGenericSuperclass();
    11. if (!(genericSuperclass instanceof ParameterizedType)) {
    12. return genericSuperclass.toString();
    13. }
    14. ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
    15. Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
    16. if (actualTypeArguments.length <= 0) {
    17. return parameterizedType.toString();
    18. }
    19. return actualTypeArguments[0].toString();
    20. }
    21. @Override
    22. public String toString() {
    23. Type genericSuperclass = TypeToken.this.getClass().getGenericSuperclass();
    24. if (!(genericSuperclass instanceof ParameterizedType)) {
    25. return genericSuperclass.toString();
    26. }
    27. ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
    28. Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
    29. if (actualTypeArguments.length <= 0) {
    30. return parameterizedType.toString();
    31. }
    32. return actualTypeArguments[0].toString();
    33. }
    34. };
    35. }
    36. public Type getType() {
    37. return mType;
    38. }
    39. }

    其实到此为止,我们已经可以使用 LiveEventBus 实现不同组件间通信了,eg:

    1. findViewById
    2. LiveEventBus.get(Int::class.java).observe(this) {
    3. Toast.makeText(this@LiveEventBusActivity,"接受到 Int 事件: $it",Toast.LENGTH_LONG).show()
    4. }
    5. }
    6. findViewById
    7. LiveEventBus.get(object : TypeToken>() {}).observe(this) {
    8. Toast.makeText(this@LiveEventBusActivity,"接受到 List 事件: $it",Toast.LENGTH_LONG).show()
    9. }
    10. }
    11. findViewById
    12. LiveEventBus.get(Int::class.java).value = 123
    13. }
    14. findViewById
    15. LiveEventBus.get(object :
    16. TypeToken>() {}).value = listOf("Hello LiveEventBus")
    17. }

    先点击上面两个按钮注册观察者后,再点击下面两个按钮发送事件,效果如下:

    可以看到效果跟使用 EventBus 是一样的,而且由于 LiveData 的天然特性,我们还不用去关注注销操作,可谓是省了一心。

    二、粘性事件

    使用过 LiveData 的朋友应该知道在 LiveData 绑定的时候会收到最新的值(不知道的朋友可以参考我的另一篇博客 Jetpack 之 LiveData_禽兽先生不禽兽的博客-CSDN博客),其效果就是先发送事件再注册观察者的话,也能收到之前的事件,如下:

     

    这可以说是 LiveData 的一个优势,但是在作为事件总线时,这个特点相当于 EventBus 的粘性事件,我们有时候并不需要粘性事件,所以我们需要对 LiveData 进行一下改造。

    定义一个 BaseObserver:

    1. public abstract class BaseObserver implements Observer {
    2. /**
    3. * 是否接收粘性事件
    4. */
    5. boolean mSticky = true;
    6. public boolean isSticky() {
    7. return mSticky;
    8. }
    9. public BaseObserver setSticky(boolean sticky) {
    10. mSticky = sticky;
    11. return this;
    12. }
    13. }

    注意在后面添加观察者的时候我们需要添加的 BaseObserver 的实现类而不是 androidx.lifecycle 包下的 Observer 了

    然后我们复制 LiveData 源码,修改两个地方,首先给 ObserverWrapper 增加一个 boolean 属性 mSticky:

    1. private abstract class ObserverWrapper {
    2. ...
    3. /**
    4. * 是否接收粘性事件
    5. */
    6. boolean mSticky = true;
    7. ...
    8. }

    因为知道是 mLastVersion 造成的这个粘性事件,所以在添加观察者时,根据 BaseObserver 的 mSticky 属性来决定是否同步 mLastVersion:

    1. public void observe(@NonNull LifecycleOwner owner, @NonNull Observersuper T> observer) {
    2. assertMainThread("observe");
    3. if (owner.getLifecycle().getCurrentState() == DESTROYED) {
    4. // ignore
    5. return;
    6. }
    7. LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    8. // 如果是 BaseObserver,则设置 wrapper 对应属性
    9. if (observer instanceof BaseObserver) {
    10. wrapper.mSticky = ((BaseObserver) observer).isSticky();
    11. }
    12. @SuppressLint("RestrictedApi") ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    13. if (existing != null && !existing.isAttachedTo(owner)) {
    14. throw new IllegalArgumentException("Cannot add the same observer"
    15. + " with different lifecycles");
    16. }
    17. if (existing != null) {
    18. return;
    19. }
    20. // 不需要粘性事件,则同步最后版本
    21. if (!wrapper.mSticky) {
    22. wrapper.mLastVersion = mVersion;
    23. }
    24. owner.getLifecycle().addObserver(wrapper);
    25. }

    如果有需要的话,observeForever 方法也可以对应修改。另外,LiveEventBus 中对 MutableLiveData 的引用也需要去掉,引入我们自己的 MutableLiveData,所有源码最后会放上链接

    如此改造,我们就可以决定观察者是否接受粘性事件了:

    1. findViewById
    2. LiveEventBus.get(Int::class.java).observe(this, object : BaseObserver<Int>() {
    3. override fun onChanged(t: Int?) {
    4. Toast.makeText(this@LiveEventBusActivity, "接受到 Int 事件: $it", Toast.LENGTH_LONG).show()
    5. }
    6. }.setSticky(false))
    7. }
    8. findViewById
    9. LiveEventBus.get(object : TypeToken>() {}).observe(this, object :
    10. BaseObserver>() {
    11. override fun onChanged(t: List<String>?) {
    12. Toast.makeText(this@LiveEventBusActivity, "接受到 List 事件: $it", Toast.LENGTH_LONG).show()
    13. }
    14. }.setSticky(false))
    15. }

    先发送事件,再注册观察者,可以看到并没有收到事件,然后再次发送事件的时候才收到:

     

    三、非活跃状态时收到事件

    这个问题我也同样在刚才提到的博客中分析过,主要是因为通常我们使用 LiveData 是结合数据请求用的,收到响应后一般就渲染 UI 了,而在后台的时候因为看不到 UI,所以也不关心非活跃状态的时候观察者是否有收到通知,但如果需要在后台分发一下事件的时候,我们也要相应的对其进行改造。

    在 BaseObserver 中再增加一个 boolean 属性 mObserveOnlyActive,在 ObserverWrapper 中也同样增加该属性:

    1. public abstract class BaseObserver implements Observer {
    2. /**
    3. * 是否接收粘性事件
    4. */
    5. boolean mSticky = true;
    6. /**
    7. * 是否仅在活跃状态下接收更新
    8. */
    9. private boolean mObserveOnlyActive = true;
    10. ...
    11. 省略 get/set
    12. }
    1. private abstract class ObserverWrapper {
    2. ...
    3. /**
    4. * 是否接收粘性事件
    5. */
    6. boolean mSticky = true;
    7. /**
    8. * 是否仅在活跃状态下接收更新
    9. */
    10. boolean mObserveOnlyActive = true;
    11. ...
    12. }

    添加观察者时增加对 mObserveOnlyActive 属性的赋值:

    1. public void observe(@NonNull LifecycleOwner owner, @NonNull Observersuper T> observer) {
    2. assertMainThread("observe");
    3. if (owner.getLifecycle().getCurrentState() == DESTROYED) {
    4. // ignore
    5. return;
    6. }
    7. LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    8. // 如果是 BaseObserver,则设置 wrapper 对应属性
    9. if (observer instanceof BaseObserver) {
    10. wrapper.mSticky = ((BaseObserver) observer).isSticky();
    11. wrapper.mObserveOnlyActive = ((BaseObserver) observer).isObserveOnlyActive();
    12. }
    13. @SuppressLint("RestrictedApi") ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    14. if (existing != null && !existing.isAttachedTo(owner)) {
    15. throw new IllegalArgumentException("Cannot add the same observer"
    16. + " with different lifecycles");
    17. }
    18. if (existing != null) {
    19. return;
    20. }
    21. // 不需要粘性事件,则同步最后版本
    22. if (!wrapper.mSticky) {
    23. wrapper.mLastVersion = mVersion;
    24. }
    25. owner.getLifecycle().addObserver(wrapper);
    26. }

    然后再修改 considerNotify 方法中对观察者不活跃就 return 的判断代码即可:

    1. private void considerNotify(ObserverWrapper observer) {
    2. // 如果 observer 不是活跃状态,但 observer 要求仅在活跃状态下通知的,直接 return
    3. if (!observer.mActive && observer.mObserveOnlyActive) {
    4. return;
    5. }
    6. // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
    7. //
    8. // we still first check observer.active to keep it as the entrance for events. So even if
    9. // the observer moved to an active state, if we've not received that event, we better not
    10. // notify for a more predictable notification order.
    11. // 如果 observer 不应该是活跃状态,但 observer 要求仅在活跃状态下通知的,直接 return
    12. if (!observer.shouldBeActive() && observer.mObserveOnlyActive) {
    13. observer.activeStateChanged(false);
    14. return;
    15. }
    16. if (observer.mLastVersion >= mVersion) {
    17. return;
    18. }
    19. observer.mLastVersion = mVersion;
    20. observer.mObserver.onChanged((T) mData);
    21. }

    现在我们将给发送事件加一个延时,然后退到后台使观察者处于非活跃状态,看看还能不能收到事件:

    1. findViewById
    2. LiveEventBus.get(Int::class.java).observe(this, object : BaseObserver() {
    3. override fun onChanged(t: Int?) {
    4. Toast.makeText(this@LiveEventBusActivity, "接受到 Int 事件: $it", Toast.LENGTH_LONG).show()
    5. }
    6. }.setSticky(false).setObserveOnlyActive(false))
    7. }
    8. findViewById
    9. LiveEventBus.get(object : TypeToken>() {}).observe(this, object :
    10. BaseObserver>() {
    11. override fun onChanged(t: List?) {
    12. Toast.makeText(this@LiveEventBusActivity, "接受到 List 事件: $it", Toast.LENGTH_LONG).show()
    13. }
    14. }.setSticky(false).setObserveOnlyActive(false))
    15. }
    16. findViewById
    17. it.postDelayed({ LiveEventBus.get(Int::class.java).value = 123 }, 3000)
    18. }
    19. findViewById
    20. it.postDelayed({
    21. LiveEventBus.get(object :
    22. TypeToken>() {}).value = listOf("Hello LiveEventBus")
    23. }, 5000)
    24. }

     

    可以看到观察者处于后台也接收到了事件,所以这个问题也得以解决。

    四、总结

    上面只是简单实现了事件总线的效果,但也由此可见使用 LiveData 来封装事件总线的可行性,对于上述两个关键问题,相信看过 LiveData 源码后对其修改并不难,所以万事还要究其根本。

    如果还需要更多如指定观察者线程、观察者优先级的功能,可以参照 EventBus 进行进一步封装。

    五、Demo 地址

    禽兽先生/JetpackDemo-LiveEventBusicon-default.png?t=M85Bhttps://gitee.com/MrQinshou/jetpack-demo/tree/master/app/src/main/java/com/qinshou/jetpackdemo/liveeventbus

  • 相关阅读:
    10分钟看懂Docker和K8S,docker k8s 区别
    二叉树中的深搜之二叉树的所有路径
    .NET性能优化-快速遍历List集合
    Qt多文本编辑器项目实战
    python3字符串内建方法split()心得
    每次启动Docker容器指定IP、hosts和端口
    Mybatis逆向工程---在SpringBoot项目中构建Mybatis生成器
    Android 如何根据区域高度动态计算最匹配的字体大小
    Docker入门学习:基本概念、安装、命令、简单使用
    【23级红细胞招新模拟训练(部分题解 不包含最后三题】
  • 原文地址:https://blog.csdn.net/zgcqflqinhao/article/details/124424066