前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >100行代码拆解EventBus核心逻辑(一)

100行代码拆解EventBus核心逻辑(一)

作者头像
阳仔
发布2019-11-23 08:50:53
4100
发布2019-11-23 08:50:53
举报
文章被收录于专栏:终身开发者

Github:https://github.com/hylinux1024 微信公众号:终身开发者(angrycode)

EventBus 作为一个基础的消息传递组件,了解其核心实现原理是日常开发工作之外需要做的必修课。本系列希望通过自己实现一个类似的消息传递组件 EasyBus 来理解 EventBus 的核心实现原理。

从官方的原理图可以直观的看出 EventBus 是一个基于订阅发布的消息传递组件。订阅者通过 EventBus 进行注册,登记自己感兴趣的事件 Event,发布者将 Event 发送到 EventBus 后就将会查询监听事件 Event 的订阅者,然后将事件 Event 转发到订阅者中。

可以发现其实就是观察者模式的实现。

观察者模式中,被观察者( Subject)内部会通过 List 来存储观察者的实例,然后就通过 notify() 方法可以通知观察者。 在 EventBus 库中,定义了 onEventXXXX(MessageEvent) 方法的类就是观察者,而 EventBus 自己就是被观察者

接下来实现一个简易版本的消息传递 EasyBus

首先预览下整体的类结构

代码语言:javascript
复制
com/github/easybus├── EasyBus.java├── Logger.java├── SubscriberMethod.java├── Subscription.java└── demo    ├── MainActivity.kt    └── MessageEvent.java

参考 EventBus 定义了一个 EasyBus 类,这个将是我们使用 EasyBus 的主要入口。

单例方法
代码语言:javascript
复制
public class EasyBus {    ...    private EasyBus() {        eventTypeBySubscriber = new HashMap<>();        subscriptionsByEventType = new HashMap<>();    }
    /**     * 使用静态内部类的方式实现单例     */    private static class Holder {        static EasyBus instance = new EasyBus();    }
    public static EasyBus getInstance() {        return Holder.instance;    }    ...}

EasyBus 中使用静态内部类的方式实现单例,这样可以保证在一个进程内只有一个 EasyBus 的实例。 这里有两个变量需要重点解释一下:

代码语言:javascript
复制
    /**     * 通过 EventType 查询订阅者信息(即注册EasyBus的Class类以及回调的通知方法)     * 当调用post方法的时候,可以通过Event类查询到对应的订阅者信息     * 从而快速的执行通知接口     */    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;    /**     * 通过订阅者(即注册 EasyBus 的类实例)查询事件 EventType Class     * 事件类型即为 onEvent 方法中的参数类型     */    private final Map<Object, List<Class<?>>> eventTypeBySubscriber;

这两个变量都是 Map 类型。subscriptionsByEventTypekeyEvent 类的类型, value 则订阅者的列表信息,通过 Subscription 类来封装;而 eventTypeBySubscriberkey 是订阅者实例, valueEvent 类的类型。

回忆上面的观察者模式类图,被观察者需要有一个 List 用来存储观察者列表,这里的观察者列表就是通过上面两个 Map 变量来实现的。

register
代码语言:javascript
复制
public void register(Object subscriber) {        Class<?> subscriberClass = subscriber.getClass();        Method[] methods = subscriberClass.getDeclaredMethods();        List<SubscriberMethod> subscriberMethods = new ArrayList<>();        for (Method method : methods) {            Class<?>[] parameterTypes = method.getParameterTypes();            if (parameterTypes.length != 1) {                continue;            }            if (method.getName().startsWith("onEvent")) {                subscriberMethods.add(new SubscriberMethod(method, parameterTypes[0]));            }        }        synchronized (this) {            for (SubscriberMethod method : subscriberMethods) {                subscribe(subscriber, method);            }        }    }

注册方法的作用是解析观察者的方法和事件类型,然后将观察者信息添加到 被观察者中的 List 里。实现逻辑也很简单,通过 Class 类进行反射调用类中的方法,过滤参数个数为1,且方法名称是以 onEvent* 开头的方法,就把其添加到 subscriberMethods 列表中。其中 SubscriberMethod 是订阅者的接收通知的方法(可以对比观察者模式中的 update 方法),它封装了方法名称、和方法中的参数类型(即事件类型)。

接下来就调用 subscribe 方法实现订阅逻辑。

代码语言:javascript
复制
private void subscribe(Object subscriber, SubscriberMethod method) {        Logger.i(subscriber.getClass().getSimpleName() + ":" + method.method.getName());        // 查询这个事件类型中对应的订阅者信息(即接收相同事件的注册者的方法有哪些)        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(method.eventType);        if (subscriptions == null) {            subscriptions = new CopyOnWriteArrayList<>();        }        subscriptions.add(new Subscription(subscriber, method));        subscriptionsByEventType.put(method.eventType, subscriptions);        // 查询注册者中的接收的事件 Event 类型(即 onEvent 方法中的参数类型)        List<Class<?>> eventTypes = eventTypeBySubscriber.get(subscriber);        if (eventTypes == null) {              = new CopyOnWriteArrayList<>();        }        eventTypes.add(method.eventType);
        eventTypeBySubscriber.put(subscriber, eventTypes);    }

该方法的主要作用就是将上面解析到的方法和事件类型添加到 subscriptionsByEventTypeeventTypeBySubscriber 这两个 Map 中。 首先查询 subscriptionsByEventType 是否有对应的订阅信息,如果没有则初始化列表 subscriptions 。然后将订阅者实例( subscriber)与订阅者中的方法( method)封装成 Subscription 。最后添加到 subscriptions 中。 同样地查询 eventTypeBySubscriber 中是否有对应的事件类型,没有则初始化列表 eventTypes。之后将 eventTypes 存储在 eventTypeBySubscriber 这个 Map 里。

post
代码语言:javascript
复制
public void post(Object event) {
        List<Subscription> subscriptionList = subscriptionsByEventType.get(event.getClass());        if (subscriptionList == null) {            Logger.i("not found subscriber");            return;        }        for (Subscription subscription : subscriptionList) {            subscription.notifySubscriber(event);        }    }

post 逻辑也很简单,主要通过事件类型查询对应的订阅者信息 List ,然后遍历该列表进行消息事件通知。

订阅者信息类主要封装订阅者实例信息和对应的方法信息

代码语言:javascript
复制
final class Subscription {    final Object subscriber;    final SubscriberMethod subscriberMethod;
    public Subscription(Object subscriber, SubscriberMethod subscriberMethod) {        this.subscriber = subscriber;        this.subscriberMethod = subscriberMethod;    }
    public void notifySubscriber(Object event) {        try {            subscriberMethod.method.invoke(subscriber, event);        } catch (Exception e) {            e.printStackTrace();        }    }    ...}

Subscription 中还有一个重要的方法 notifySubscriber , 它是通过反射对订阅者的方法进行调用。

unregister

梳理了上面的 register 方法 unregister 方法就比较容易理解了。

代码语言:javascript
复制
public void unregister(Object subscriber) {        List<Class<?>> eventTypes = eventTypeBySubscriber.get(subscriber);        if (eventTypes != null) {            for (Class<?> eventType : eventTypes) {                unsubscribe(subscriber, eventType);            }        }    }

通过订阅者实例信息从 eventTypeBySubscriber 查询到订阅的事件类型列表,然后遍历 eventTypes 执行 unsubscribe 方法。

代码语言:javascript
复制
private void unsubscribe(Object subscriber, Class<?> eventType) {        List<Subscription> subscriptionList = subscriptionsByEventType.get(eventType);        if (subscriptionList == null) {            return;        }        int size = subscriptionList.size();        for (int i = 0; i < size; i++) {            if (subscriptionList.get(i).subscriber == subscriber) {                subscriptionList.remove(i);                i--;                size--;            }        }    }

unsubscribe 方法中将订阅者信息从订阅者列表中移除,就完成了退订功能。

引用
  • https://github.com/greenrobot/EventBus
  • https://github.com/hylinux1024/EasyBus
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-11-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 终身开发者 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单例方法
  • register
  • post
  • unregister
  • 引用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档