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方法切到子线程。

相关推荐
丘狸尾35 分钟前
[cisco 模拟器] ftp服务器配置
android·运维·服务器
van叶~3 小时前
探索未来编程:仓颉语言的优雅设计与无限可能
android·java·数据库·仓颉
Crossoads6 小时前
【汇编语言】端口 —— 「从端口到时间:一文了解CMOS RAM与汇编指令的交汇」
android·java·汇编·深度学习·网络协议·机器学习·汇编语言
古木20198 小时前
前端面试宝典
前端·面试·职场和发展
li_liuliu8 小时前
Android4.4 在系统中添加自己的System Service
android
C4rpeDime10 小时前
自建MD5解密平台-续
android
鲤籽鲲11 小时前
C# Random 随机数 全面解析
android·java·c#
码农爱java15 小时前
设计模式--抽象工厂模式【创建型模式】
java·设计模式·面试·抽象工厂模式·原理·23种设计模式·java 设计模式
Jiude15 小时前
算法题题解记录——双变量问题的 “枚举右,维护左”
python·算法·面试
m0_5485147715 小时前
2024.12.10——攻防世界Web_php_include
android·前端·php