上一篇文章:EventBus源码解析贴了很多源码解释了EventBus的运作原理,根据上篇文章看到的代码总结一下

目录

register
unregister
post
资源池的实现
拼接字符串
Poster事件入队和出队的机制


register

SubscriberMethodFinder.findSubscriberMethods

检查缓存

根据订阅者检查Finder内部的缓存,当存在该订阅者的缓存,则直接使用缓存的数据返回,提升效率。

ignoreGeneratedIndex

false表示忽略构造器获取到的SubscriberInfoIndex列表,直接使用反射。默认为false。

findUsingReflection
里面很多代码和findUsingInfo重合,所以就不展开了

findUsingInfo

  1. 准备FindState对象,用于辅助查找。
  2. 获取SubscriberInfo
  3. 当SubscriberInfo不为空,使用SubscriberInfo的数据。为空,调用findUsingReflectionInSingleClass方法
  4. 移动到订阅者的父类,继续寻找订阅方法
  5. 返回订阅方法并缓存FindState对象

准备FIndState对象
这个过程比较简单,从FindState池里面获取FindState对象,当获取不到的时候,就创建一个。准备我拿成之后就为该对象初始化必要的初始值。

getSubscriberInfo
获取SubscriberInfo,SubscriberInfo的作用是,让开发者手动声明订阅者所有的订阅方法。如果开发者自己手动声明,EventBus就不会使用反射的方式寻找订阅方法,直接使用SubscriberInfo提供的信息。这种方式相对来说性能比较高,因为使用反射还需要扫描所有方法,然后一个一个判断。
如果执行完成之后不为空,就执行添加的代码,将获取到的方法添加到findState的subscriberMethods里面。
如果为空,调用findUsingReflectionInSingleClass方法。

findUsingReflectionInSingleClass
该方法内部就会使用反射,扫描所有的方法,判断方法的:修饰符、方法参数、是否带有Subscribe注解。这三种方法去判断一个方法是否为订阅方法,如果是,就组装成SubscriberMethod对象,并添加到findState的subscriberMethods里面。

moveToSuperclass
上面两种方式执行完毕后,都会findState执行这个方法。这方法的作用就是findUsingInfo写的第4个步骤,移动到订阅者的父类。移动到订阅者的父类之后,就继续寻找订阅方法。当订阅者没有父类或者是父类是Java或Android的核心库,就停止。判断是否为Java或Android的核心库的方式是,直接贴代码:

// Skip system classes, this degrades performance.// Also we might avoid some ClassNotFoundException (see FAQ for background).if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") ||        clazzName.startsWith("android.") || clazzName.startsWith("androidx.")) {         clazz = null;}

如果clazz为空,就结束循环,开始返回订阅方法列表并将FindState对象放入到FIndState池里面

getMethodsAndRelease
在该方法里面,会为FindState里面的订阅方法列表创建一个副本。然后情况FIndState的数据并放入到FindState池里面,然后将这个副本返回。

寻找结束
寻找结束后会重新回到findSubscriberMethods方法,并获取到订阅方法列表。
这个时候就会做判断,为空时,抛异常。
不为空,将订阅者作为Key,将方法列表作为Value缓存起来。


EventBus.subscribe

在寻找方法完成后,就会对这些方法遍历,遍历过程中会调用subscribe方法

检查方法是否存在
会检查是否存在完全相同的订阅方法。
完全相同的标准:订阅者、订阅方法所在的类、订阅方法名称、订阅方法的Event类型
上面这4个数据完全相同,就断定他们是同一个方法,会抛异常。

订阅方法插入到合适的位置
这个比较简单,就是根据订阅方法的优先级做一次排序。

将订阅者和事件列表组成映射关系
这里只是将他们组成映射关系,然后就没有做其他操作,他们的映射关系会在unregister的时候发挥出作用。

订阅方法是粘性的
所谓粘性的,就是Subscribe注解的sticky属性为true。当订阅方法是粘性的,且在调用register之前发送过订阅事件,且事件类型和订阅方法的事件类型一致,就会调用订阅方法。
梳理一下调用的条件:订阅方法必须是粘性的、调用register之前调用过postSticky、发送粘性事件的Event和订阅方法的Evnet的类型一样。


unregister

获取订阅者所有的Event
获取方式其实很简单,在register的时候,有一个步骤为将订阅者和事件列表组成映射关系,所以这个时候通过这个Map就直接获取到事件列表。

unsubscribeByEventType
获取到事件列表之后,就调用这个方法。在这个方法里面,就根据Event获取到订阅方法列表。遍历订阅方法列表,判断订阅者和取消订阅的对象是否为同一个。如果是,将移除该订阅方法。


post

获取当前线程Event队列,并将Event添加到Event队列
之所以说当前线程,是因为使用ThreadLocal保证一个线程只有一个对象,获取到PostingThreadState之后就获取内部的Event队列。

判断当前线程是否在POTING
通过PostingThreadState.isPosting判断当前是否在POSTING,如果POSTING,就结束方法运行。否则,下一步。这个时候会将isPosting设置为true。

记录当前线程的信息并开启while循环从Event队列取出Event
while循环的结束条件是Event队列为空,在while内部会不断从Event队列取出Event并执行。所以外面才能判断如果是isPosting就结束运行,因为只需要将Event加入到Event队列即可。剩下的就交给while处理就行了。处理的方法是postSingleEvent。

postSingleEvent
在这里面经过一些判断后最终会调用postSingleEventForEventType方法。

postSingleEventForEventType
在这个方法里面,会根据Event获取到订阅方法列表,并遍历列表调用postToSubscription方法执行订阅方法。在执行完一个订阅方法之后,会判断是否取消POSTING,如果取消,会抛弃剩下的订阅方法结束该方法。之所以要判断是否取消POSTING,是因为EventBus提供了cancelEventDelivery这个方法让开发者在POSTING过程中取消执行某个Event的订阅方法。

postToSubscription
在这里面会根据订阅方法的线程模式做不同的操作。

  • POSTING:当前线程模式。这种模式直接调用invokeSubscriber方法执行订阅方法。这也是默认的线程模式。
  • MAIN:主线程模式。这种模式下,会判断调用线程是否为主线程。如果是,直接执行。如果不是,切换到主线程执行。
  • MAIN_ORDERED:这种模式下,无论是在什么线程,都会直接交给主线程执行。
  • BACKGROUND:这种模式下,会判断当前是否为主线程。如果是,切换到后台线程执行。如果不是,直接执行。
  • ASYNC:这种模式下,不管当前是什么线程,直接将任务扔给子线程的线程池。

注:上面没有写模式名称的,是因为不知道怎么翻译比较好。

图里面还有一个default,这个不是线程模式,所以不能和上面的混在一起。
这里的default是指swtich语法的default条件,也就是找不到合适的条件。所以这个时候会抛出一个未知线程模式的异常。


资源池的实现

EventBus内部一些地方为了避免频繁创建对象,使用池这种设计方式来缓存对象和复用对象。拿FindState池来说,提供了prepareFindState方法和getMethodsAndRelease方法来复用和缓存FindState对象。
private static final int POOL_SIZE = 4;private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];private FindState prepareFindState() {         synchronized (FIND_STATE_POOL) {         //遍历POOL_SIZE数组        for (int i = 0; i < POOL_SIZE; i++) {             //根据index取出FindState对象            FindState state = FIND_STATE_POOL[i];            //如果不为空,表示有缓存,取出之后将所在的index置为空            //之所以要置空,是因为该对象返回之后会被使用。            //如果不置空,那该对象就有可能同时被多次使用,那就会出现问题。            if (state != null) {                     FIND_STATE_POOL[i] = null;                return state;            }        }    }    //当循环结束后,没有找到缓存对象,就重新创建一个。    return new FindState();}//在FindState使用完成之后,会调用该方法将FindState对象缓存起来private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {     //将FindState里面的SubscriberMethod列表克隆出来    List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);    //清空FindState里面的数据    findState.recycle();    synchronized (FIND_STATE_POOL) {             for (int i = 0; i < POOL_SIZE; i++) {             //找到没有FindState对象的index,将FindState存进去            if (FIND_STATE_POOL[i] == null) {                     FIND_STATE_POOL[i] = findState;                break;            }        }    }    return subscriberMethods;}

所以在开发的时候,如果某个对象需要频繁创建,就可以考虑能不能使用这种方式来实现一个资源池,避免频繁创建对象。


拼接字符串

拼接字符串可以算是开发中一个特别常见的操作。所以可能在写代码的时候,并没有考虑那么多,需要拼接的时候就直接拼接。而EventBus在这方面,也下了功夫。

在FindState里面有这样一段代码
static class FindState {         final StringBuilder methodKeyBuilder = new StringBuilder(128);    private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {             methodKeyBuilder.setLength(0);        methodKeyBuilder.append(method.getName());        methodKeyBuilder.append('>').append(eventType.getName());        ....}}

由于需要频繁的拼接字符串,所以EventBus使用StrngBuilder来拼接,这倒也没什么问题。不过EventBus为了不用频繁创建StringBuilder对象,每次在调用setLength清空StringBuilder里面的数据,然后再重新append,从而避免频繁创建StringBuilder对象。


Poster事件入队和出队的机制

这个可以看HandlerPoster和BackgroundPoster的实现,我就贴HanlderPoster,反正都差不多。
public class HandlerPoster extends Handler implements Poster {     private boolean handlerActive;//在源码中new是在构造方法里面new的,不过其实写在哪里区别都不大。private final PendingPostQueue queue = new PendingPostQueue();    public void enqueue(Subscription subscription, Object event) {             PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);        synchronized (this) {             //入队            queue.enqueue(pendingPost);            //handlerActive这个变量是用来判断当前Hander是否在执行handleMessage方法的         //当没有执行的时候,调用sendMessage            if (!handlerActive) {                     handlerActive = true;                if (!sendMessage(obtainMessage())) {                         throw new EventBusException("Could not send handler message");                }            }        }    }    @Override    public void handleMessage(Message msg) {             boolean rescheduled = false;        try {                 long started = SystemClock.uptimeMillis();            //当执行的时候,在while循环内部不断地从queue取出消息            //这样就不用频繁地调用sendMessage,从而减少主线程Looper的调度            while (true) {                     PendingPost pendingPost = queue.poll();                if (pendingPost == null) {                         synchronized (this) {                             // Check again, this time in synchronized                        pendingPost = queue.poll();                        if (pendingPost == null) {                                 handlerActive = false;                            return;                        }                    }                }                eventBus.invokeSubscriber(pendingPost);                long timeInMethod = SystemClock.uptimeMillis() - started;                if (timeInMethod >= maxMillisInsideHandleMessage) {                         if (!sendMessage(obtainMessage())) {                             throw new EventBusException("Could not send handler message");                    }                    rescheduled = true;                    return;                }            }        } finally {                 handlerActive = rescheduled;        }    }}

目前只想到这些,后续想到了什么会继续补充。

更多相关文章

  1. SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
  2. AIDL用法总结
  3. Android架构组件(3)LiveData框架
  4. Android之TabLayout使用和默认选中+移动(解决)
  5. Android:Activity(四):Activity生命周期
  6. WebKit 分析–for android - Braincol - 博客园
  7. AOP在Android中最佳用法
  8. Android难点之——自定义View(上)
  9. 《Android(安卓)第一行代码》十一章 Service学习笔记

随机推荐

  1. Android(安卓)TextView显示文字过长时添
  2. 转贴 Android(安卓)开发之旅:又见Hello Wo
  3. android学习笔记(7)AbsoluteLayout+Frame
  4. android的四层体系结构,基于mvc三层结构浅
  5. Android(安卓)滑动侧边栏(Sliding Menu)第
  6. Android(安卓)Property System | Android
  7. Android图片转换
  8. Android设置一个按钮右对齐
  9. android:imeOptions指定了弹出键盘时右下
  10. android 应用程序数据共享shareuserid篇+