上一篇文章:EventBus源码解析贴了很多源码解释了EventBus的运作原理,根据上篇文章看到的代码总结一下
register
unregister
post
资源池的实现
拼接字符串
Poster事件入队和出队的机制
检查缓存
根据订阅者检查Finder内部的缓存,当存在该订阅者的缓存,则直接使用缓存的数据返回,提升效率。
ignoreGeneratedIndex
false表示忽略构造器获取到的SubscriberInfoIndex列表,直接使用反射。默认为false。
findUsingReflection
里面很多代码和findUsingInfo重合,所以就不展开了
findUsingInfo
准备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缓存起来。
在寻找方法完成后,就会对这些方法遍历,遍历过程中会调用subscribe方法
检查方法是否存在
会检查是否存在完全相同的订阅方法。
完全相同的标准:订阅者、订阅方法所在的类、订阅方法名称、订阅方法的Event类型
上面这4个数据完全相同,就断定他们是同一个方法,会抛异常。
订阅方法插入到合适的位置
这个比较简单,就是根据订阅方法的优先级做一次排序。
将订阅者和事件列表组成映射关系
这里只是将他们组成映射关系,然后就没有做其他操作,他们的映射关系会在unregister的时候发挥出作用。
订阅方法是粘性的
所谓粘性的,就是Subscribe注解的sticky属性为true。当订阅方法是粘性的,且在调用register之前发送过订阅事件,且事件类型和订阅方法的事件类型一致,就会调用订阅方法。
梳理一下调用的条件:订阅方法必须是粘性的、调用register之前调用过postSticky、发送粘性事件的Event和订阅方法的Evnet的类型一样。
获取订阅者所有的Event
获取方式其实很简单,在register的时候,有一个步骤为将订阅者和事件列表组成映射关系,所以这个时候通过这个Map就直接获取到事件列表。
unsubscribeByEventType
获取到事件列表之后,就调用这个方法。在这个方法里面,就根据Event获取到订阅方法列表。遍历订阅方法列表,判断订阅者和取消订阅的对象是否为同一个。如果是,将移除该订阅方法。
获取当前线程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
在这里面会根据订阅方法的线程模式做不同的操作。
注:上面没有写模式名称的,是因为不知道怎么翻译比较好。
图里面还有一个default,这个不是线程模式,所以不能和上面的混在一起。
这里的default是指swtich语法的default条件,也就是找不到合适的条件。所以这个时候会抛出一个未知线程模式的异常。
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;
}
所以在开发的时候,如果某个对象需要频繁创建,就可以考虑能不能使用这种方式来实现一个资源池,避免频繁创建对象。
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对象。
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;
}
}
}
目前只想到这些,后续想到了什么会继续补充。