Android-Jetpack

如何优雅的使用LiveData实现一套EventBus(事件总线

2020-06-09  本文已影响0人  Codyer

前言

EventBus大家都很熟悉了,各种实现方式也是层出不穷,然而,作为有追求的程序员们,永远在不停的造轮子,毕竟,在程序员的眼中,至今,没有哪个轮子看上去是完美无暇的。
因此,作为有追求的程序员中的一员,我也想假装很权威的站出来,然后无所畏惧的从远古时期讲讲事件总线的来龙去脉。
有兴趣的小伙伴可以搬个椅子听我白话一番。也许可以给你带来不一样的视野。

一、什么是EventBus(事件总线)?


1) 要搞清楚什么是事件总线,我们先了解一下什么叫总线:

总线(Bus)是计算机各种功能部件之间传送信息的公共通信干线,它是由导线组成的传输线束, 按照计算机所传输的信息种类,计算机的总线可以划分为数据总线地址总线控制总线,分别用来传输数据、数据地址和控制信号。总线是一种内部结构,它是cpu、内存、输入、输出设备传递信息的公用通道,主机的各个部件通过总线相连接,外部设备通过相应的接口电路再与总线相连接,从而形成了计算机硬件系统。在计算机系统中,各个部件之间传送信息的公共通路叫总线,微型计算机是以总线结构来连接各个功能部件的。

2) 在总线上传输的数据分很多种,根据用途可以分为以下几种:

其实上面的总线都是实际物理存在的概念,而事件本身也是信息的一种形式,为了理解方便,在软件开发领域,大家便把,在多个模块间,为事件提供传输通道的组件或者说库,称之为事件总线,也可以称之为消息总线

二、为什么要使用事件总线?


1) 搞清楚事件总线的定义之后,我们来分析一下,为什么我们需要使用事件总线:

可以看到,如上图所示,无论是作为发布事件的组件,还是订阅事件的组件,在总线的帮助下,都可以很好的进行解藕。

插播说明:我们发现发布-订阅模式包含生产-消费者模式,也包含观察-被观察者模式,其中还有中介者模式,其实,我们常说的23种设计模式,大部分时候并不是单独存在的,那只是最小设计模式单元,为了便于理解,以上图为参考画出如下两种模式,对比一下你就明白了,这些模式之间的微妙关系。

image

三、EventBus常见实现方式


结合上一章节对事件总线的了解,我们知道,事件总线必不可缺的元素就是事件,另一个必备点就是,当有新的事件时可以通知到关心的模块,不然其他模块总要隔三差五的来询问有没有事件,那样效率也太低了吧。

所以事件总线的订阅和发布是逃不了了,观察者模式也是板上钉钉的了,为了后面更顺畅,在说明常见事件总线实现方式之前,我们还得补充一个小知识:(觉得长的这块也可以略过)

1) 要实现事件总线,就涉及到事件(信息)的流转分发,因此,在此之前我们还应该聊一聊信息流转方式,而信息流转又分为进程内流转进程间流转

综上所见,可能还有其他方式实现事件总线,可以看到各有优缺点,其实也没有说谁是最好的实现,存在即合理,每种方式都有自己的生存空间,合适即合理,没有最好的,只有最合适的。

四、LiveData简介


上面巴拉巴拉了那么多,其实就是想从一个大局方向分析一下事件总线的来龙去脉,也为下面用LiveData实现事件总线做一个伏笔。
虽然市面上有很多事件总线的实现方式,但是基于简约原则,少即是多,越少变数越安全的理念,能不引入新的第三方时,我尽量使用原生实现。这个带来的好处有几个:

那么,什么是LiveData呢?虽然很多人都很熟悉了,但是这里还是本着尽量不让一个人落下的原则,还是介绍一下:

LiveData是一种可观察的数据存储器类。与常规的可观察类不同,LiveData具有生命周期感知能力,意指它遵循其他应用组件(如 ActivityFragmentService)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

生命周期感知能力,是不是听上去很高级,我是觉得很高级,像拥有超能力一样。
那么这种超能能力又有什么优点呢?

LiveData 遵循观察者模式。当生命周期状态发生变化时,LiveData 会通知 Observer 对象。您可以整合代码以在这些 Observer 对象中更新界面。观察者可以在每次发生更改时更新界面,而不是在每次应用数据发生更改时更新界面。

观察者会绑定到 Lifecycle对象,并在其关联的生命周期遭到销毁后进行自我清理。

如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。

界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。

如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。

如果由于配置更改(如设备旋转)而重新创建了 ActivityFragment,它会立即接收最新的可用数据。

您可以使用单一实例模式扩展 LiveData对象以封装系统服务,以便在应用中共享它们。LiveData 对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察 LiveData 对象。

看了上面这些描述,是不是和我一样觉得LiveData很高级?下面我们就开始把这高级的东西用起来吧!

五、用LiveData实现简单的LiveEventBus


话不多说,直接上代码:

import androidx.lifecycle.MutableLiveData;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by cody.yi. on 2020/6/9.
 * LiveEventBus
 */
public class LiveEventBus {

    public static <T> MutableLiveData<T> getDefault(String key, Class<T> clz) {
        return ready().with(key, clz);
    }

    private final Map<String, MutableLiveData<Object>> bus;

    private LiveEventBus() {
        bus = new HashMap<>();
    }

    private static class InstanceHolder {
        static final LiveEventBus INSTANCE = new LiveEventBus();
    }

    private static LiveEventBus ready() {
        return LiveEventBus.InstanceHolder.INSTANCE;
    }

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

使用步骤

LiveEventBus.getDefault("Event1",String.class).setValue("推送数据1");
 或者在非主线程时使用
LiveEventBus.getDefault("Event1",String.class).postValue("推送数据1");
LiveEventBus.getDefault("Event1",String.class).observe(this, new Observer<String>() {
            @Override
            public void onChanged(final String event) {
                Toast.makeText(BusDemoActivity.this, "监听到数据 -> " + event, Toast.LENGTH_SHORT).show();
            }
        });

简单两步实现了事件总线,而且使用简单,不需要手动注册反注册操作。LiveData具有的优势它都有,是不是很香?

六、简单LiveEventBus包含的问题


其实简单需求,上面已经够用了,但是看了LiveData的源码就会发现,其实还有几个小瑕疵,在某些特殊情况下会有点问题,下面先逐一分析问题,然后统一给出最终的解决方案。

private final Runnable mPostValueRunnable = new Runnable() {
        @Override
        public void run() {
            Object newValue;
            synchronized (mDataLock) {
                newValue = mPendingData;
                // 缓存里的值已经被发送,被发送了就回到未设置状态
                mPendingData = NOT_SET;
            }
            setValue((T) newValue);
        }
    };

protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            // 缓存里面是否还有值已被发送
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if (!postTask) {//就是这块返回,导致并不是每个数据都会被执行
            return;
        }
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }

其实这个问题,我们可以自己把postValue重写一下就可以了。

设计思路以及部分代码

1、通过包装类自己管理注册序号(源码通过版本管理),解决粘性问题。
2、实现自己的post方式,统一判断当前所在线程,调用合适的方式。
3、通过定义事件Event和范围EventGroup,给事件定义含义以及数据类型,然后使用APT技术自动生成需要使用的事件类。
4、提供统一调用方式,也提供简单测试方式,支持扩展。
5、进程间消息总线扩展,使用AIDL和Messenger两种实现方式。

事件值包裹类:

final class ValueWrapper<T> {
    // 每个被观察的事件数据都有一个序号,只有产生的事件数据在观察者加入之后才通知到观察者
    // 即事件数据序号要大于观察者序号
    final int sequence;
    @NonNull
    final
    T value;

    ValueWrapper(@NonNull T value, int sequence) {
        this.sequence = sequence;
        this.value = value;
    }
}

观察者包裹类:

public abstract class ObserverWrapper<T> {
    // 每个观察者都记录自己序号,只有在进入观察状态之后产生的数据才通知到观察者
    int sequence;
    Observer<ValueWrapper<T>> observer;
}

LiveData包裹类:

/**
 * Created by xu.yi. on 2019/3/31.
 * 和lifecycle绑定的事件总线
 * 每添加一个observer,LiveEventWrapper 的序列号增加1,并赋值给新加的observer,
 * 每次消息更新使用目前的序列号进行请求,持有更小的序列号才需要获取变更通知。
 * <p>
 * 解决会收到注册前发送的消息更新问题
 */
@SuppressWarnings("unused")
public class LiveEventWrapper<T> {
    private int mSequence = 0;
    private final MutableLiveData<ValueWrapper<T>> mMutableLiveData;

    public LiveEventWrapper() {
        mMutableLiveData = new MutableLiveData<>();
    }

   /**
     * 如果在多线程中调用,保留每一个值
     * 无需关心调用线程,只要确保在相同进程中就可以
     *
     * @param value 需要更新的值
     */
    public void post(@NonNull T value) {
        checkThread(() -> setValue(value));
    }
   /**
     * 设置监听之前发送的消息不可以接收到
     * 重写 observer 的函数 isSticky ,返回true,可以实现粘性事件
     *
     * @param owner           生命周期拥有者
     * @param observerWrapper 观察者包装类
     */
    public void observe(@NonNull LifecycleOwner owner, @NonNull ObserverWrapper<T> observerWrapper) {
        observerWrapper.sequence = observerWrapper.isSticky() ? -1 : mSequence++;
        checkThread(() -> mMutableLiveData.observe(owner, filterObserver(observerWrapper)));
    }

 /**
     * 是否是在主线程
     *
     * @return 是主线程
     */
    private boolean isMainThread() {
        return Looper.getMainLooper().getThread() == Thread.currentThread();
    }

    /**
     * 检查线程并执行不同的操作
     *
     * @param runnable 可运行的一段代码
     */
    private void checkThread(Runnable runnable) {
        if (isMainThread()) {
            runnable.run();
        } else {
            // 主线程中观察
            BusFactory
                    .ready()
                    .getMainHandler()
                    .post(runnable);
        }
    }
}

从包装类中过滤出原始观察者:

    /**
     * 从包装类中过滤出原始观察者
     *
     * @param observerWrapper 包装类
     * @return 原始观察者
     */
    @NonNull
    private Observer<ValueWrapper<T>> filterObserver(@NonNull final ObserverWrapper<T> observerWrapper) {
        if (observerWrapper.observer != null) {
            return observerWrapper.observer;
        }
        return observerWrapper.observer = valueWrapper -> {
            // 产生的事件序号要大于观察者序号才被通知事件变化
            if (valueWrapper != null && valueWrapper.sequence > observerWrapper.sequence) {
                if (observerWrapper.uiThread()) {
                    observerWrapper.onChanged(valueWrapper.value);
                } else {
                    BusFactory
                            .ready()
                            .getExecutorService()
                            .execute(() -> observerWrapper.onChanged(valueWrapper.value));
                }
            }
        };
    }

通过APT动态生成代码的部分,因为比较简单,对APT感兴趣的可以去看一下相关文章,也可以参考一下我的源码。

高端使用方式
1、 定义事件

//定义了事件范围为demo,激活
@EventGroup(value = "TestScope", active = true)
public class EventDefine {
    @Event(description = "eventInt 事件测试", multiProcess = false, active = true)
    Integer eventInt;

    @Event(description = "eventString 事件测试", multiProcess = true, active = true)
    String eventString;

    @Event(description = "eventBean 事件测试", multiProcess = true, active = true)
    JavaBean eventBean;
}

说明
其实事件定义只用到两个注解

1)、@EventGroup 使用在 class 上,定义事件分组名,是否激活

2)、@Event 使用在变量上,定义具体 事件描述,是否激活,是否支持多进程

定义完注解后,通过前面导入的注解处理器 annotationProcessor ,ElegantBus 会自动生成以 EventGroup 定义的分组名的事件总线 例如上面的定义就会生成一个 TestScopeBus

然后我们所有地方就可以直接使用这个事件总线进行事件管理。

2、发送事件和监听事件:

TestScopeBus.eventInt().post(888);
TestScopeBus.eventString().post("新字符串");
TestScopeBus.eventBean().post(new JavaBean());
TestScopeBus.eventInt().observe(owner, new ObserverWrapper<Integer>() {
    @Override
    public void onChanged(final Integer value) {
        ...
    }
});

更多细节可以查看源码https://github.com/codyer/ElegantBus

七、ElegantBus 如何进行更多扩展


----------------------------------------------------------- by Cody.yi

谢谢阅读

如有错误缺漏之处欢迎指正!

上一篇下一篇

猜你喜欢

热点阅读