EventBus源码解析-总结篇

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

目录

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


register

EventBus源码解析-总结篇_第1张图片

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

EventBus源码解析-总结篇_第2张图片

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

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


post

EventBus源码解析-总结篇_第3张图片

获取当前线程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;
        }
    }
}

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

你可能感兴趣的:(android,android)