EventBus 3.1.1 源码解析

本文要解决的问题

markdown 复制代码
      1.EventBus是如何通过订阅的方式实现消息传递的?
      2.什么是粘性事件?如何实现的?
      3.Eventbus内部线程调度是如何实现的?
 
 

1.EventBus是如何通过订阅的方式实现消息传递的?

首先我们先来看一下我们在执行 EventBus.getDefault().register() 的时候做了哪些事情

ini 复制代码
public void register(Object obj){
    // 先去数据源中获取subcribleMethods,如果不存在,则去寻找方法并添加
    List<SubscribleMethod> list = cacheMap.get(obj);
    if(list == null){
        list = findSubscribleMethods(obj);
        cacheMap.put(obj,list);
    }
}

public List<SubscribleMethod> findSubscribleMethods(Object obj){
    List<SubscribleMethod> list = new ArrayList<>();
    Class<?> clazz = obj.getClass();
    // 循环去查找父类是否存在subscrible注解方法
    while (clazz != null){

        // 判断当前是否是系统类,如果是,就退出循环
        String name = clazz.getName();
        if(name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")){
            break;
        }

        // 得到所有的方法,这个clazz在本项目中,暂时指代的是MainActivity
        Method[] methods = clazz.getMethods();

        for(Method method : methods){
            // 通过注解找到我们需要注册的方法
            Subscribe subscribe = method.getAnnotation(Subscribe.class);
            if(subscribe == null){
                continue;
            }
            // 获取方法中的参数,并判断
            Class<?>[] types = method.getParameterTypes();
            if(types.length != 1){
                throw new RuntimeException("EventBus只能接受一个参数");
            }
            // 获取线程模式
            ThreadMode threadMode = subscribe.threadMode();
            SubscribleMethod subscribleMethod = new SubscribleMethod(method,threadMode,types[0]);
            list.add(subscribleMethod);
        }
        clazz = clazz.getSuperclass();
    }
    return list;
}

通过上面代码我们可以知道EventBus在register的时候是讲注册类的所有订阅方法subscribleMethod都添加到了cacheMap里面,通过Subscribe注解我们可以获取订阅方法,方法参数和线程模式。接下来看一下 EventBus.getDefault().post(bean)都做了哪些事情。

typescript 复制代码
public void post(final Object type){
    Set<Object> set = cacheMap.keySet();
    Iterator<Object> iterator = set.iterator();
    while (iterator.hasNext()){
        final Object obj = iterator.next();
        List<SubscribleMethod> list = cacheMap.get(obj);
        for(final SubscribleMethod subscribleMethod : list){
            // 简单的理解:两个列对比一下,看看是否一致 (不严谨的说法)
            // a(subscribleMethod.getType())对象所对应的类信息,是b(type.getClass())对象所对应的类信息的父类或者父接口
            if(subscribleMethod.getType().isAssignableFrom(type.getClass())){
                switch (subscribleMethod.getThreadMode()){
                    // 不管你在post是在主线程 还是在子线程,我都在主线程接受
                    case MAIN:
                        // 主 - 主
                        if(Looper.myLooper() == Looper.getMainLooper()){
                            invoke(subscribleMethod,obj,type);
                        }else{
                            // 子 - 主
                            mHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    invoke(subscribleMethod,obj,type);
                                }
                            });
                        }
                        break;
                    // 不管你在post是在子线程还是在主线程,我都在子线程接受
                    case BACKGROUND:
                        // 主 - 子
                        if(Looper.myLooper() == Looper.getMainLooper()){
                            mExecutorService.execute(new Runnable() {
                                @Override
                                public void run() {
                                    invoke(subscribleMethod,obj,type);
                                }
                            });
                        }else{
                            // 子 - 子
                            invoke(subscribleMethod,obj,type);
                        }
                        break;
                }
            }
        }
    }
}

通过上面代码我们可以知道post方法就是通过cacheMap集合进行遍历,通过post传递的bean类型从cacheMap集合里面去匹配subscribleMethod里面对应的订阅方法,然后通过method.invoke()反射的方式,最终执行订阅方法。其中涉及到线程切换也是通过handler、looper机制实现。由此我们可以知道,EventBus消息传递机制就是通过注解和反射原理实现事件总线这种解耦的特性。

2.什么是粘性事件?如何实现的?

erlang 复制代码
粘性事件,是指在发送事件之后再订阅该事件也能收到该事件,这就使得我们可以预先处理一些事件,让有消费者时再把这些事件投递给消费者.
typescript 复制代码
           发送粘性事件
    public void sticky(View view) {
        EventBus.getDefault().postSticky(new EventBean("abc"));
    }

            接收粘性事件
    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void receiveSoundRecongnizedmsg(EventBean bean) {
          L.i(bean.getName());
    }

存储粘性事件

csharp 复制代码
  public void postSticky(Object event) {
        synchronized (stickyEvents) {
          //存储
            stickyEvents.put(event.getClass(), event);
        }
        // Should be posted after it is putted, in case the subscriber wants to remove immediately
        post(event);
    }

通过源码得知当粘性事件发送之后就会被存储到stickyEvents队列里面,当有相对应的订阅的时候才会收到粘性事件。

scss 复制代码
         //当订阅的事件为粘性时 执行以下方法
        if (subscriberMethod.sticky) {
            if (eventInheritance) {
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    Class<?> candidateEventType = entry.getKey();
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        //获取粘性事件
                        Object stickyEvent = entry.getValue();
                        //执行对应的订阅方法
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }

当订阅方法,粘性参数为true的时候,会去检索粘性事件存储队列,当队列有匹配的对象的时候执行订阅方法。

vbnet 复制代码
 public boolean removeStickyEvent(Object event) {
        synchronized (stickyEvents) {
            Class<?> eventType = event.getClass();
            Object existingEvent = stickyEvents.get(eventType);
            if (event.equals(existingEvent)) {
                stickyEvents.remove(eventType);
                return true;
            } else {
                return false;
            }
        }
    }

移除粘性事件(当粘性事件处理过之后,需移除粘性事件,否则这个事件会一直存储在队列里面,每次启动都会执行订阅方法) 我们看到移除粘性事件方法是从stickyEvents队列里面找到匹配的对象,然后移除,此事件就不会被重复执行了。

3.Eventbus内部线程调度是如何实现的?

arduino 复制代码
public enum ThreadMode {
     // 事件的处理和事件的发送在相同的进程
    POSTING,
    //事件的处理会在UI线程执行
    MAIN,
    //后台进程,处理如保存到数据库
    BACKGROUND
    //异步执行,另起线程操作,事件的处理会在单独的线程执行,主要用于后台线程中耗时操作
    ASYNC
}

我们知道eventbus的订阅方法有以上四种线程模式,那么event是如何从发送者的线程切换到接收方的线程的呢。

csharp 复制代码
  private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    //从子线程切换到主线程
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED:
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    // 从主线程切切换到子线程
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:
              //另起子线程
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

由此可以得知eventbus是通过 mainThreadPoster; backgroundPoster; asyncPoster的enqueue方法进行线程调度的。

scala 复制代码
public class HandlerPoster extends Handler implements Poster {}

final class BackgroundPoster implements Runnable, Poster {}

class AsyncPoster implements Runnable, Poster {}

通过上面代码我们可以得知 mainThreadPoster; backgroundPoster; asyncPoster这三个类分别继承自Handler Runnable Runnable,也就是说EventBus线程切换底层是通过Handle的handleMessage()方法回到主线程,通过Runnable的run方法切到子线程。

相关推荐
鱼跃鹰飞6 小时前
大厂面试真题-简单说说线程池接到新任务之后的操作流程
java·jvm·面试
帅得不敢出门8 小时前
安卓设备adb执行AT指令控制电话卡
android·adb·sim卡·at指令·电话卡
程序员清风9 小时前
浅析Web实时通信技术!
java·后端·面试
我又来搬代码了9 小时前
【Android】使用productFlavors构建多个变体
android
测试199810 小时前
外包干了2年,快要废了。。。
自动化测试·软件测试·python·面试·职场和发展·单元测试·压力测试
mingzhi6111 小时前
渗透测试-快速获取目标中存在的漏洞(小白版)
安全·web安全·面试·职场和发展
嚣张农民11 小时前
一文简单看懂Promise实现原理
前端·javascript·面试
德育处主任11 小时前
Mac和安卓手机互传文件(ADB)
android·macos
芦半山11 小时前
Android“引用们”的底层原理
android·java
迃-幵12 小时前
力扣:225 用队列实现栈
android·javascript·leetcode