EventBus源码解析-总结篇
上一篇文章:EventBus源码解析贴了很多源码解释了EventBus的运作原理,根据上篇文章看到的代码总结一下
目录
register
unregister
post
资源池的实现
拼接字符串
Poster事件入队和出队的机制
register
SubscriberMethodFinder.findSubscriberMethods
检查缓存
根据订阅者检查Finder内部的缓存,当存在该订阅者的缓存,则直接使用缓存的数据返回,提升效率。
ignoreGeneratedIndex
false表示忽略构造器获取到的SubscriberInfoIndex列表,直接使用反射。默认为false。
findUsingReflection
里面很多代码和findUsingInfo重合,所以就不展开了
findUsingInfo
- 准备FindState对象,用于辅助查找。
- 获取SubscriberInfo
- 当SubscriberInfo不为空,使用SubscriberInfo的数据。为空,调用findUsingReflectionInSingleClass方法
- 移动到订阅者的父类,继续寻找订阅方法
- 返回订阅方法并缓存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; } }}
目前只想到这些,后续想到了什么会继续补充。
更多相关文章
- SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
- AIDL用法总结
- Android架构组件(3)LiveData框架
- Android之TabLayout使用和默认选中+移动(解决)
- Android:Activity(四):Activity生命周期
- WebKit 分析–for android - Braincol - 博客园
- AOP在Android中最佳用法
- Android难点之——自定义View(上)
- 《Android(安卓)第一行代码》十一章 Service学习笔记