EventBus源碼研讀(上)

基礎用法

在讀代碼之前,首先你得了解它的基本用法.如果你已經能夠很熟練的使用EventBus等事件總線庫了,那麼你可以跳過本節.
首先引入依賴包,查看GitHub主頁的說明:https://github.com/greenrobot/EventBus
在Gradle文件加入 
compile 'de.greenrobot:eventbus:2.4.0'

用法與廣播相同,且比廣播更簡單:

註冊訂閱者

首先你需要註冊一個事件訂閱者,爲了方便理解你可以把他當成廣播的廣播接收者 你可以在任何一個類中使用如下代碼註冊以及解除註冊

//把當前類註冊爲訂閱者(接收者)
EventBus.getDefault().register(this);

//解除註冊當前類(同廣播一樣,一定要調用,否則會內存泄露)
EventBus.getDefault().unregister(this);

註冊了訂閱者以後,我們需要創建一個回調方法onEvent,當我們訂閱的事件發送的時候就會回調它

//其實命名不一定必須是onEvent(),但那屬於高級用法了,這裏我們只說最簡單的
public void onEvent(Object event) {}

事件發送

當有了訂閱者以後,我們的代碼已經可以工作了.但是此時的代碼是沒有意義的,我們訂閱的事件還沒有發生. 就像廣播需要一個sendBroadcast(),EventBus需要post(event) 
你可以在任何一個類中使用如下代碼發送事件:

/**
 * 這裏的event類型必須和上面我們onEvent()方法的參數類型一致
 * (子父類關係也不行,必須是相同類型,原因我們下面看源碼)
 */
EventBus.getDefault().post(event);

至此,EventBus就可以正常工作了.

進入源碼世界

入口類EventBus類

我們從使用的流程來,首先看EventBus#getDefault()

public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
}

只是簡單的維護單例,調用構造方法,再看構造方法,調用重載的構造方法,重載的構造方法又需要一個EventBusBuilder對象

public EventBus() {
    this(DEFAULT_BUILDER);
}

EventBus(EventBusBuilder builder) {
}

EventBusBuilder類

看名字就知道,這個類是用來創建EventBus對象的.
開源實驗室:圖1

Builder類提供了這麼多個可選的配置屬性,這裏變量含義大家直接看我的註釋,就不多作解釋了
我們主要來看最終的建造方法

/**
 * 根據參數創建對象,並賦值給EventBus.defaultInstance, 必須在默認的eventbus對象使用以前調用
 *
 * @throws EventBusException if there's already a default EventBus instance in place
 */
public EventBus installDefaultEventBus() {
    synchronized (EventBus.class) {
        if (EventBus.defaultInstance != null) {
            throw new EventBusException("Default instance already exists." +
                    " It may be only set once before it's used the first time to ensure " +
                    "consistent behavior.");
        }
        EventBus.defaultInstance = build();
        return EventBus.defaultInstance;
    }
}

/**
 * 根據參數創建對象
 */
public EventBus build() {
    return new EventBus(this);
}

EventBusBuilder類提供了兩種建造方法,還記得之前的getDefault()方法嗎,維護了一個單例對象,installDefaultEventBus() 方法建造的EventBus對象最終會賦值給那個單例對象,但是有一個前提就是我們之前並沒有創建過那個單例對象. 
這裏大家思考一下,爲什麼如果EventBus.defaultInstance不爲null以後程序要拋出異常?咱們之後說答案。
第二個方法就是默認的建造者方法了.

再回到我們的EventBus構造方法,根據提供的建造者初始化了一大堆屬性
圖3

我們繼續看這些初始化的字段.

三個Poster類

先是一大堆Map,看不懂,跳過去,我們先來看這三個Poster,需要說明的一點就是:Poster只負責處理粘滯事件,原因我們之後看代碼。

private final HandlerPoster mainThreadPoster; //前臺發送者
private final BackgroundPoster backgroundPoster; //後臺發送者
private final AsyncPoster asyncPoster;   //後臺發送者(只讓隊列第一個待訂閱者去響應)

其實從類名我們就能看出個大概了,就是三個發送事件的方法。
我們來看看他們的內部實現. 
這幾個Poster的設計可以說是整個EventBus的一個經典部分,越看越想繼續多看幾遍.

每個Poster中都有一個發送任務隊列,PendingPostQueue queue;

進到隊列裏面再看 定義了兩個節點,從字面上理解就是隊列的頭節點和尾節點

private PendingPost head; //待發送對象隊列頭節點
private PendingPost tail;//待發送對象隊列尾節點

再看這個PendingPost類的實現:

//單例池,複用對象
private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();

Object event; //事件類型
Subscription subscription; //訂閱者
PendingPost next; //隊列下一個待發送對象

首先是提供了一個的設計,類似於我們的線程池,目的是爲了減少對象創建的開銷,當一個對象不用了,我們可以留着它,下次再需要的時候返回這個保留的而不是再去創建。
再看最後的變量,PendingPost next 非常典型的隊列設計,隊列中每個節點都有一個指向下一個節點的指針(sorry,數據結構用C學的)。

/**
 * 首先檢查複用池中是否有可用,如果有則返回複用,否則返回一個新的
 *
 * @param subscription 訂閱者
 * @param event        訂閱事件
 * @return 待發送對象
 */
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);
}
/**
 * 回收一個待發送對象,並加入複用池
 *
 * @param pendingPost 待回收的待發送對象
 */
static void releasePendingPost(PendingPost pendingPost) {
    pendingPost.event = null;
    pendingPost.subscription = null;
    pendingPost.next = null;
    synchronized (pendingPostPool) {
        // 防止池無限增長
        if (pendingPostPool.size() < 10000) {
            pendingPostPool.add(pendingPost);
        }
    }
}

obtainPendingPost(),對池複用的實現,每次新創建的節點尾指針都爲 null 。
releasePendingPost(),回收pendingPost對象,既然有從池中取,當然需要有存。這裏,原作非常細心的加了一次判斷,if (pendingPostPool.size() < 10000) 其實我覺得10000都很大了,1000就夠了,我們一次只可能創建一個pendingPost,如果ArrayList裏面存了上千條都沒有取走,那麼肯定是使用出錯了。 

PendingPost的代碼我們就看完了,再回到上一級,隊列的設計:

接着是PendingPostQueue的入隊方法

synchronized void enqueue(PendingPost pendingPost) {
	...
    if (tail != null) {
        tail.next = pendingPost;
        tail = pendingPost;
    } else if (head == null) {
        head = tail = pendingPost;
    } 
    ...
}

首先將當前節點的上一個節點(入隊前整個隊列的最後一個節點)的尾指針指向當期正在入隊的節點(傳入的參數pendingPost),並將隊列的尾指針指向自己(自己變成隊列的最後一個節點),這樣就完成了入隊。
如果是隊列的第一個元素(隊列之前是空的),那麼直接將隊列的頭尾兩個指針都指向自身就行了。
出隊也是類似的隊列指針操作 

synchronized PendingPost poll() {
    PendingPost pendingPost = head;
    if (head != null) {
        head = head.next;
        if (head == null) {
            tail = null;
        }
    }
    return pendingPost;
}

首先將出隊前的頭節點保留一個臨時變量(它就是要出隊的節點),拿到這個將要出隊的臨時變量的下一個節點指針,將出隊前的第二個元素(出隊後的第一個元素)的賦值爲現在隊列的頭節點,出隊完成。  
值得提一點的就是,PendingPostQueue的所有方法都聲明瞭synchronized,這意味着在多線程下它依舊可以正常工作,細想想這也是必須的,對嗎?

再回到上一級,接着是HandlerPoster的入隊方法enqueue(),

/**
 * @param subscription 訂閱者
 * @param event        訂閱事件
 */
void enqueue(Subscription subscription, Object event) {
    PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
    synchronized (this) {
        queue.enqueue(pendingPost);
        if (!handlerActive) {
            handlerActive = true;
            if (!sendMessage(obtainMessage())) {
                throw new EventBusException("Could not send handler message");
            }
        }
    }
}

入隊方法會根據參數創建 待發送對象 pendingPost 並加入隊列,如果此時 handleMessage() 沒有在運行中,則發送一條空消息讓 handleMessage 響應 
接着是handleMessage()方法

@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) {
                    // 雙重校驗,類似單例中的實現
                    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;
    }
}

handleMessage()不停的在待發送隊列queue中去取消息。 需要說明的是在循環之外有個臨時boolean變量rescheduled,最後是通過這個值去修改了handlerActive。而 handlerActive 是用來判斷當前queue中是否有正在發送對象的任務,看到上面的入隊方法enqueue(),如果已經有任務在跑着了,就不需要再去sendMessage()喚起我們的handleMessage()

最終通過eventBus對象的invokeSubscriber()最終發送出去,並回收這個pendingPost,讓註冊了的訂閱者去響應(相當於回調),至於這個發送方法,我們之後再看。

看完了HandlePoster類,另外兩個異步的發送者實現代碼也差不多,唯一的區別就是另外兩個是工作在異步,實現的Runnable接口,大家自己類比,這裏就不帖代碼了.

Poster工作原理

最後我們再來回顧一下PosterPendingPostQueuePendingPost這三個類,再看看下面這張圖,是不是有種似曾相識的感覺。 

開源實驗室:圖4

啊哈,那是HandleMessageLooper的工作原理,再看看Poster的
開源實驗室:圖5

至此,整個EventBus源碼的發送接收核心部分已經分析完了。
還記得上面我們留下的那幾個問題嗎:
1、爲什麼如果EventBus.defaultInstance不爲null以後程序要拋出異常?
2、Poster只對粘滯事件有效的說明代碼在哪。
3、invokeSubscriber()最終的發送怎麼實現的。
接下來我們繼續分析它的註冊流程以及粘滯事件的設計(那又是一個經典的地方)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章