EventBus 3.0+ 原始碼詳解(史上最詳細圖文講解)

蔣志碧發表於2018-07-30

本文的整體結構圖

本文篇幅很長,建議收藏了找時間慢慢看

整體結構圖.png

本文講解的是 'org.greenrobot:eventbus:3.1.1' 最新版,和其他版本有細微差別,思想都是一樣的。

EventBus 的簡單示例

@Override
protected void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
protected void onStop() {
    super.onStop();
    EventBus.getDefault().unregister(this);
}


@Subscribe(threadMode = ThreadMode.MAIN, sticky = false, priority = 2)
public void onMessageEvent(MyBusEvent event) {
    Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
}
複製程式碼

EventBus 的建立是一個單例模式,我們就從 getDefault() 開始講起吧。

一、EventBus 的建立(單例 + Builder 設計模式)

1.1、單例模式獲取 EventBus 例項

單例模式UML.png

EventBus 的建立是一個雙重校驗鎖的單例模式。

public static EventBus getDefault() {
    if (sDefaultBus == null) {
        synchronized (EventBus.class) {
            if (sDefaultBus == null) {
                sDefaultBus = new EventBus();
            }
        }
    }
    return sDefaultBus;
}
複製程式碼

單例模式沒什麼好說的,我們都知道單例模式的建構函式是私有型別 private,但是 EventBus 的建構函式卻是 public 型別。

**這樣設計的目:**EventBus 在程式碼使用過程中不僅僅只有一條匯流排,還有其他的訂閱匯流排,訂閱者可以註冊到不同的 EventBus 匯流排,然後通過不同的 EventBus 匯流排傳送資料。

不同的 EventBus 傳送的資料是相互隔離的,訂閱者只能收到註冊了該 EventBus 匯流排內事件,而不會收到別的 EventBus 事件匯流排的資料。這樣的設計為後面的不同環境的執行緒切換創造了好的條件。

	/**
     * Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a central bus, consider {@link #getDefault()}.
     建立一個新的 EventBus 例項,每個例項在 events 事件被髮送的時候都是一個單獨的領域,為了使用一個 事件匯流排,考慮用 getDefault() 構建。
     */
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
public EventBus() {
    this(DEFAULT_BUILDER);
}
複製程式碼

1.2、Builder 模式構建 EventBus

Builder模式UML圖.png

這裡的程式碼我們重點看一下 this(DEFAULT_BUILDER) 裡面的 DEFAULT_BUILDER。DEFAULT_BUILDER 是一個 EventBusBuilder,從這裡可以看出 EventBus 是通過 建造者模式進行構建的,接下來我們看下是如何構建的吧。

EventBus(EventBusBuilder builder) {
    logger = builder.getLogger();
    
    //Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType
    subscriptionsByEventType = new HashMap<>();
    //Map<Object, List<Class<?>>> typesBySubscriber
    typesBySubscriber = new HashMap<>();
    //Map<Class<?>, Object> stickyEvents
    stickyEvents = new ConcurrentHashMap<>();
    
    /**
    用於執行緒間排程
    **/
    mainThreadSupport = builder.getMainThreadSupport();
    mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
    backgroundPoster = new BackgroundPoster(this);
    asyncPoster = new AsyncPoster(this);
    //用於記錄event生成索引
    indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
    //對已經註解過的Method的查詢器,會對所設定過 @Subscriber 註解的的方法查詢相應的Event
    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
            builder.strictMethodVerification, builder.ignoreGeneratedIndex);
	
    //當呼叫事件處理函式發生異常是否需要列印Log
    logSubscriberExceptions = builder.logSubscriberExceptions;
    //當沒有訂閱者訂閱這個訊息的時候是否列印Log
    logNoSubscriberMessages = builder.logNoSubscriberMessages;
    //當呼叫事件處理函式,如果異常,是否需要傳送Subscriber這個事件
    sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
    //當沒有事件處理函式時,對事件處理是否需要傳送sendNoSubscriberEvent這個標誌
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    //是否需要丟擲SubscriberException
    throwSubscriberException = builder.throwSubscriberException;
    
    //與Event有繼承關係的類是否都需要傳送
    eventInheritance = builder.eventInheritance;
    //執行緒池 Executors.newCachedThreadPool()
    executorService = builder.executorService;
}
複製程式碼

##二、EventBus 中幾個重要的成員變數

2.1、EventBus 中重要的 3 個 HashMap。

####2.1.1、subscriptionsByEventType

Map<Class<?>, CopyOnWriteArrayList> subscriptionsByEventType 是以 Event 為 Key,Subscriber 為 Value,當傳送這個 Event 時,可以在 Map 中找到相應的 Subscriber。

####2.1.2、typesBySubscriber

Map<Object, List<Class<?>>> typesBySubscriber 以 Subscriber 為 Key,以 Event 為 Value,當我們註冊和反註冊時,都會操作 typesBySubscriber 這個 Map。

####2.1.3、stickyEvents

Map<Class<?>, Object> stickyEvents 是管理 EventBus 的粘性事件,粘性事件的 Event 傳送出去之後,即使負責接收的 Subscriber 沒有註冊,當註冊之後,依然能收到該事件,和廣播接受者的粘性事件類似。

###2.2、EventBus 中重要的有 3 個 Post。

  • mainThreadPoster:主執行緒的 Poster

    mainThreadSupport.createPoster(this) 返回的是HandlerPoster(eventBus, looper, 10),looper 是一個 MainThread 的 Looper。意味著這個 HandlerPoster 可能就是 Handler 的實現。

  • backgroundPoster:後臺執行緒的 Poster

  • asyncPoster:非同步執行緒的 Poster

2.2.1、mainThreadPoster # HandlerPoster

HandlerPoster 其實就是 Handler 的實現,內部維護了一個 PendingPostQueue 的訊息佇列,在 enqueue(Subscription subscription, Object event) 方法中不斷從 pendingPostPool 的 ArrayList 快取池中獲取 PendingPost 新增到 PendingPostQueue 佇列中,並將該 PendingPost 事件傳送到 Handler 中處理。

在 handleMessage 中,通過一個 while 死迴圈,不斷從 PendingPostQueue 中取出 PendingPost 出來執行,獲取到 post 之後由 eventBus 通過該 post 查詢相應的 Subscriber 處理事件。

while 退出的條件有兩個

  1. 獲取到的 PendingPost 為 null,即是 PendingPostQueue 已經沒有訊息可處理。
  2. 每個 PendingPost 在 Handler 中執行的時間超過了最大的執行時間。

HandlerPoster UML 類圖

HandlerPoster UML 類圖

HandlerPoster 執行過程

HandlerPoster執行過程.png

public class HandlerPoster extends Handler implements Poster {

    private final PendingPostQueue queue;//存放待執行的 Post Events 的事件佇列
    private final int maxMillisInsideHandleMessage;//Post 事件在 handleMessage 中執行的最大的時間值,超過這個時間值則會拋異常
    private final EventBus eventBus;
    private boolean handlerActive;//標識 Handler 是否被執行起來

   protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
        super(looper);
        this.eventBus = eventBus;
        this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
        queue = new PendingPostQueue();
    }

    public void enqueue(Subscription subscription, Object event) {
        //從 pendingPostPool 的 ArrayList 快取池中獲取 PendingPost 新增到 PendingPostQueue 佇列中,並將該 PendingPost 事件傳送到 Handler 中處理。
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            queue.enqueue(pendingPost);//新增到佇列中
            if (!handlerActive) {//標記 Handler 為活躍狀態
                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 (true) {//死迴圈,不斷從 PendingPost 佇列中取出 post 事件執行
                PendingPost pendingPost = queue.poll();
                if (pendingPost == null) {//如果為 null,表示佇列中沒有 post 事件,此時標記 Handelr 關閉,並退出 while 迴圈
                    synchronized (this) {
                        // Check again, this time in synchronized
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
                            handlerActive = false; //標記 Handler 為非活躍狀態
                            return;
                        }
                    }
                }
                //獲取到 post 之後由 eventBus 通過該 post 查詢相應的 Subscriber 處理事件
                eventBus.invokeSubscriber(pendingPost);
                //計算每個事件在 handleMessage 中執行的時間
                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;
        }
    }
}

final class PendingPost {
    //通過ArrayList來實現PendingPost的新增和刪除
    private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();

    Object event;
    Subscription subscription;
    PendingPost next;

    private PendingPost(Object event, Subscription subscription) {
        this.event = event;
        this.subscription = subscription;
    }

    //獲取 PendingPost
    static PendingPost obtainPendingPost(Subscription subscription, Object event) {
        synchronized (pendingPostPool) {
            int size = pendingPostPool.size();
            if (size > 0) {
                PendingPost pendingPost = pendingPostPool.remove(size - 1);
                pendingPost.event = event;
                pendingPost.subscription = subscription;
                pendingPost.next = null;
                return pendingPost;
            }
        }
        return new PendingPost(event, subscription);
    }

    //釋放 PendingPost
    static void releasePendingPost(PendingPost pendingPost) {
        pendingPost.event = null;
        pendingPost.subscription = null;
        pendingPost.next = null;
        synchronized (pendingPostPool) {
            // Don't let the pool grow indefinitely
            if (pendingPostPool.size() < 10000) {
                pendingPostPool.add(pendingPost);
            }
        }
    }

}
複製程式碼

2.2.2、backgroundPoster # BackGroundPoster

BackGroundPoster執行過程.png

BackgroundPoster 實現了 Runnable 和 Poster,enqueue() 和 HandlerPoster 中實現一樣,在上文中已經講過,這裡不再贅述。

我們來看下 run() 方法中的實現,不斷從 PendingPostQueue 中取出 pendingPost 到 EventBus 中分發,這裡注意外部是 while() 死迴圈,意味著 PendingPostQueue 中所有的 pendingPost 都將分發出去。而 AsyncPoster 只是取出一個。

final class BackgroundPoster implements Runnable, Poster {

    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;
                //線上程池中執行這個 pendingPost
                eventBus.getExecutorService().execute(this);//Executors.newCachedThreadPool()
            }
        }
    }

    @Override
    public void run() {
        try {
            try {
                //不斷迴圈從 PendingPostQueue 取出 pendingPost 到 eventBus 執行
                while (true) {
                    //在 1000 毫秒內從 PendingPostQueue 中獲取 pendingPost
                    PendingPost pendingPost = queue.poll(1000);
                    //雙重校驗鎖判斷 pendingPost 是否為 null
                    if (pendingPost == null) {
                        synchronized (this) {
                            // Check again, this time in synchronized
                            pendingPost = queue.poll();//再次嘗試獲取 pendingPost
                            if (pendingPost == null) {
                                executorRunning = false;
                                return;
                            }
                        }
                    }
                    //將 pendingPost 通過 EventBus 分發出去
                   //這裡會將PendingPostQueue中【所有】的pendingPost都會分發,這裡區別於AsyncPoster
                    eventBus.invokeSubscriber(pendingPost);
                }
            } catch (InterruptedException e) {
                eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e);
            }
        } finally {
            executorRunning = false;
        }
    }
}
複製程式碼

2.2.3、asyncPoster # AsyncPoster

AsyncPoster執行過程.png

class AsyncPoster implements Runnable, Poster {

    private final PendingPostQueue queue;
    private final EventBus eventBus;

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

    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        queue.enqueue(pendingPost);
        eventBus.getExecutorService().execute(this);//Executors.newCachedThreadPool()
    }

    @Override
    public void run() {
        PendingPost pendingPost = queue.poll();
        if(pendingPost == null) {
            throw new IllegalStateException("No pending post available");
        }
        eventBus.invokeSubscriber(pendingPost);
    }
}
複製程式碼

三、@Subscribe 的註解

當我們指定訂閱方法的時候,會在方法上加上註解,如下,下面我們看看這個註解的具體含義

@Subscribe(threadMode = ThreadMode.MAIN, sticky = false, priority = 2)
public void onMessageEvent(EventTest event) {
    
}

@Documented //命名為 java doc 文件
@Retention(RetentionPolicy.RUNTIME) //指定在執行時有效,即在執行時能保持這個 Subscribe
@Target({ElementType.METHOD}) //指定型別為 METHOD,表名用來描述方法
public @interface Subscribe {
    //指定執行緒模式,可以指定在 Subscribe 中接收的 Event 所處的執行緒
    ThreadMode threadMode() default ThreadMode.POSTING;
    boolean sticky() default false;
    int priority() default 0;
}
複製程式碼

3.1、ThreadMode

public enum ThreadMode {
    POSTING,//EventBus 預設的執行緒模式
    MAIN,//主執行緒
    MAIN_ORDERED,//主執行緒
    BACKGROUND,//後臺執行緒
    ASYNC//非同步執行緒
}
複製程式碼

ThreadMode 是 enum(列舉)型別,threadMode 預設值是 POSTING。3.1.1 版本的 EventBus 新增了一種型別,共 5 種,以前的版本是 4 種。

  • POSTING:事件釋出在什麼執行緒,就在什麼執行緒訂閱。故它不需要切換執行緒來分發事件,因此開銷最小。它要求 task 完成的要快,不能請求 MainThread,適用簡單的 task。
  • MAIN:無論事件在什麼執行緒釋出,都在主執行緒訂閱。事件將排隊等待交付(非阻塞)。使用此模式的訂閱者必須快速返回,以避免阻塞主執行緒。
  • MAIN_ORDERED:無論事件在什麼執行緒釋出,都在主執行緒訂閱。區別於 MAIN,MAIN_ORDERED 事件將始終排隊等待交付。這將確保 post 呼叫是非阻塞的。
  • BACKGROUND:如果釋出的執行緒不是主執行緒,則在該執行緒訂閱,如果是主執行緒,則使用一個單獨的後臺執行緒訂閱。
  • ASYNC:在非主執行緒和釋出執行緒中訂閱。當處理事件的 Method 是耗時的,需要使用此模式。儘量避免同時觸發大量的耗時較長的非同步操作,EventBus 使用執行緒池高效的複用已經完成非同步操作的執行緒。

3.2、sticky 粘性事件

粘性事件是事件消費者在事件釋出之後才註冊,依然能接收到該事件的特殊型別。

StickyEvent 與普通 Event的 普通就在於,EventBus 會自動維護被作為 StickyEvent 被 post 出來(即在釋出事件時使用 EventBus.getDefault().postSticky(new MyEvent()) 方法)的事件的最後一個副本在快取中。 任何時候在任何一個訂閱了該事件的訂閱者中的任何地方,都可以通 EventBus.getDefault().getStickyEvent(MyEvent.class)來取得該型別事件的最後一次快取。

sticky(粘性)預設值是 false,如果是 true,那麼可以通過 EventBus 的 postSticky 方法分發最近的粘性事件給該訂閱者(前提是該事件可獲得)。

3.3、Priority

priority 是 Method 的優先順序,優先順序高的可以先獲得分發事件的權利。這個不會影響不同的 ThreadMode 的分發事件順序。

四、register 註冊

register註冊.png

  1. 通過反射獲取到訂閱者的 Class 物件。
  2. 通過 Class 物件找到對應的訂閱者方法集合。
  3. 遍歷訂閱者方法集合,將訂閱者和訂閱者方法訂閱起來。

register() 接收的引數為 Object 型別的訂閱者,通常也就是程式碼中 Activity 和 Fragment 的例項 this。EventBus 通過 getDefault() 來獲取例項,當我們每新建一個 EventBus 匯流排,它的釋出和訂閱事件都是相互隔離的,EventBus 如何做到釋出和訂閱相互隔離呢?我們看下 register 的實現。

public void register(Object subscriber) {
    //1. 通過反射獲取到訂閱者的Class物件
    Class<?> subscriberClass = subscriber.getClass();
    //2. 通過Class物件找到對應的訂閱者方法集合
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    //3. 遍歷訂閱者方法集合,將訂閱者和訂閱者方法訂閱起來。
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}
複製程式碼

在 1 中沒什麼可細講的,接下來看下第 2 步中的 SubscriberMethodFinder 這個類的 findSubscriberMethods() 方法。

subscriberMethodFinder 是 EventBus 的一個成員,可以看作是一個訂閱方法查詢器,是在EventBus 構造方法通過 EventBusBuilder 的一些引數構造出來的。

呼叫 findSubscriberMethods 方法,傳入訂閱者的 Class 物件,字面意思是找出訂閱者中所有的訂閱方法,用一個 List 集合來接收。

4.1、SubscriberMethodFinder # findSubscriberMethods() 詳解

findSubscriberMethods()詳解.png

//訂閱者的 Class 物件為 key,訂閱者中的訂閱方法 List 為 value
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    //1. 首先在 METHOD_CACHE 中查詢該 Event 對應的訂閱者集合是否已經存在,如果有直接返回
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }

    //2. 根據訂閱者類 subscriberClass 查詢相應的訂閱方法
    if (ignoreGeneratedIndex) {//是否忽略生成 index
        //通過反射獲取
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        //通過 SubscriberIndex 方式獲取
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    
    //若訂閱者中沒有訂閱方法,則拋異常
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        // 快取訂閱者的訂閱方法 List
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}
//----------------------------SubscriberMethod----------------------------
//封裝了EventBus中的引數,就是一個EventBus訂閱方法包裝類
public class SubscriberMethod {
    final Method method;
    final ThreadMode threadMode;
    final Class<?> eventType;
    final int priority;
    final boolean sticky;
    String methodString;
 	//.....涉及到的方法後文再講
}
複製程式碼

METHOD_CACHE 是一個 ConcurrentHashMap,以訂閱者的 Class 物件為 key,訂閱者中的訂閱方法 List 為 value,快取了註冊過的訂閱方法。

如果有快取則返回返回快取,如果沒有則繼續往下執行。這裡看到 ignoreGeneratedIndex 這個屬性,意思為是否忽略生成 index,是在構造 SubscriberMethodFinder 通過 EventBusBuilder 的同名屬性賦值的,預設為 false,當為 true 時,表示以反射的方式獲取訂閱者中的訂閱方法,當為 false 時,則以 Subscriber Index 的方式獲取。接下來分別分析這兩種方式。

####4.1.1、findUsingReflection() 方法解析

當 ignoreGeneratedIndex 為 true 時 --> findUsingReflection()

findUsingReflection()解析.png

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
    // 建立並初始化 FindState 物件
    FindState findState = prepareFindState();
    // findState 與 subscriberClass 關聯
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        // 使用反射的方式獲取單個類的訂閱方法
        findUsingReflectionInSingleClass(findState);
        // 使 findState.clazz 指向父類的 Class,繼續獲取
        findState.moveToSuperclass();
    }
    // 返回訂閱者及其父類的訂閱方法 List,並釋放資源
    return getMethodsAndRelease(findState);
}
複製程式碼

####4.1.2、findUsingInfo() 方法解析

當 ignoreGeneratedIndex 為 false 時 --> findUsingInfo()

findUsingInfo()解析.png

跟反射方式的 findUsingReflection 的首尾有點類似,不同的是它是通過 SubscriberInfo 這個類來獲取訂閱方法的,那麼 SubscriberInfo 物件是怎麼獲取的呢,那麼同樣只看關鍵程式碼:getSubscriberInfo()

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    //1.通過 prepareFindState 獲取到 FindState(儲存找到的註解過的方法的狀態)
    FindState findState = prepareFindState();
    //2.findState 與 subscriberClass 關聯
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        //獲取訂閱者資訊
        //通過 SubscriberIndex 獲取 findState.clazz 對應的 SubscriberInfo
        findState.subscriberInfo = getSubscriberInfo(findState);
        if (findState.subscriberInfo != null) {
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            for (SubscriberMethod subscriberMethod : array) {
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    // 逐個新增進 findState.subscriberMethods
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            // 使用反射的方式獲取單個類的訂閱方法
            findUsingReflectionInSingleClass(findState);
        }
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}
複製程式碼

FindState 封裝了所有的訂閱者和訂閱方法的集合。

static class FindState {
    //儲存所有訂閱方法
    final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
    //事件型別為Key,訂閱方法為Value
    final Map<Class, Object> anyMethodByEventType = new HashMap<>();
    //訂閱方法為Key,訂閱者的Class物件為Value
    final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
    final StringBuilder methodKeyBuilder = new StringBuilder(128);

    Class<?> subscriberClass;
    Class<?> clazz;
    boolean skipSuperClasses;
    SubscriberInfo subscriberInfo;
    //......
}
複製程式碼

通過 prepareFindState 獲取到 FindState 物件,根據 FindState 物件可以進行下一步判斷,

private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];
private FindState prepareFindState() {
    //找到 FIND_STATE_POOL 物件池
    synchronized (FIND_STATE_POOL) {
        for (int i = 0; i < POOL_SIZE; i++) {
            //當找到了對應的FindState
            FindState state = FIND_STATE_POOL[i];
            if (state != null) {//FindState 非空表示已經找到
                FIND_STATE_POOL[i] = null; //清空找到的這個FindState,為了下一次能接著複用這個FIND_STATE_POOL池
                return state;//返回該 FindState
            }
        }
    }
    //如果依然沒找到,則建立一個新的 FindState
    return new FindState();
}
複製程式碼

####4.1.3、findUsingReflectionInSingleClass() 方法解析

通過反射找到對應的方法,並進行過濾

findUsingReflectionInSingleClass()解析.png

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        // This is faster than getMethods, especially when subscribers are fat classes like Activities
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
        methods = findState.clazz.getMethods();
        findState.skipSuperClasses = true;
    }
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        //忽略非 public 和 static 的方法
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            // 獲取訂閱方法的所有引數
            Class<?>[] parameterTypes = method.getParameterTypes();
            // 訂閱方法只能有一個引數,否則忽略
            if (parameterTypes.length == 1) {
                // 獲取有 Subscribe 的註解
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    // 獲取第一個引數
                    Class<?> eventType = parameterTypes[0];
                    // 檢查 eventType 決定是否訂閱,通常訂閱者不能有多個 eventType 相同的訂閱方法
                    if (findState.checkAdd(method, eventType)) {
                        // 獲取執行緒模式
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        // 新增訂閱方法進 List
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException("@Subscribe method " + methodName +
                        "must have exactly 1 parameter but has " + parameterTypes.length);
            }
        } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(methodName +
                    " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
        }
    }
}
複製程式碼

經過修飾符、引數個數、是否有註解、和訂閱者是否有 eventType 相同的方法幾層條件的篩選,最終將訂閱方法新增進 findState 的 subscriberMethods 這個 List 中。

EventBus 不僅僅獲取當前類的訂閱方法,還會獲取它所有父類的訂閱方法。

在 EventBus 中,一個訂閱者包括這個訂閱者的所有父類和子類,不會有多個方法相同的去接收同一個事件,但是有可能出現這樣一種情況,子類去訂閱了該事件,父類也去訂閱了該事件。當出現這種情況,EventBus 如何判斷?通過呼叫 checkAddWithMethodSignature() 方法,根據方法簽名來檢查。

####4.1.4、checkAdd() & checkAddWithMethodSignature() 方法解析

checkAdd()&checkAddWithMethodSignature()解析.png

boolean checkAdd(Method method, Class<?> eventType) {
    //事件型別為Key,訂閱方法為Value
	final Map<Class, Object> anyMethodByEventType = new HashMap<>();
    //put()方法執行之後,返回的是之前put的值
    Object existing = anyMethodByEventType.put(eventType, method);
    if (existing == null) {
        return true;
    } else {
        if (existing instanceof Method) {
            if (!checkAddWithMethodSignature((Method) existing, eventType)) {
                // Paranoia check
                throw new IllegalStateException();
            }
            // Put any non-Method object to "consume" the existing Method
            anyMethodByEventType.put(eventType, this);
        }
        //根據方法簽名來檢查
        return checkAddWithMethodSignature(method, eventType);
    }
}

//訂閱方法為Key,訂閱者的Class物件為Value
final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
    methodKeyBuilder.setLength(0);
    methodKeyBuilder.append(method.getName());
    methodKeyBuilder.append('>').append(eventType.getName());

    String methodKey = methodKeyBuilder.toString();
    Class<?> methodClass = method.getDeclaringClass();
    //put方法返回的是put之前的物件
    Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
    //如果methodClassOld不存在或者是methodClassOld的父類的話,則表明是它的父類,直接返回true。否則,就表明在它的子類中也找到了相應的訂閱,執行的 put 操作是一個 revert 操作,put 進去的是 methodClassOld,而不是 methodClass
    if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
        // Only add if not already found in a sub class
        return true;
    } else {
        //這裡是一個revert操作,所以如果找到了它的子類也訂閱了該方法,則不允許父類和子類都同時訂閱該事件,put 的是之前的那個 methodClassOld,就是將以前的那個 methodClassOld 存入 HashMap 去覆蓋相同的訂閱者。
        //不允許出現一個訂閱者有多個相同方法訂閱同一個事件
        // Revert the put, old class is further down the class hierarchy
        subscriberClassByMethodKey.put(methodKey, methodClassOld);
        return false;
    }
}
複製程式碼

FindState 的流程

  1. 呼叫 prepareFindState(),從 FIND_STATE_POOL 中獲取一個 FindState,如果沒有找到,則建立一個新的 FindState。
  2. 將 subscriberClass 通過 FindState 進行初始化。
  3. 返回所有的訂閱者的方法和集合。

到此,兩種方式講解完畢。無論通過哪種方式獲取,獲取到訂閱方法 List 之後,接下來是真正訂閱的過程,回到register() 中看程式碼。

###4.2、subscribe 訂閱

public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        //迭代每個 Subscribe 方法,呼叫 subscribe() 傳入 subscriber(訂閱者) 和 subscriberMethod(訂閱方法) 完成訂閱,
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}
複製程式碼

subscribe訂閱.png

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

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;
    // 建立 Subscription 封裝訂閱者和訂閱方法資訊
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    //可併發讀寫的ArrayList,key為EventType,value為Subscriptions
    //根據事件型別從 subscriptionsByEventType 這個 Map 中獲取 Subscription 集合
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    //如果為 null,表示還沒有訂閱過,建立並 put 進 Map
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        //若subscriptions中已經包含newSubscription,表示該newSubscription已經被訂閱過,丟擲異常
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType);
        }
    }

    // 按照優先順序插入subscriptions
    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }

    //key為訂閱者,value為eventType,用來存放訂閱者中的事件型別
    //private final Map<Object, List<Class<?>>> typesBySubscriber;
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    //將EventType放入subscribedEvents的集合中
    subscribedEvents.add(eventType);

    //判斷是否為Sticky事件
    if (subscriberMethod.sticky) {
        //判斷是否設定了事件繼承
        if (eventInheritance) {
            //獲取到所有Sticky事件的Set集合
            Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
            //遍歷所有Sticky事件
            for (Map.Entry<Class<?>, Object> entry : entries) {
                Class<?> candidateEventType = entry.getKey();
                //判斷當前事件型別是否為黏性事件或者其子類
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    // 執行設定了 sticky 模式的訂閱方法
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}
複製程式碼

4.3、register 流程圖

register流程圖.png

事件的訂閱講解到這裡,接下來看看事件的分發 Post。

五、Post 分發

###5.1、Post 方法解析

一般的事件釋出方式

EventBus.getDefault().post(new EventTest());
複製程式碼

接下來看看 Post 方法的具體程式碼。

post分發.png

//currentPostingThreadState 執行緒獨有的
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            return new PostingThreadState();
        }
    };

public void post(Object event) {
    //獲取當前執行緒的 posting 狀態
    PostingThreadState postingState = currentPostingThreadState.get();
    //獲取當前事件佇列
    List<Object> eventQueue = postingState.eventQueue;
    //將事件新增進當前執行緒的事件佇列
    eventQueue.add(event);
	//判斷是否正在posting
    if (!postingState.isPosting) {
        postingState.isMainThread = isMainThread();
        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;
        }
    }
}

//傳送事件的執行緒封裝類
final static class PostingThreadState {
    final List<Object> eventQueue = new ArrayList<>();//事件佇列
    boolean isPosting;//是否正在 posting
    boolean isMainThread;//是否為主執行緒
    Subscription subscription;
    Object event;
    boolean canceled;//是否已經取消
}
複製程式碼

EventBus 用 ThreadLocal 儲存每個執行緒的 PostingThreadState,一個儲存了事件釋出狀態的類,當 post 一個事件時,新增到事件佇列末尾,等待前面的事件釋出完畢後再拿出來釋出,這裡看事件釋出的關鍵程式碼postSingleEvent()。

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
   Class<?> eventClass = event.getClass();
   boolean subscriptionFound = false;
   if (eventInheritance) {
       //查詢到所有繼承關係的事件型別,將該類的父類全部放入集合中
       List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
       int countTypes = eventTypes.size();
       
       for (int h = 0; h < countTypes; h++) {
           Class<?> clazz = eventTypes.get(h);
           //判斷是否找到訂閱者訂閱了該事件
           subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
       }
   } else {
       subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
   }
   if (!subscriptionFound) {
       if (logNoSubscriberMessages) {
           logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
       }
       if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
               eventClass != SubscriberExceptionEvent.class) {
           //傳送沒有訂閱者訂閱該事件
           post(new NoSubscriberEvent(this, event));
       }
   }
}
複製程式碼

程式碼也非常簡單,首先看 eventInheritance 這個屬性,是否開啟事件繼承,若是,找出釋出事件的所有父類,也就是 lookupAllEventTypes(),然後遍歷每個事件型別進行釋出。若不是,則直接釋出該事件。

如果需要釋出的事件沒有找到任何匹配的訂閱資訊,則釋出一個 NoSubscriberEvent 事件。這裡只看釋出事件的關鍵程式碼 postSingleEventForEventType()。

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
   CopyOnWriteArrayList<Subscription> subscriptions;
   //獲取到 Subscription 的集合
   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 傳送
               postToSubscription(subscription, event, postingState.isMainThread);
               aborted = postingState.canceled;
           } finally {
               postingState.event = null;
               postingState.subscription = null;
               postingState.canceled = false;
           }
           if (aborted) {
               break;
           }
       }
       return true;
   }
   return false;
}
複製程式碼

來到這裡,開始根據事件型別匹配出訂閱資訊,如果該事件有訂閱資訊,則執行 postToSubscription(),釋出事件到每個訂閱者,返回 true,若沒有,則返回 false。繼續追蹤釋出事件到具體訂閱者的程式碼 postToSubscription()。

###5.2、postToSubscription() 方法解析

postToSubscription()解析.png

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
        //訂閱執行緒跟隨釋出執行緒,EventBus 預設的訂閱方式
        case POSTING:
            // 訂閱執行緒和釋出執行緒相同,直接訂閱
            invokeSubscriber(subscription, event);
            break;
        // 訂閱執行緒為主執行緒
        case MAIN:
            //如果在 UI 執行緒,直接呼叫 invokeSubscriber
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
     //如果不在 UI 執行緒,用 mainThreadPoster 進行排程,即上文講述的 HandlerPoster 的 Handler 非同步處理,將訂閱執行緒切換到主執行緒訂閱
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        // 訂閱執行緒為主執行緒
        case MAIN_ORDERED:
            if (mainThreadPoster != null) {
                mainThreadPoster.enqueue(subscription, event);
            } else {
                // temporary: technically not correct as poster not decoupled from subscriber
                invokeSubscriber(subscription, event);
            }
            break;
        // 訂閱執行緒為後臺執行緒
        case BACKGROUND:
            //如果在 UI 執行緒,則將 subscription 新增到後臺執行緒的執行緒池
            if (isMainThread) {
                backgroundPoster.enqueue(subscription, event);
            } else {
            //不在UI執行緒,直接分發
                invokeSubscriber(subscription, event);
            }
            break;
        // 訂閱執行緒為非同步執行緒
        case ASYNC:
            // 使用執行緒池執行緒訂閱
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
    }
}
複製程式碼

訂閱者五種執行緒模式的特點對應的就是以上程式碼,簡單來講就是訂閱者指定了在哪個執行緒訂閱事件,無論釋出者在哪個執行緒,它都會將事件釋出到訂閱者指定的執行緒。這裡我們繼續看實現訂閱者的方法。

void invokeSubscriber(Subscription subscription, Object event) {
    try {
        subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
    } catch (InvocationTargetException e) {
        handleSubscriberException(subscription, event, e.getCause());
    } catch (IllegalAccessException e) {
        throw new IllegalStateException("Unexpected exception", e);
    }
}
複製程式碼

訂閱者接收到了事件,呼叫訂閱方法,傳入釋出的事件作為引數,至此,事件釋出過程就結束了。

5.3、post 流程圖

post流程圖.png

六、unregister 反註冊

先看反註冊的程式碼

EventBus.getDefault().unregister(this);
複製程式碼

跟蹤 unregister()

public synchronized void unregister(Object subscriber) {
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        typesBySubscriber.remove(subscriber);
    } else {
        logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}
複製程式碼

註冊過程我們就知道 typesBySubscriber 是儲存訂閱者的所有訂閱事件型別的一個 Map,這裡根據訂閱者拿到訂閱事件型別 List,然後逐個取消訂閱,最後 typesBySubscriber 移除該訂閱者,。這裡只需要關注它是如果取消訂閱的,跟蹤 unsubscribeByEventType()。

/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions != null) {
        int size = subscriptions.size();
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
            if (subscription.subscriber == subscriber) {
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}
複製程式碼

subscriptionsByEventType 是儲存事件型別對應訂閱資訊的 Map,程式碼邏輯非常清晰,找出某事件型別的訂閱資訊 List,遍歷訂閱資訊,將要取消訂閱的訂閱者和訂閱資訊封裝的訂閱者比對,如果是同一個,則說明該訂閱資訊是將要失效的,於是將該訂閱資訊移除。

七、總結

回顧一下 EventBus 的三個步驟

  1. 註冊訂閱者
  2. 事件釋出
  3. 反註冊訂閱者

好了,EventBus 的原始碼解析到這就結束了,想進一步瞭解 EventBus 的朋友可以親自去閱讀原始碼。

相關文章