文章目录
  1. 1. 三、实现细节
    1. 1.1. 3.1 注册流程
    2. 1.2. 3.2 消息处理流程
  2. 2. 四、优化
    1. 2.1. 4.1 主线程执行消息
    2. 2.2. 4.2 后台线程执行消息
  3. 3. 五、问题
    1. 3.1. 5.1 订阅方法继承问题
      1. 3.1.0.1. 5.1.1 新版本订阅方法的继承问题
      2. 3.1.0.2. 5.1.2 旧版本的继承问题
  4. 3.2. 5.2 订阅方法重载问题
  • 4. 六、写在最后
  • EventBus实现分析(上)中介绍了EventBus简单使用解决什么问题,这一节介绍EventBus的实现细节。

    三、实现细节

    前面在介绍EventBus的使用时,也顺带着讲了一些细节。下面先从两个流程开始,一个是注册流程,另一个是发送消息并且处理的流程。

    3.1 注册流程

    注册过程要根据消息类型缓存对应消息接收的方法,缓存的集合是一个HashMap,缓存集合代码如下所示:

    1
    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

    其中键是消息类型的Class对象,值中的org.greenrobot.eventbus.Subscription的对象中包含当前方法所在的对象,即订阅者,还包含订阅的方法。代码如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    final class Subscription {
    final Object subscriber;
    final SubscriberMethod subscriberMethod;
    // ... 其它代码省略
    }

    public class SubscriberMethod {
    // 订阅方法
    final Method method;
    // 线程模型
    final ThreadMode threadMode;
    // 消息类型
    final Class<?> eventType;
    // 优先级
    final int priority;
    // stick模式
    final boolean sticky;
    /** Used for efficient comparison */
    String methodString;
    // ...其它代码省略
    }

    中间的解析缓存过程如下图所示:

    解析缓存过程图

    3.2 消息处理流程

    上面的缓存过程在这一步起作用了。

    1)暂存消息到消息队列

    当调用EventBus#post消息时,先从当前线程中取到消息队列,然后将消息放入到消息队列中,再从消息队列中取消息进行处理。代码如下所示:

    org.greenrobot.eventbus.EventBus#post

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public void post(Object event) {
    // 每个线程都有一个消息队列,避免同步,提高效率
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    eventQueue.add(event);

    if (!postingState.isPosting) {
    // 参数准备
    postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
    postingState.isPosting = true;
    if (postingState.canceled) {
    throw new EventBusException("Internal error. Abort state was not reset");
    }
    try {
    while (!eventQueue.isEmpty()) {
    // 执行消息
    postSingleEvent(eventQueue.remove(0), postingState);
    }
    } finally {
    postingState.isPosting = false;
    postingState.isMainThread = false;
    }
    }
    }

    2)找到消息类型对应方法

    这一步主要是从消息类型的HashMap缓存subscriptionsByEventType中取出对应的方法集合。代码如下postSingleEventForEventType方法所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    // eventInheritance默认为true
    if (eventInheritance) {
    // 查找所有event类型
    List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
    int countTypes = eventTypes.size();
    for (int h = 0; h < countTypes; h++) {
    Class<?> clazz = eventTypes.get(h);
    // 遍历event的类型,针对每种类型都会处理
    subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
    }
    } else {
    subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }

    // ... 省略部分代码
    }

    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 = false;
    try {
    postToSubscription(subscription, event, postingState.isMainThread);
    aborted = postingState.canceled;
    } finally {
    // ... 省略部分代码
    }

    // ...省略部分代码
    }
    return true;
    }
    return false;
    }

    在上面postSingleEvent方法中使用一个eventInheritance参数,这个参数默认为true,也就是说消息类型对应的父类都会被找出来。上一节的示例中,如果有订阅方法的参数是Object,post消息是String,这时候对应处理消息的方法参数是Object的方法也会被执行;如果是false,则参数为Object的方法不会执行,只有参数为String的方法会执行。这个参数是在EventBusBuilder中设置后,创建EventBus对象时使用。

    3)执行对应方法

    postToSubscription是将消息加入执行列表中还是直接执行,这个是根据线程模型来的,前面有说过。下面就不贴代码了。

    四、优化

    在最后将消息加入到消息队列中的时候,EventBus在处理消息时,做了优化处理。

    4.1 主线程执行消息

    当消息需要在UI线程中执行时,消息可能会被加入到消息队列中,也就是加入到mainThreadPoster队列中。代码如下所示:

    org.greenrobot.eventbus.EventBus#postToSubscription

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
    case MAIN:
    if (isMainThread) {
    invokeSubscriber(subscription, event);
    } else {
    mainThreadPoster.enqueue(subscription, event);
    }
    break;
    // 部分代码省略...
    }
    }

    在主线程中执行消息代码如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    final class HandlerPoster extends Handler {

    private final PendingPostQueue queue;
    private final int maxMillisInsideHandleMessage;
    private final EventBus eventBus;
    private boolean handlerActive;

    HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
    super(looper);
    this.eventBus = eventBus;
    // 这个值是10,即10ms
    this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
    queue = new PendingPostQueue();
    }

    void enqueue(Subscription subscription, Object event) {
    PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
    synchronized (this) {
    queue.enqueue(pendingPost);
    if (!handlerActive) {
    handlerActive = true;
    if (!sendMessage(obtainMessage())) {
    // 触发handleMessage方法被调用
    throw new EventBusException("Could not send handler message");
    }
    }
    }
    }

    @Override
    public void handleMessage(Message msg) {
    boolean rescheduled = false;
    try {
    long started = SystemClock.uptimeMillis();
    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) {
    // 处理消息超过10ms
    if (!sendMessage(obtainMessage())) {
    throw new EventBusException("Could not send handler message");
    }
    rescheduled = true;
    // 结束循环
    return;
    }
    }
    } finally {
    handlerActive = rescheduled;
    }
    }
    }

    这里做了一个优化处理是当EventBusUI线程中待执行的消息队列queue中的消息执行耗时超过maxMillisInsideHandleMessage,即10ms,就会停止执行queue消息队列中的消息,不阻碍App中UI线程中的其他消息执行。等App中的UI线程中的消息执行完后,再执行到这里的handleMessage方法。在handleMessage方法中的sendMessage(obtainMessage())操作就是这个意思。

    4.2 后台线程执行消息

    同样,EventBus执行后台线程(非UI线程)中的消息队列中的消息时,也使用了优化。org.greenrobot.eventbus.EventBus#backgroundPoster为后台线程的消息队列,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    final class BackgroundPoster implements Runnable {

    private final PendingPostQueue queue;
    private final EventBus eventBus;

    private volatile boolean executorRunning;

    BackgroundPoster(EventBus eventBus) {
    this.eventBus = eventBus;
    queue = new PendingPostQueue();
    }

    public void enqueue(Subscription subscription, Object event) {
    PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
    synchronized (this) {
    // 先加入到队列中
    queue.enqueue(pendingPost);
    if (!executorRunning) {
    executorRunning = true;
    // 最后会在线程池中的线程中执行run方法
    eventBus.getExecutorService().execute(this);
    }
    }
    }

    @Override
    public void run() {
    try {
    try {
    while (true) {
    // 如果消息队列中有消息,则立及返回,否则会等1秒钟,再从消息队列取消息
    PendingPost pendingPost = queue.poll(1000);
    if (pendingPost == null) {
    synchronized (this) {
    // Check again, this time in synchronized
    pendingPost = queue.poll();
    if (pendingPost == null) {
    executorRunning = false;
    return;
    }
    }
    }
    // 执行对应订阅的方法
    eventBus.invokeSubscriber(pendingPost);
    }
    } catch (InterruptedException e) {
    Log.w("Event", Thread.currentThread().getName() + " was interruppted", e);
    }
    } finally {
    executorRunning = false;
    }
    }

    }

    while循环中的queue.poll方法如下所示:

    org.greenrobot.eventbus.PendingPostQueue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    synchronized PendingPost poll() {
    PendingPost pendingPost = head;
    if (head != null) {
    head = head.next;
    if (head == null) {
    tail = null;
    }
    }
    return pendingPost;
    }

    synchronized PendingPost poll(int maxMillisToWait) throws InterruptedException {
    if (head == null) {
    // 线程等待
    wait(maxMillisToWait);
    }
    // 这里调用上面的poll方法
    return poll();
    }

    上面这种做法避免了线程切换带来的开销。也就是说,如果两次post消息间隔是在1秒内,则有可能这两次消息都是在同一个线程中执行,而不是两个线程。但不知道为什么这个值是1秒。

    五、问题

    5.1 订阅方法继承问题

    5.1.1 新版本订阅方法的继承问题
    • 子类override的方法,@subscribe注解不管是写在子类方法上还是基类的方法上,子类方法都会被执行。

    前提条件是订阅对象使用的是子类对象,因为是遵循java多态语法规则,子类override基类中的方法,所以调用的是子类中的方法。如果子类方法中调用了super,则会调用基类方法。如果没有调用,则会完全override基类方法,基类方法不会执行。

    • 基类方法上使用@subscribe注解后,子类override的方法则不需要注解标记,即使加了效果也是一样的。
    5.1.2 旧版本的继承问题

    旧版本解析订阅方法都是以onEvent开头的,规则更简单一点,override方法完全按照java多态语法来。

    5.2 订阅方法重载问题

    重载的方法如果也标记为订阅方法,消息类型都兼容,则都会执行。什么意思呢?就像前面的举例,如果post的是String类型,重载的参数是StringObject类型,则Object类型的方法也会执行。如果post的是Object类型,那么String参数的订阅方法不会被执行,Object参数的订阅方法会被执行。

    为什么都订阅的重载方法都会执行呢?因为它们都订阅了啊,我是认真的。

    六、写在最后

    最后总结一下,用EventBus要注意register方法和unregister方法配对使用,否则容易导致内存泄露。EventBus要灵活使用,建议在项目某一解耦层使用,如果大面积使用会导致项目缺乏逻辑性,使用不当会导致代码臃肿,所以注意使用场景。

    (全文完)

    文章目录
    1. 1. 三、实现细节
      1. 1.1. 3.1 注册流程
      2. 1.2. 3.2 消息处理流程
    2. 2. 四、优化
      1. 2.1. 4.1 主线程执行消息
      2. 2.2. 4.2 后台线程执行消息
    3. 3. 五、问题
      1. 3.1. 5.1 订阅方法继承问题
        1. 3.1.0.1. 5.1.1 新版本订阅方法的继承问题
        2. 3.1.0.2. 5.1.2 旧版本的继承问题
    4. 3.2. 5.2 订阅方法重载问题
  • 4. 六、写在最后