首页 > 编程语言 >EventBus 源码分析 - 注解 + 反射

EventBus 源码分析 - 注解 + 反射

时间:2023-06-19 10:34:34浏览次数:44  
标签:subscriberMethod findState class 源码 注解 EventBus event subscription


EventBus 源码解析

随着 LiveDataKotlin Flow 的出现,EventBus 已经慢慢过时了。不过 EventBus 源码的设计思想以及实现原理还是值得我们去学习的。

getDefault() 方法

EventBus().getDefault().register(this)

首先 EventBus 的创建用到了 DCL 单例模式,源码如下:

public class EventBus {
   
    static volatile EventBus defaultInstance;
   
    public static EventBus getDefault() {
        EventBus instance = defaultInstance;
        if (instance == null) {
            synchronized (EventBus.class) {
                instance = EventBus.defaultInstance;
                if (instance == null) {
                    instance = EventBus.defaultInstance = new EventBus();
                }
            }
        }
        return instance;
    }
}

register() 方法第一部分

示例:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
       
        EventBus.getDefault().register(this);
    }

    /**
     * threadMode 执行的线程方式
     * priority 执行的优先级
     * sticky 粘性事件
     */
    @Subscribe(threadMode = ThreadMode.MAIN,priority = 50,sticky = true)
    public void test1(String msg){
        // 如果有一个地方用 EventBus 发送一个 String 对象,那么这个方法就会被执行
        Log.e("TAG","msg1 = "+msg);
    }
    
    @Subscribe(threadMode = ThreadMode.MAIN,priority = 100,sticky = true)
    public void test2(String msg){
        // 如果有一个地方用 EventBus 发送一个 String 对象,那么这个方法就会被执行
        Log.e("TAG","msg2 = "+msg);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        
        EventBus.getDefault().unregister(this);
    }
}

EventBus().getDefault().post("text")

register() 方法源码:

public void register(Object subscriber) {
    
    // 获取 class 对象
    Class<?> subscriberClass = subscriber.getClass();
    
    // 遍历这个 class 的所有方法,将含有 @Subscribe 注解的方法找出来
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

这里的 subscriber 就是我们的 MainActivity,第一行代码很简单,就是获取 MainActivityclass 对象。

第二行代码的意思:遍历这个 class 的所有方法,将含有 @Subscribe 注解的方法找出来,并将它们封装成 List<SubscriberMethod>SubscriberMethod 的成员变量如下所示:

EventBus 源码分析 - 注解 + 反射_List

我们来看下 findSubscriberMethods 源码:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {

    // 先从缓存里面读取
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }

    if (ignoreGeneratedIndex) {
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

这里的 subscriberClass 就是我们的 MainActivityclass 对象。

ignoreGeneratedIndex 这个属性表示是否忽略注解成器生成的 EventBusIndex.class 文件, 我们只要知道 ignoreGeneratedIndex 默认是 false 就行了。

所以会走 findUsingInfo(subscriberClass) 方法,我们来看下源码:

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        findState.subscriberInfo = getSubscriberInfo(findState);
        if (findState.subscriberInfo != null) {
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            for (SubscriberMethod subscriberMethod : array) {
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            // 走这个方法
            findUsingReflectionInSingleClass(findState);
        }
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}

这里我直接说结论,因为我们没有使用编译时注解生成 EventBusIndex.class 文件,所以 findState.subscriberInfo 为空,所以这里会走 findUsingReflectionInSingleClass(findState) 方法,最终就是通过反射去找。我们来看下 findUsingReflectionInSingleClass 源码:

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
       ......
        }
    }
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 1) {
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    Class<?> eventType = parameterTypes[0];
                    if (findState.checkAdd(method, eventType)) {
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            } 
        } 
    }
}

第一行代码 findState.clazz.getDeclaredMethods(); 就是通过反射获取订阅类的所有方法。

然后遍历 methods,判断方法的修饰符是不是 public,然后获取方法参数的 class,并判断是不是只有一个参数,并且还判断是否被 @Subscribe 修饰。method.getParameterTypes() 就是获取方法的参数类型。String msg 获取到的就是 String.class,如下:

EventBus 源码分析 - 注解 + 反射_android_02

遍历完 methods 之后,就会将所获得的属性添加到 subscriberMethods 中,如下:

findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky()));

最后通过 findStatesubscriberMethods 返回回去。也就是 register() 方法中的List<SubscriberMethod>

第一部分小结

public void register(Object subscriber) {
    
    // 获取 class 对象
    Class<?> subscriberClass = subscriber.getClass();
    
    // 遍历这个 class 的所有方法,将含有 @Subscribe 注解的方法找出来
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

subscriberMethodFinder.findSubscriberMethods() 方法就是通过反射去解析注册者(MainActivity)的所有方法。并且找出被 @Subscribe 注解修饰的方法,然后通过 Annotation 解析所有需要的参数(包括 threadModeprioritysticky、参数的类型 eventTypemethod)。最后把这些参数封装成 SubscriberMethod 添加到集合返回。

EventBus 源码分析 - 注解 + 反射_kotlin_03

register() 方法第二部分

接下来我们来分析 subscribe 方法:

synchronized (this) {
    for (SubscriberMethod subscriberMethod : subscriberMethods) {
        subscribe(subscriber, subscriberMethod);
    }
}

我们先看一下 subscribe() 方法的参数,subscriber 参数就是我们的 MainActivitysubscriberMethod 参数是我们上面对方法的封装。

我们来看一下 subscribe() 方法的源码:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    // eventType 是方法参数的 class,也就是 String.class
    Class<?> eventType = subscriberMethod.eventType;
    
    // 又封装了一个 Subscription 对象 subscriber : MainActivity, subscriberMethod : 解析好的方法
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    
    // 第一次拿为空,put
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        ......
    }

    // 优先级排序 
    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }

    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    // 粘性事件的处理
    ```
}

这里又封装了一个 Subscription 对象,成员变量就是:

  • subscriber 参数就是我们的 MainActivity
  • subscriberMethod 参数是我们上面对方法的封装

接下来用 subscriptionsByEventTypeSubscription 对象存起来,subscriptionsByEventType 是个 Map,如下:

private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
  • key : Class<?>(参数类型,也就是 Strin.class)
  • value : CopyOnWriteArrayList<Subscription>(线程安全的 ArrayList,里面是 Subscription)

subscriptionsByEventType.get(eventType) 第一次拿肯定为空,所以会调用 subscriptionsByEventType.put(eventType, subscriptions)。到这里,subscriber 方法就分析好了。

第二部分小结

解析所有的 subscriberMethodeventType,然后将它们封装成 Map<Class<?>, CopyOnWriteArrayList<Subscription>> 类型的 subscriptionsByEventTypekeyeventTypevalueSubscription 的列表,Subscription 包含两个属性 subscribersubscriberMethod

EventBus 源码分析 - 注解 + 反射_android_04

post() 方法

其实根据上面那个图,我们很容易猜出 post() 方法肯定会遍历 subscriptionsByEventType,找到一样的 eventType,然后去执行 CopyOnWriteArrayList 列表里面的 Subscription 对象里面的 subscriberMethod 对象里面的 method

我们来看下 post() 方法源码:(经过一系列调用)

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {

    CopyOnWriteArrayList<Subscription> subscriptions;
    
    synchronized (this) {
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    
    if (subscriptions != null && !subscriptions.isEmpty()) {
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted;
            try {
                postToSubscription(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}

这里的 event 就是 post("text") 里面的 text,所以 eventClass 就是 String.class,然后执行 subscriptions = subscriptionsByEventType.get(eventClass),就会将我们上面图片中的 value 取出来。然后遍历 subscriptions,执行 postToSubscription(subscription, event, postingState.isMainThread) 方法,我们看下这个方法的源码:

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);
    }
}

我们的 threadMode 有四种形式:

  • POSTING:同一个线程,在哪个线程发送,就在哪个线程执行。
  • MAIN:在主线中执行。
  • BACKGROUND:子线程,如果发送事件是主线程,那么久调用线程池切换线程来执行方法。
  • ASYNC:异步线程,无论发布时间是主线程还是子线程,都有通过线程池切换线程执行。

所以我们看到源码中,如果是 POSTING 方式就直接调用 invokeSubscriber(subscription, event) 执行方法。其他方式就可能需要调用线程池切换线程来执行订阅方法。

invokeSubscriber 源码如下:

void invokeSubscriber(Subscription subscription, Object event) {
    try {
      //  subscriber 是 MainActivity,event 是 post 发送的 "text"
      subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
    } catch (InvocationTargetException e) {
        handleSubscriberException(subscription, event, e.getCause());
    } catch (IllegalAccessException e) {
        throw new IllegalStateException("Unexpected exception", e);
    }
}

post() 方法小结

post() 方法会将 post(Object event) 里的 eventclass 对象作为 key,去 subscriptionsByEventType 这个 map 里面取出 keyeventclass 相同的 subscriptions,然后遍历 subscriptions去执行订阅方法。

unRegister()

unRegister() 方法就是将上面所产生的那些 map 清空

总结

EventBus 执行订阅方法的原理主要是通过注解反射。细节上用的的知识还是非常多的,比如 DCL 单例模式Builder 设计模式享元设计模式FindState 的对象池设计)、线程池HandlerThreadLocal 等。

并且还引入了注解处理器,使用这种方式可以不需要用到反射来查找订阅方法,优化了运行时性能。不过本文的源码分析分析的还是利用反射来查找订阅方法。

通过阅读 EventBus 源码,可以看出作者很多地方的精彩设计,很值得我们学习。本文只是分析了核心的 EventBus 源码,很多细节上的源码就直接跳过去了。


标签:subscriberMethod,findState,class,源码,注解,EventBus,event,subscription
From: https://blog.51cto.com/u_16163480/6511319

相关文章

  • Android 换肤之资源(Resources)加载源码分析(一)
    本系列计划3篇:Android换肤之资源(Resources)加载(一)—本篇setContentView()/LayoutInflater源码分析(二)换肤框架搭建(三)看完本篇你可以学会什么?Resources在什么时候被解析并加载的Application#ResourcesActivity#Resourcesdrawable如何加载出来的创建自己的Resources加......
  • 注解
    用于类上的注解@Accessors一般用chain=true,当该值为true时,调用setter方法时会返回当前的对象,方便采取链式编程的方法进行代码编写列如:CatSetName(123).setAge(20).setId();fluent属性为true时,对应的getter方法前没有get,setter方法前没有setprefix属性可以忽略前缀lombok注解......
  • memcpy源码
    【调用栈】 【代码】 【glibc2.17和2.18性能的讨论】https://sourceware.org/bugzilla/show_bug.cgi?id=24872......
  • 【QCustomPlot】使用方法(源码方式)
    说明使用QCustomPlot绘图库辅助开发时整理的学习笔记。同系列文章目录可见《绘图库QCustomPlot学习笔记》目录。本篇介绍QCustomPlot的一种使用方法,通过包含源码的方式进行使用,这也是最常用的方法,示例中使用的QCustomPlot版本为Version2.1.1。目录说明1.下载源码2.......
  • 杰森气象——实况天气小程序(内附完整源码)
    项目介绍当今社会,天气的变化对我们的生活产生着越来越大的影响。为了更好地了解天气状况,越来越多的人开始使用天气查询小程序。今天,介绍的是一款实用的天气查询小程序——杰森气象。杰森气象是一款功能强大的天气查询小程序,它可以帮助我们随时了解天气状况,包括实时天气、预警信息、......
  • Java 注解
    一、Java注解(Annotation)简介从Java5版本之后可以在源代码中嵌入一些补充信息,这种补充信息称为注解(Annotation),是Java平台中非常重要的一部分。注解都是@符号开头的,例如:在学习方法重写时使用过的@Override注解。同Class和Interface一样,注解也属于一种类型。Annotation......
  • 尚医通day13【预约挂号】(内附源码)
    页面预览预约挂号根据预约周期,展示可预约日期,根据有号、无号、约满等状态展示不同颜色,以示区分可预约最后一个日期为即将放号日期选择一个日期展示当天可预约列表预约确认第01章-预约挂号接口分析(1)根据预约周期,展示可预约日期数据(2)选择日期展示当天可预约列表1、......
  • nginx-clojure 源码构建一些问题
    因为nginx-clojure就是一个标准的nginx模块,一些是尝试基于源码进行构建发现一些问题的说明简单说明nginx当前1.25版本的构建是有问题的,1.24版本构建是可以的,1.23版本实际上官方已经提供了但是如果查看nginx官方文档会发现1.23版本的下载官方是似乎移除了,没直接提供了......
  • springboot中自定义注解在service方法中,aop失效
    问题描述写了个自定义注解,但是该注解只会出现在serviece层中的方法中。启动发现aop未拦截到问题原因:调用service中的xx()方法时,Spring的动态代理帮我们动态生成了一个代理的对象,暂且叫他$XxxxService。所以调用xx()方法实际上是代理对象$XxxxService调用的。但是在xx()方法内调用同......
  • 一次Mybaits查询的源码分析
    很好奇Mybaits是怎么将xml和mapper对应起来的,用一段比较简单的demo去debug追踪一下源码看看先用xml配置的方式,看懂了再去看注解的方式是怎么实现的获取MapperMybaits是如何从xml中加载到mapper的<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEconfigurationPUB......