小點(diǎn)知識——觀察者模式,由淺談到理解

觀察者模式

自己對觀察者模式的理解:
定義:Define a one-to-many dependency between objects so that when one object changes state, all its dependents aer notified and updated automatically.

意圖:定義對象間的一種一對多的依賴關(guān)系庆揩,當(dāng)一個(gè)對象改變狀態(tài)時(shí)喻频,則所有依賴于它的對象都會(huì)得到通知并被自動(dòng)更新茸俭。

主要解決:一個(gè)對象狀態(tài)改變給其他對象通知的問題,而且要考慮到易用和低耦合寨典,保證高度的協(xié)作。

使用場景:

1房匆、有多個(gè)子類共有的方法耸成,且邏輯相同。
2、重要的、復(fù)雜的方法怔球,可以考慮作為模板方法祖灰。

優(yōu)缺點(diǎn):

優(yōu)點(diǎn):

1、觀察者和被觀察者是抽象耦合的侮攀。
2、建立一套觸發(fā)機(jī)制扫尺。

缺點(diǎn):

1约急、如果一個(gè)被觀察者對象有很多的直接和間接的觀察者的話零远,將所有的觀察者都通知到會(huì)花費(fèi)很多時(shí)間。
2厌蔽、如果在觀察者和觀察目標(biāo)之間有循環(huán)依賴的話牵辣,觀察目標(biāo)會(huì)觸發(fā)它們之間進(jìn)行循環(huán)調(diào)用,可能導(dǎo)致系統(tǒng)崩潰奴饮。
3服猪、觀察者模式?jīng)]有相應(yīng)的機(jī)制讓觀察者知道所觀察的目標(biāo)對象是怎么發(fā)生變化的,而僅僅只是知道觀察目標(biāo)發(fā)生了變化拐云。

注意事項(xiàng):

  1. JAVA 中已經(jīng)有了對觀察者模式的支持類罢猪。
  2. 避免循環(huán)引用。
  3. 如果順序執(zhí)行叉瘩,某一觀察者錯(cuò)誤會(huì)導(dǎo)致系統(tǒng)卡殼膳帕,一般采用異步方式。

觀察者模式的組成:

  1. 觀察者薇缅,我們稱它為Observer危彩,有時(shí)候我們也稱它為訂閱者,即Subscriber泳桦。
  2. 被觀察者汤徽,我們稱它為Observable,即可以被觀察的東西灸撰,有時(shí)候還會(huì)稱之為主題谒府,即Subject。

擼起袖子浮毯,為了驗(yàn)證我們的觀察者模式完疫,一波觀察者模式的場景,首先定義一個(gè)Theif為觀察目標(biāo)债蓝。


    public class Theif {
        //描述小偷形象
        private String description;
    
        public Theif(String description){
            this.description = description;
        }
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        @Override
        public String toString() {
            return "一名"+description+"小偷在進(jìn)行扒竊";
        }
    }

定義我們的被觀察者壳鹤,我們希望能夠通用,所以定義成泛型饰迹。泛型指具體的被觀察者芳誓,內(nèi)部暴露出register和unRegister方法供觀察者訂閱和取消訂閱,至于觀察者們的保存啊鸭,我們用ArrayList集合儲(chǔ)存即可锹淌,另外,當(dāng)小偷發(fā)生變化的時(shí)候莉掂,需要通知觀察者來做出響應(yīng)葛圃,還需要一個(gè)notifyObservers方法。

public class Observable<T> {
    //|存放觀察者們
    List<Observer<T>> mObservers = new ArrayList<>();
    //供觀察者訂閱 傳入觀察者即可
    public void register(Observer<T> observer) {    
        if (observer == null) {    
            throw new NullPointerException("observer == null");    
        }
        //多線程 添加同步鎖
        // 如果線程A進(jìn)入了該代碼,線程B 在等待库正,這是A線程創(chuàng)建完一個(gè)實(shí)例出來后曲楚,線程B 獲得鎖進(jìn)入同步代碼,
        // 實(shí)例已經(jīng)存在褥符,木有必要再添加一個(gè)
        synchronized (this) {    
            if (!mObservers.contains(observer))    
                mObservers.add(observer);    
        }    
    }
    //根據(jù)傳入的觀察者 供觀察者取消訂閱
    public synchronized void unregister(Observer<T> observer) {    
        mObservers.remove(observer);    
    }    
    //通知觀察者們
    public void notifyObservers(T data) {    
        for (Observer<T> observer : mObservers) {    
            observer.onFindThief(this, data);
        }    
    }    

}    

定義Observer接口為觀察者龙誊,接收到消息后,即進(jìn)行更新操作喷楣,對接收到的信息進(jìn)行處理趟大。觀察者們 只需要實(shí)現(xiàn)Observer接口,該接口也是泛型的

public interface Observer<T> {
    //觀察者發(fā)現(xiàn)目標(biāo),通過調(diào)用這個(gè)方法铣焊,并攜帶被觀察者和數(shù)據(jù) 進(jìn)行更新
    void onFindThief(Observable<T> observable,T data);
}

一旦訂閱逊朽,發(fā)現(xiàn)小偷有變化,就會(huì)調(diào)用onFindThief接口曲伊。一定要用register方法來注冊叽讳,否則觀察者收不到變化的信息,而一旦不感興趣坟募,就可以調(diào)用unregister方法岛蚤。

public class TestJava {

    public static void main(String agr[]){

        //一旦訂閱,發(fā)現(xiàn)小偷有變化懈糯,就會(huì)調(diào)用onFindThief接口
        //Observable被觀察者 被觀察者的對象是小偷(Theif)
        Observable<Theif> observable=new Observable<>();
        //為了簡便直接在這里創(chuàng)建一個(gè)觀察者對象涤妒。觀察者1  對小偷進(jìn)行觀察,一旦發(fā)現(xiàn)小偷就打印出目標(biāo)
        Observer<Theif> observer1 = new Observer<Theif>() {
            @Override
            public void onFindThief(Observable<Theif> observable, Theif data) {
                System.out.println("觀察者1——發(fā)現(xiàn)目標(biāo):"+data.toString());
            }
        };
        //觀察者2  對小偷進(jìn)行觀察赚哗,一旦發(fā)現(xiàn)小偷就打印出目標(biāo)
        Observer<Theif> observer2 = new Observer<Theif>() {
            @Override
            public void onFindThief(Observable<Theif> observable, Theif data) {
                System.out.println("觀察者2——發(fā)現(xiàn)目標(biāo):"+data.toString());
            }
        };
        //一定要用register方法來注冊她紫,否則觀察者收不到變化的信息,而一旦不感興趣蜂奸,就可以調(diào)用unregister方法
        observable.register(observer1);
        observable.register(observer2);

        //目標(biāo)
        Theif theif = new Theif("穿紅色外套");
        //被觀察的目標(biāo)有變化犁苏,通知觀察者們
        observable.notifyObservers(theif);
        //觀察者1 吃飯時(shí)間到,不再觀察了 調(diào)用unregister方法注銷被觀察對象扩所,其他觀察者還有觀察
        observable.unregister(observer1);

        //目標(biāo)
        Theif theif2 = new Theif("中年男子,染黃頭發(fā)");
        //被觀察的目標(biāo)有變化朴乖,通知觀察者們
        observable.notifyObservers(theif2);

    }
}

輸出結(jié)果為:

觀察者1——發(fā)現(xiàn)目標(biāo):一名穿紅色外套小偷在進(jìn)行扒竊
觀察者2——發(fā)現(xiàn)目標(biāo):一名穿紅色外套小偷在進(jìn)行扒竊
觀察者2——發(fā)現(xiàn)目標(biāo):一名中年男子祖屏,染黃頭發(fā)小偷在進(jìn)行扒竊

一個(gè)簡單的觀察者模式就出來了,當(dāng)然上面的代碼還可以優(yōu)化买羞。有興趣的可以自己進(jìn)一步優(yōu)化袁勺。

小結(jié)

觀察者模式主要包括兩個(gè)部分:

  • Subject被觀察者。是一個(gè)接口或者是抽象類畜普,定義被觀察者必須實(shí)現(xiàn)的職責(zé)期丰,它必須能偶動(dòng)態(tài)地增加、取消觀察者,管理觀察者并通知觀察者钝荡。
  • Observer觀察者街立。觀察者接收到消息后,即進(jìn)行更新操作埠通,對接收到的信息進(jìn)行處理赎离。
  1. ConcreteSubject具體的被觀察者。定義被觀察者自己的業(yè)務(wù)邏輯端辱。
  2. ConcreteObserver具體觀察者梁剔。每個(gè)觀察者在接收到信息后處理的方式不同,各個(gè)觀察者有自己的處理邏輯舞蔽。
  3. 上面的例子Observable為被觀察者 被觀察者的對象是小偷(Theif)荣病,也即是ConcreteSubject具體的被觀察者
  4. 上面的例子:可以自己定義一個(gè)類并實(shí)現(xiàn)Observer接口,那么這個(gè)類就是ConcreteObserver具體觀察者渗柿。

開源框架EventBus众雷、Rxjava等也是基于觀察者模式的,觀察者模式的注冊做祝,取消砾省,發(fā)送事件三個(gè)典型方法都有。以EventBus3.0作為例子混槐,分析其源碼编兄。

什么是EventBus

EventBus是一個(gè)消息總線,以觀察者模式實(shí)現(xiàn)声登,用于簡化程序的組件狠鸳、線程通信,可以輕易切換線程悯嗓、開辟線程件舵。EventBus3.0跟先前版本的區(qū)別在于加入了annotation @Subscribe,取代了以前約定命名的方式

優(yōu)點(diǎn)

開銷小脯厨,代碼優(yōu)雅铅祸。將發(fā)送者和接受者解耦。

EventBus的用法——常用的幾個(gè)方法

EventBus.getDefault().register(Object subscriber);    
EventBus.getDefault().unregister(Object subscriber);    
EventBus.getDefault().post(Object event);  
EventBus.getDefault().postSticky(Object event);  

當(dāng)我們要調(diào)用EventBus的功能時(shí)合武,比如注冊或者發(fā)送事件等临梗,總會(huì)調(diào)用EventBus.getDefault()來獲取EventBus實(shí)例

//源碼
static volatile EventBus defaultInstance;
/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
}
public EventBus() {
  this(DEFAULT_BUILDER);
}

從源碼看出很明顯這是一個(gè)單例模式。如果對單例模式感興趣可以去研究一下單例模式稼跳。一看構(gòu)造函數(shù)this(DEFAULT_BUILDER)做了些什么盟庞。這里DEFAULT_BUILDER是默認(rèn)的EventBusBuilder用來構(gòu)造EventBus:

private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

進(jìn)入EventBusBuilder類看看,這么多個(gè)可選的配置屬性,這里變量含義大家直接看我的注釋,就不多作解釋了汤善。我們主要看最終的建造方法:

/**
 * 根據(jù)參數(shù)創(chuàng)建對象,并賦值給EventBus.defaultInstance, 必須在默認(rèn)的eventbus對象使用以前調(diào)用
 *
 * @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;
    }
}

/**
 * 根據(jù)參數(shù)創(chuàng)建對象
 */
public EventBus build() {
    return new EventBus(this);
}

EventBusBuilder類提供了兩種建造方法,還記得之前的getDefault()方法嗎,維護(hù)了一個(gè)單例對象,installDefaultEventBus() 方法建造的EventBus對象最終會(huì)賦值給那個(gè)單例對象,但是有一個(gè)前提就是我們之前并沒有創(chuàng)建過那個(gè)單例對象.

為什么如果EventBus.defaultInstance不為null程序要拋出異常什猖?先把這個(gè)問題留著票彪,繼續(xù)往下看。

回到EventBus的構(gòu)造方法中不狮,this(DEFAULT_BUILDER)是指EventBus一個(gè)帶實(shí)參的構(gòu)造函數(shù):

EventBus(EventBusBuilder builder) {
    subscriptionsByEventType = new HashMap<>();
    typesBySubscriber = new HashMap<>();
    stickyEvents = new ConcurrentHashMap<>();
    mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
    backgroundPoster = new BackgroundPoster(this);
    asyncPoster = new AsyncPoster(this);
    indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
            builder.strictMethodVerification, builder.ignoreGeneratedIndex);
    logSubscriberExceptions = builder.logSubscriberExceptions;
    logNoSubscriberMessages = builder.logNoSubscriberMessages;
    sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    throwSubscriberException = builder.throwSubscriberException;
    eventInheritance = builder.eventInheritance;
    executorService = builder.executorService;
}

EventBusBuilder類提供了這么多個(gè)可選的配置屬性,這里Map變量含義大家直接看我的注釋降铸。EventBus構(gòu)造函數(shù)也涉及到了構(gòu)造模式,感興趣的可以查閱一下荤傲,這里就不多說了垮耳。

這里說說EventBus中的三個(gè)Poster類(HandlerPoster、BackgroundPoster遂黍、AsyncPoster)终佛。三個(gè)Poster類的作用是什么?Poster應(yīng)該是發(fā)布時(shí)候會(huì)涉及到雾家,不過我們之后看代碼再作解答铃彰,先解決這三個(gè)類,看代碼芯咧。

private final HandlerPoster mainThreadPoster; //前臺發(fā)送者
private final BackgroundPoster backgroundPoster; //后臺發(fā)送者
private final AsyncPoster asyncPoster;   //后臺發(fā)送者(只讓隊(duì)列第一個(gè)待訂閱者去響應(yīng))

這幾個(gè)Poster的設(shè)計(jì)可以說是整個(gè)EventBus的一個(gè)經(jīng)典部分牙捉。每個(gè)Poster中都有一個(gè)發(fā)送任務(wù)隊(duì)列,PendingPostQueue queue。進(jìn)到隊(duì)列PendingPostQueue里面再看敬飒。

private PendingPost head; //待發(fā)送對象隊(duì)列頭節(jié)點(diǎn)
private PendingPost tail;//待發(fā)送對象隊(duì)列尾節(jié)點(diǎn)

接著我們再看這個(gè)PendingPost類的實(shí)現(xiàn):

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

Object event; //事件類型
Subscription subscription; //訂閱者
PendingPost next; //隊(duì)列下一個(gè)待發(fā)送對象

首先是提供了一個(gè)池的設(shè)計(jì)邪铲,類似于我們的線程池,目的是為了減少對象創(chuàng)建的開銷无拗,當(dāng)一個(gè)對象不用了带到,我們可以留著它,下次再需要的時(shí)候返回這個(gè)保留的而不是再去創(chuàng)建英染。接著看最后的變量揽惹,PendingPost next 非常典型的隊(duì)列設(shè)計(jì),隊(duì)列中每個(gè)節(jié)點(diǎn)都有一個(gè)指向下一個(gè)節(jié)點(diǎn)的指針四康。

/**
 * 首先檢查復(fù)用池中是否有可用,如果有則返回復(fù)用,否則返回一個(gè)新的
 *
 * @param subscription 訂閱者
 * @param event        訂閱事件
 * @return 待發(fā)送對象
 */
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);
}

/**
 * 回收一個(gè)待發(fā)送對象,并加入復(fù)用池
 *
 * @param pendingPost 待回收的待發(fā)送對象
 */
static void releasePendingPost(PendingPost pendingPost) {
    pendingPost.event = null;
    pendingPost.subscription = null;
    pendingPost.next = null;
    synchronized (pendingPostPool) {
        // 防止池?zé)o限增長
        if (pendingPostPool.size() < 10000) {
            pendingPostPool.add(pendingPost);
        }
    }
}

obtainPendingPost()方法中對池復(fù)用的實(shí)現(xiàn)搪搏,每次新創(chuàng)建的節(jié)點(diǎn)尾指針都為 null 。細(xì)心的人還看到了一個(gè)關(guān)鍵字synchronized闪金。Synchronized是Java并發(fā)編程中最常用的用于保證線程安全的方式疯溺,其使用相對也比較簡單。(但是如果能夠深入了解其原理毕泌,對監(jiān)視器鎖等底層知識有所了解喝检,一方面可以幫助我們正確的使用Synchronized關(guān)鍵字,另一方面也能夠幫助我們更好的理解并發(fā)編程機(jī)制撼泛,有助我們在不同的情況下選擇更優(yōu)的并發(fā)策略來完成任務(wù)。對平時(shí)遇到的各種并發(fā)問題澡谭,也能夠從容的應(yīng)對愿题。不過這都是題個(gè)話了损俭,有興趣可以多了解一下。)

releasePendingPost()潘酗,回收pendingPost對象杆兵,既然可以從池中取,當(dāng)然需要可以存仔夺。非常細(xì)心的可以注意到池的大小小于10000琐脏,不禁會(huì)想這是為什么呢,想表達(dá)什么呢缸兔?日裙,if (pendingPostPool.size() < 10000) 其實(shí)我覺得10000都很大了,1000就夠了惰蜜,我們一次只可能創(chuàng)建一個(gè)pendingPost昂拂,如果ArrayList里面存了上千條都沒有取走,那么肯定是使用出錯(cuò)了抛猖。當(dāng)然ArrayList里不會(huì)存放那么多池格侯,我只能猜測他們也只是預(yù)防一下,但也不是不可能不存在這樣的情況财著。

三個(gè)Poster類的最底層都是PendingPost類联四,PendingPost的代碼我們就看完了。那么繼續(xù)往上一級PendingPostQueue類了解撑教。PendingPostQueue這是一個(gè)隊(duì)列的設(shè)計(jì)朝墩。那么我們首先看看PendingPostQueue的入隊(duì)方法:

synchronized void enqueue(PendingPost pendingPost) {
    if (pendingPost == null) {
        throw new NullPointerException("null cannot be enqueued");
    }
    if (tail != null) {
        tail.next = pendingPost;
        tail = pendingPost;
    } else if (head == null) {
        head = tail = pendingPost;
    } else {
        throw new IllegalStateException("Head present, but no tail");
    }
    notifyAll();
}

從源碼可以看出首先來個(gè)判空處理。接著將當(dāng)前節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)(入隊(duì)前整個(gè)隊(duì)列的最后一個(gè)節(jié)點(diǎn))的尾指針指向當(dāng)期正在入隊(duì)的節(jié)點(diǎn)(傳入的參數(shù)pendingPost)驮履,并將隊(duì)列的尾指針指向自己(自己變成隊(duì)列的最后一個(gè)節(jié)點(diǎn))鱼辙,這樣就完成了入隊(duì)。如果是隊(duì)列的第一個(gè)元素(隊(duì)列之前是空的),那么直接將隊(duì)列的頭尾兩個(gè)指針都指向自身就行了玫镐。

接著看看PendingPostQueue的出隊(duì)也是類似的隊(duì)列指針操作:

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

首先將出隊(duì)前的頭節(jié)點(diǎn)保留一個(gè)臨時(shí)變量(它就是要出隊(duì)的節(jié)點(diǎn)),拿到這個(gè)將要出隊(duì)的臨時(shí)變量的下一個(gè)節(jié)點(diǎn)指針倒戏,將出隊(duì)前的第二個(gè)元素(出隊(duì)后的第一個(gè)元素)的賦值為現(xiàn)在隊(duì)列的頭節(jié)點(diǎn),出隊(duì)完成恐似。

非常細(xì)心的人會(huì)發(fā)現(xiàn) PendingPostQueue的所有方法都聲明了synchronized杜跷。這意味著在多線程下它依舊可以正常工作。如果對synchronized還是不太了解的可以查閱資料矫夷。

既然所有方法都聲明了synchronized葛闷,那就必定有他的道理。那么我們往上一級看看就知道方法前添加synchronized關(guān)鍵字是不是存在的意義双藕。接著看HandlerPoster的入隊(duì)方法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");
            }
        }
    }
}

入隊(duì)方法會(huì)根據(jù)參數(shù)創(chuàng)建淑趾,待發(fā)送對象pendingPost并加入隊(duì)列,從代碼上可以看出驗(yàn)證了synchronized關(guān)鍵字是有存在的意義。 handleMessage()沒有在運(yùn)行中,則發(fā)送一條空消息讓handleMessage響應(yīng)忧陪,那繼續(xù)看看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) {
                    // 雙重校驗(yàn),類似單例中的實(shí)現(xiàn)
                    pendingPost = queue.poll();
                    if (pendingPost == null) {
                        handlerActive = false;
                        return;
                    }
                }
            }
            //如果訂閱者沒有取消注冊,則分發(fā)消息
            eventBus.invokeSubscriber(pendingPost);
            
            //如果在一定時(shí)間內(nèi)仍然沒有發(fā)完隊(duì)列中所有的待發(fā)送者,則退出
            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()不停的在待發(fā)送隊(duì)列queue中去取消息扣泊。直得注意的是在循環(huán)之外有個(gè)臨時(shí)boolean變量rescheduled,最后是通過這個(gè)值去修改了handlerActive近范。而 handlerActive 是用來判斷當(dāng)前queue中是否有正在發(fā)送對象的任務(wù),看到上面的入隊(duì)方法enqueue(),如果已經(jīng)有任務(wù)在跑著了延蟹,就不需要再去sendMessage()喚起我們的handleMessage()评矩。最終通過eventBus對象的invokeSubscriber()最終發(fā)送出去,并回收這個(gè)pendingPost阱飘。那么這里有個(gè)問題了斥杜,什么時(shí)候才會(huì)通過eventBus對象的invokeSubscriber()發(fā)送出去?HandlePoster類已經(jīng)看完了沥匈,其實(shí)三個(gè)Poster類里面的實(shí)現(xiàn)都很相似易懂的蔗喂,以HandlePoster類來介紹,另外兩個(gè)類(BackgroundPoster、AsyncPoster)異步的發(fā)送者實(shí)現(xiàn)代碼也差不多,唯一的區(qū)別就是另外兩個(gè)是工作在異步,實(shí)現(xiàn)的Runnable接口咐熙。

整理一下上面出現(xiàn)和問題:

  1. 為什么如果EventBus.defaultInstance不為null程序要拋出異常弱恒?
  2. 三個(gè)Poster類(HandlerPoster、BackgroundPoster棋恼、AsyncPoster)的作用是什么返弹?
  3. invokeSubscriber()什么時(shí)候才觸發(fā)發(fā)送出去?

回顧一下Poster爪飘、PendingPostQueue义起、PendingPost這三個(gè)類,是不是有種似曾相識的感覺师崎。哈哈默终,沒有錯(cuò)。那是Handler犁罩、Message齐蔽、Looper的工作原理。如果不了解Handler可以查閱一下資料床估。

EventBus注冊

獲取到EventBus后含滴,可以將訂閱者注冊到EventBus中,下面來看一下register方法:

public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    // 用 subscriberMethodFinder 提供的方法丐巫,找到在 subscriber 這個(gè)類里面訂閱的內(nèi)容谈况。
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

通過SubscriberMethodFinder#findSubscriberMethods方法找出一個(gè)SubscriberMethod的集合,也就是傳進(jìn)來的訂閱者所有的訂閱的方法递胧,接下來遍歷訂閱者的訂閱方法來完成訂閱者的訂閱操作碑韵。對于SubscriberMethod(訂閱方法)類中,主要就是用保存訂閱方法的Method對象缎脾、線程模式祝闻、事件類型、優(yōu)先級遗菠、是否是粘性事件等屬性治筒,就不貼代碼了屉栓。那說說SubscriberMethodFinder類中的findSubscriberMethods方法吧舷蒲,SubscriberMethodFinder從字面理解耸袜,就是訂閱者方法發(fā)現(xiàn)者。

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
   //從緩存中獲取SubscriberMethod集合
   List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
   if (subscriberMethods != null) {
       return subscriberMethods;
   }
   //ignoreGeneratedIndex屬性表示是否忽略注解器生成的MyEventBusIndex
   if (ignoreGeneratedIndex) {
    //通過反射獲取subscriberMethods
       subscriberMethods = findUsingReflection(subscriberClass);
   } else {
       subscriberMethods = findUsingInfo(subscriberClass);
   }
   //在獲得subscriberMethods以后牲平,如果訂閱者中不存在@Subscribe注解并且為public的訂閱方法堤框,則會(huì)拋出異常。
   if (subscriberMethods.isEmpty()) {
       throw new EventBusException("Subscriber " + subscriberClass
               + " and its super classes have no public methods with the @Subscribe annotation");
   } else {
       METHOD_CACHE.put(subscriberClass, subscriberMethods);
       return subscriberMethods;
   }
}

首先從緩存中查找纵柿,如果找到了就返回subscriberMethods蜈抓。如果緩存中沒有的話,則根據(jù) ignoreGeneratedIndex選擇如何查找訂閱方法昂儒,ignoreGeneratedIndex屬性表示是否忽略注解器生成的MyEventBusIndex沟使。ignoreGeneratedIndex 默認(rèn)就是false,如果ignoreGeneratedIndex是true 那么通反射獲取subscriberMethods渊跋,可以通過EventBusBuilder來設(shè)置它的值腊嗡。最后,找到訂閱方法后拾酝,放入緩存燕少,以免下次繼續(xù)查找。

可以看到其中findUsingInfo()方法就是去索引中查找訂閱者的回調(diào)方法蒿囤,我們戳進(jìn)去看看這個(gè)方法的實(shí)現(xiàn):

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    // 最新版的EventBus3中客们,尋找方法時(shí)所需的臨時(shí)變量都被封裝到了FindState這個(gè)靜態(tài)內(nèi)部類中
    FindState findState = prepareFindState(); // 到對象池中取得上下文,避免頻繁創(chuàng)造對象材诽,這個(gè)設(shè)計(jì)很贊
    findState.initForSubscriber(subscriberClass); // 初始化尋找方法的上下文
    while (findState.clazz != null) { // 子類找完了底挫,會(huì)繼續(xù)去父類中找
        findState.subscriberInfo = getSubscriberInfo(findState); // 獲得訂閱者類的相關(guān)信息
        if (findState.subscriberInfo != null) { // 上一步能拿到相關(guān)信息的話,就開始把方法數(shù)組封裝成List
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            for (SubscriberMethod subscriberMethod : array) {
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    // checkAdd是為了避免在父類中找到的方法是被子類重寫的脸侥,此時(shí)應(yīng)該保證回調(diào)時(shí)執(zhí)行子類的方法
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else { // 索引中找不到建邓,降級成運(yùn)行時(shí)通過注解和反射去找
            findUsingReflectionInSingleClass(findState);
        }
        findState.moveToSuperclass(); // 上下文切換成父類
    }
    return getMethodsAndRelease(findState); // 找完后,釋放FindState進(jìn)對象池湿痢,并返回找到的回調(diào)方法
}

通過prepareFindState()找到所需的臨時(shí)變量都被封裝到了FindState這個(gè)靜態(tài)內(nèi)部類中涝缝,初始化尋找方法的上下文,子類找完了譬重,會(huì)繼續(xù)去父類中找拒逮。然后getSubscriberInfo方法來獲取訂閱者信息。如果獲得到訂閱者類的相關(guān)信息臀规,不僅會(huì)去遍歷父類滩援,而且還會(huì)避免因?yàn)橹貙懛椒▽?dǎo)致執(zhí)行多次回調(diào),過慮了重重保險(xiǎn)關(guān)卡塔嬉,然后subscriberMethods添加subscriberMethod玩徊。如果沒有獲得到訂閱者類的相關(guān)信息租悄,便會(huì)執(zhí)行findUsingReflectionInSingleClass方法,將訂閱方法保存到findState中恩袱。最后再通過getMethodsAndRelease方法對findState做回收處理并反回訂閱方法的List集合泣棋。其中需要關(guān)心的是getSubscriberInfo()是如何返回索引數(shù)據(jù)的。

private SubscriberInfo getSubscriberInfo(FindState findState) {
    if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) { // subscriberInfo已有實(shí)例畔塔,證明本次查找需要查找上次找過的類的父類
        SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
        if (findState.clazz == superclassInfo.getSubscriberClass()) { // 確定是所需查找的類
            return superclassInfo;
        }
    }
    if (subscriberInfoIndexes != null) { // 從我們傳進(jìn)來的subscriberInfoIndexes中獲取相應(yīng)的訂閱者信息
        for (SubscriberInfoIndex index : subscriberInfoIndexes) {
            SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
            if (info != null) { return info; }
        }
    }
    return null;
}

在getSubscriberInfo方法中潭辈,傳入的實(shí)參如果已有實(shí)例,證明本次查找需要查找上次找過的類的父類澈吨。如果沒有實(shí)例把敢,應(yīng)用到了我們生成的索引,避免我們需要在findSubscriberMethods時(shí)去調(diào)用耗時(shí)的findUsingReflection方法谅辣。關(guān)于注解為我們生成的索引這里先不解答修赞,后面會(huì)有代碼解說(在我們開始查找訂閱方法的時(shí)候并沒有忽略注解器為我們生成的索引MyEventBusIndex,如果我們通過EventBusBuilder配置了MyEventBusIndex桑阶,便會(huì)獲取到subscriberInfo柏副,調(diào)用subscriberInfo的getSubscriberMethods方法便可以得到訂閱方法相關(guān)的信息,這個(gè)時(shí)候就不在需要通過注解進(jìn)行獲取訂閱方法联逻。如果沒有配置MyEventBusIndex搓扯,便會(huì)執(zhí)行findUsingReflectionInSingleClass方法,將訂閱方法保存到findState中包归。)

下面就來看一下findUsingReflectionInSingleClass的執(zhí)行過程:

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();
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 1) {
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    Class<?> eventType = parameterTypes[0];
                    if (findState.checkAdd(method, eventType)) {
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        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");
        }
    }
}

在這里主要是使用了Java的反射和對注解的解析锨推。首先通過反射來獲取訂閱者中所有的方法。并根據(jù)方法的類型公壤,參數(shù)和注解來找到訂閱方法换可。找到訂閱方法后將訂閱方法相關(guān)信息保存到FindState當(dāng)中。

當(dāng)獲取到了subscriberMethods數(shù)據(jù)集厦幅,接著開始對subscriberMethods數(shù)據(jù)集遍歷沾鳄,在訂閱方法里進(jìn)行注冊:

 private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    ////根據(jù)傳入的響應(yīng)方法名獲取到響應(yīng)事件(參數(shù)類型)
    Class<?> eventType = subscriberMethod.eventType;
      //根據(jù)訂閱者和訂閱方法構(gòu)造一個(gè)訂閱事件
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
      //獲取當(dāng)前訂閱事件中Subscription的List集合
        //通過響應(yīng)事件作為key,并取得這個(gè)事件類型將會(huì)響應(yīng)的全部訂閱者
        //沒個(gè)訂閱者至少會(huì)訂閱一個(gè)事件,多個(gè)訂閱者可能訂閱同一個(gè)事件(多對多)
        //key:訂閱的事件,value:訂閱這個(gè)事件的所有訂閱者集合
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
     //該事件對應(yīng)的Subscription的List集合不存在,則重新創(chuàng)建并保存在subscriptionsByEventType中
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
    //訂閱者已經(jīng)注冊則拋出EventBusException
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }
    //遍歷訂閱事件确憨,找到比subscriptions中訂閱事件小的位置译荞,優(yōu)先級插入到訂閱者集合中
    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;
        }
    }
     //通過訂閱者獲取該訂閱者所訂閱事件的集合(當(dāng)前訂閱者訂閱了哪些事件)
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
      //將當(dāng)前的訂閱事件添加到subscribedEvents中
    subscribedEvents.add(eventType);
    if (subscriberMethod.sticky) {
        if (eventInheritance) {
            // Existing sticky events of all subclasses of eventType have to be considered.
            // Note: Iterating over all events may be inefficient with lots of sticky events,
            // thus data structure should be changed to allow a more efficient lookup
            // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
            //粘性事件的處理
            Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry<Class<?>, Object> entry : entries) {
                Class<?> candidateEventType = entry.getKey();
                //如果eventtype是candidateEventType同一個(gè)類或是其子類
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    //調(diào)用checkPostStickyEventToSubscription()做一次安全判斷,就調(diào)用
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

checkPostStickyEventToSubscription()#postToSubscription(...)

private void postToSubscription(...) {
    switch (threadMode) {
        case PostThread:
            //直接調(diào)用響應(yīng)方法
            invokeSubscriber(subscription, event);
            break;
        case MainThread:
            //如果是主線程則直接調(diào)用響應(yīng)事件,否則使用handle去在主線程響應(yīng)事件
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
            //休弃。吞歼。。
    }
}

訂閱的代碼主要就做了兩件事塔猾,第一件事是將我們的訂閱方法和訂閱者封裝到subscriptionsByEventType和typesBySubscriber中篙骡,subscriptionsByEventType是我們投遞訂閱事件的時(shí)候,就是根據(jù)我們的EventType找到我們的訂閱事件,從而去分發(fā)事件糯俗,處理事件的尿褪;typesBySubscriber在調(diào)用unregister(this)的時(shí)候,根據(jù)訂閱者找到EventType得湘,又根據(jù)EventType找到訂閱事件杖玲,從而對訂閱者進(jìn)行解綁。第二件事忽刽,如果是粘性事件的話天揖,就立馬投遞、執(zhí)行跪帝。

我們就知道了 EventBus 中那幾個(gè)map的全部含義。同時(shí)也回答了上面的第一個(gè)問題:為什么如果EventBus.defaultInstance不為null以后程序要拋出異常些阅,就是因?yàn)檫@幾個(gè)Map集合不同了。Map變了以后,訂閱的事件就全部變?yōu)榱硪粋€(gè)EventBus對象的了完慧,就沒辦法響應(yīng)之前那個(gè)EventBus對象的訂閱方法了互墓。

如果不是sticky粘性事件都直接不執(zhí)行了,還怎么響應(yīng)缤谎。如果是sticky粘性事件抒倚,最后是調(diào)用checkPostStickyEventToSubscription()做一次安全判斷,就調(diào)用postToSubscription()發(fā)送事件了坷澡。這里就關(guān)聯(lián)到了我們之前講的Poster類的作用了托呕。也回答了上面的第二個(gè)問題:三個(gè)Poster類的作用,Poster類就是只負(fù)責(zé)粘滯事件的代碼频敛。同時(shí)也回答了上面的第三個(gè)問題:invokeSubscriber(项郊。。斟赚。)什么時(shí)候觸發(fā)的着降。之前我們不知道subscriberMethod是什么,現(xiàn)在我們能看懂了拗军,就是通過反射調(diào)用訂閱者類subscriber的訂閱方法onEventXXX()任洞,并將event作為參數(shù)傳遞進(jìn)去。源碼如下:

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);
    }
}

前面提到注解器為我們生成的索引MyEventBusIndex发侵。索引是在初始化EventBus時(shí)通過EventBusBuilder.addIndex(SubscriberInfoIndex index)方法傳進(jìn)來的交掏。下面就說說添加事件總線注釋預(yù)處理器生成的索引。

//源碼
/** Adds an index generated by EventBus' annotation preprocessor. */
public EventBusBuilder addIndex(SubscriberInfoIndex index) {
    if(subscriberInfoIndexes == null) {
        subscriberInfoIndexes = new ArrayList<>();
    }
    subscriberInfoIndexes.add(index);
    return this;
}

可以看到器紧,傳進(jìn)來的索引信息會(huì)保存在subscriberInfoIndexes這個(gè)List中耀销,后續(xù)會(huì)通過EventBusBuilder傳到相應(yīng)EventBus的SubscriberMethodFinder實(shí)例中。我們先來分析SubscriberInfoIndex這個(gè)參數(shù):

public interface SubscriberInfoIndex {
    SubscriberInfo getSubscriberInfo(Class<?> subscriberClass);
}

可見索引只需要做一件事情——就是能拿到訂閱者的信息。而實(shí)現(xiàn)這個(gè)接口的類如果我們沒有編譯過熊尉,是找不到的罐柳。

EventBus3.X新特性Subscriber Index,在默認(rèn)屬性中有一個(gè)屬性為ignoreGeneratedIndex狰住,在后面的源碼
分析中會(huì)介紹到這個(gè)屬性為true時(shí)會(huì)使用反射方法獲取訂閱者的事件處理函數(shù)张吉,為false時(shí)會(huì)使用subscriber Index生成的SubscriberInfo來獲取訂閱者的事件處理函數(shù),具體內(nèi)容會(huì)在源碼分析中介紹
首先催植,Subscriber Index會(huì)在編譯期間生成SubscriberInfo肮蛹,然后在運(yùn)行時(shí)使用SubscriberInfo中保存的事件處理函數(shù)處理事件,減少了反射時(shí)需要是耗時(shí)创南,會(huì)有運(yùn)行速度上的提升伦忠,但是用起來會(huì)比較麻煩。

注意事項(xiàng):只有用注解@Subscriber描述的public方法才能被索引稿辙,并且由于Java的特性匿名內(nèi)部類就算用@Subscriber描述也不能被索引昆码,不過別擔(dān)心,當(dāng)EventBus不能使用索引時(shí)會(huì)自動(dòng)在運(yùn)行時(shí)使用反射方法獲取事件處理函數(shù)邻储。

要添加事件處理函數(shù)到索引需要借助EventBus的注解處理器赋咽,添加EventBus的注解處理器到Module的Gradle文件中:

buildscript {
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
    compile 'org.greenrobot:eventbus:3.0.0'
    apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
apt {
    arguments {
        eventBusIndex "包名.MyEventBusIndex"http://生成索引的名稱
    }
}

在導(dǎo)入這些庫沒有問題后,Gradle之后build項(xiàng)目吨娜。這個(gè)時(shí)候就可以初始化EventBus脓匿,上面講過,自定義EventBus的兩種方式宦赠。

我們可以自定義設(shè)置自己的EventBus來為其添加MyEventBusIndex對象陪毡。代碼如下所示:

EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();

我們也能夠?qū)yEventBusIndex對象安裝在默認(rèn)的EventBus對象當(dāng)中。代碼如下所示:

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();

如果有多個(gè)索引文件還可以使用下面的代碼:

EventBus eventBus = EventBus.builder()
    .addIndex(new MyEventBusAppIndex())
    .addIndex(new MyEventBusLibIndex()).build();

順便說一句袱瓮,MyEventBusIndex的路徑為:項(xiàng)目根目錄\build\generated\source\apt\debug\包名\MyEventBusIndex.java

EventBus 事件的發(fā)送

一缤骨、post方式

在獲取到EventBus對象以后,可以通過post方法將給定事件發(fā)布到事件總線尺借。

public void post(Object event) {
     //PostingThreadState保存著事件隊(duì)列和線程狀態(tài)信息
      PostingThreadState postingState = currentPostingThreadState.get();
     //獲取事件隊(duì)列绊起,并將當(dāng)前事插入到事件隊(duì)列中
      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 {
            //處理隊(duì)列中的所有事件
              while (!eventQueue.isEmpty()) {
                  postSingleEvent(eventQueue.remove(0), postingState);
              }
          } finally {
              postingState.isPosting = false;
              postingState.isMainThread = false;
          }
      }
  }

我們知道,對應(yīng)的值燎斩,是通過post去完成的虱歪。這里用ThreadLocal的線程包裝類,去完成對應(yīng)的線程記錄和操作
對應(yīng)的泛型PostingThreadState對象栅表,記錄對應(yīng)的信息笋鄙,將對應(yīng)的線程放入一個(gè)List中,通過對應(yīng)的狀態(tài)怪瓶,判斷Looper.getMainLooper() == Looper.myLooper() 是否是主線程等操作萧落, 將狀態(tài)存入PostingThreadState的屬性中,遍歷容器中的Event,最后通過存儲(chǔ)的Subscription中的subscriberMethod的method找岖,反射去invoke調(diào)用方法陨倡。

先從PostingThreadState對象中取出事件隊(duì)列,然后再將當(dāng)前的事件插入到事件隊(duì)列當(dāng)中许布。如果postingState.isPosting為true說明正在發(fā)布兴革,則不做處理。如果不是正在發(fā)布蜜唾,則最后將隊(duì)列中的事件依次交由postSingleEvent方法進(jìn)行處理杂曲,并移除該事件。這里也涉及到了Handler袁余、looper擎勘、ThreadLocal就不進(jìn)行解答了。

進(jìn)入postSingleEvent方法中了解其操作過程:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
       Class<?> eventClass = event.getClass();
       boolean subscriptionFound = false;
       //eventInheritance表示是否向上查找事件的父類,默認(rèn)為true
       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);
       }
         //找不到該事件時(shí)的異常處理
       if (!subscriptionFound) {
           if (logNoSubscriberMessages) {
               Log.d(TAG, "No subscribers registered for event " + eventClass);
           }
           if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                   eventClass != SubscriberExceptionEvent.class) {
               post(new NoSubscriberEvent(this, event));
           }
       }
   }

eventInheritance表示是否向上查找事件的父類,它的默認(rèn)值為true泌霍,可以通過在EventBusBuilder中來進(jìn)行配置货抄。當(dāng)eventInheritance為true時(shí),則通過lookupAllEventTypes找到所有的父類事件并存在List中朱转,然后通過postSingleEventForEventType方法對事件逐一處理。

進(jìn)入postSingleEventForEventType方法看看是怎么進(jìn)一步對事件處理:

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
      CopyOnWriteArrayList<Subscription> subscriptions;
         //取出該事件對應(yīng)的Subscription集合
      synchronized (this) {
          subscriptions = subscriptionsByEventType.get(eventClass);
      }
      if (subscriptions != null && !subscriptions.isEmpty()) {
      //將該事件的event和對應(yīng)的Subscription中的信息(包擴(kuò)訂閱者類和訂閱方法)傳遞給postingState
          for (Subscription subscription : subscriptions) {
              postingState.event = event;
              postingState.subscription = subscription;
              boolean aborted = false;
              try {
                 //對事件進(jìn)行處理
                  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;
  }

同步取出該事件對應(yīng)的Subscription集合并遍歷該集合將事件event和對應(yīng)Subscription傳遞給postingState并調(diào)用postToSubscription方法對事件進(jìn)行處理积暖。

進(jìn)一步進(jìn)入postToSubscription方法藤为,了解內(nèi)部是怎么處理的:

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING:
            //直接調(diào)用響應(yīng)方式
            invokeSubscriber(subscription, event);
            break;
        case MAIN:
            //如果是主線程則直接調(diào)用響應(yīng)事件,否則使用handle去在主線程響應(yīng)事件
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case BACKGROUND:
            if (isMainThread) {
                backgroundPoster.enqueue(subscription, event);
            } else {
                invokeSubscriber(subscription, event);
            }
            break;
        case ASYNC:
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
    }
}

首先獲取threadMode,即訂閱方法運(yùn)行的線程模式夺刑,如果是POSTING缅疟,那么直接調(diào)用invokeSubscriber()方法即可,如果是MAIN遍愿,則要判斷當(dāng)前線程是否是MAIN線程存淫,如果是也是直接調(diào)用invokeSubscriber()方法,否則會(huì)交給mainThreadPoster來處理沼填,其他情況相類似桅咆。舉個(gè)例子,如果線程模式是MAIN坞笙,提交事件的線程是主線程的話則通過反射岩饼,直接運(yùn)行訂閱的方法,如果不是主線程薛夜,我們需要mainThreadPoster將我們的訂閱事件入隊(duì)列籍茧,mainThreadPoster是HandlerPoster類型的繼承自Handler,通過Handler將訂閱方法切換到主線程執(zhí)行梯澜。

二寞冯、postSticky方式

粘性事件發(fā)布

/**
 * Posts the given event to the event bus and holds on to the event (because it is sticky). The most recent sticky
 * event of an event's type is kept in memory for future access by subscribers using {@link Subscribe#sticky()}.
 */
public void postSticky(Object event) {
    synchronized (stickyEvents) {
        stickyEvents.put(event.getClass(), event);
    }
    // Should be posted after it is putted, in case the subscriber wants to remove immediately
    post(event);
}

將給定事件發(fā)布到事件總線并保存到事件(因?yàn)樗钦承缘模W罱恼承允录愋偷氖录槐4嬖趦?nèi)存中以供訂閱者使用。用stickyEvents來存放粘性事件吮龄,并且從這個(gè)put我們可以知道stickyEvents的key是event的Class對象俭茧,value是event,然后進(jìn)行Post流程。我們可以去驗(yàn)證一下螟蝙,在一個(gè)對象如Activity中先調(diào)用EventBus.getDefault().postSticky(new MessageEvent(“Hello everyone!”));恢恼,再調(diào)用EventBus.register,結(jié)果如何呢胰默?從結(jié)果可以預(yù)想在register(更準(zhǔn)確的說應(yīng)該是subscribe)中肯定會(huì)有post的調(diào)用场斑。帶著這個(gè)設(shè)想,我們繼續(xù)看subcribe方法牵署,里面就有粘性事件的判斷漏隐。

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
     
    。奴迅。青责。。

      //將當(dāng)前的訂閱事件添加到subscribedEvents中
    subscribedEvents.add(eventType);
    if (subscriberMethod.sticky) {
        if (eventInheritance) {
            // Existing sticky events of all subclasses of eventType have to be considered.
            // Note: Iterating over all events may be inefficient with lots of sticky events,
            // thus data structure should be changed to allow a more efficient lookup
            // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
            //粘性事件的處理
            Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry<Class<?>, Object> entry : entries) {
                Class<?> candidateEventType = entry.getKey();
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

在代碼端取具,接收事件時(shí)這樣脖隶。

@Subscribe(threadMode = ThreadMode.MAIN)
public void onJump(MsgEvent msg){
    
}

//粘性事件
@Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
public void onJump(MsgEvent msg){
    
}   

注解@Subscribe

ThreadMode 是enum(枚舉)類型暇检,threadMode默認(rèn)值是POSTING产阱。ThreadMode有四種模式:

  1. POSTING :Subscriber會(huì)在post event的所在線程回調(diào),故它不需要切換線程來分發(fā)事件块仆,因此開銷最小构蹬。它要求task完成的要快,不能請求MainThread悔据,適用簡單的task庄敛。

  2. MAIN :Subscriber會(huì)在主線程(有時(shí)候也被叫做UI線程)回調(diào),如果post event所在線程是MainThread,則可直接回調(diào)科汗。注意不能阻塞主線程藻烤。

  3. BACKGROUND :Subscriber會(huì)在后臺線程中回調(diào)。如果post event所在線程不是MainThread肛捍,那么可直接回調(diào)隐绵;如果是MainThread,EventBus會(huì)用單例background thread來有序地分發(fā)事件。注意不能阻塞background thread拙毫。

  4. ASYNC:當(dāng)處理事件的Method是耗時(shí)的依许,需要使用此模式。盡量避免同時(shí)觸發(fā)大量的耗時(shí)較長的異步操作缀蹄,EventBus使用線程池高效的復(fù)用已經(jīng)完成異步操作的線程峭跳。

再回到@Subscribe,還有兩個(gè)參數(shù)膘婶,sticky(粘性)默認(rèn)值是false,如果是true,那么可以通過EventBus的postSticky方法分發(fā)最近的粘性事件給該訂閱者(前提是該事件可獲得)蛀醉。

EventBus 取消注冊

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 {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}


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--;
                }
            }
        }
    }

unsubscribeByEventType方法中獲取相應(yīng)事件中的相應(yīng)列表刪除掉該訂閱者悬襟。typesBySubscriber我們在訂閱者注冊的過程中講到過這個(gè)屬性,他根據(jù)訂閱者找到EventType拯刁,然后根據(jù)EventType和訂閱者來得到訂閱事件來對訂閱者進(jìn)行解綁脊岳。

對EventBus小結(jié)

弄清楚上面的代碼之后,我們一起來梳理一下設(shè)計(jì)者是如何完成觀察者模式中的訂閱和發(fā)送事件以及取消訂閱垛玻。

  • 我們先看subscriber割捅。看到注冊方法register(Object subscriber)帚桩,傳入的就是一個(gè)Objec對象亿驾。一個(gè)subscriber可以訂閱多個(gè)類型的Event,而且對于同一種類型的Event可以有多個(gè)method進(jìn)行處理(盡管我們一般不會(huì)這樣做)账嚎。

  • 在subscribe方法中莫瞬,引入Subscription對subscriber、event進(jìn)行了封裝郭蕉。經(jīng)過判斷之后疼邀,把“合格”的subscription加入subscriptionsByEventType中。

  • 當(dāng)我們分發(fā)事件時(shí)召锈,也就是post(Object event)時(shí)檩小,他會(huì)間接調(diào)用postSingleEventForEventType這個(gè)方法,通過傳入的參數(shù)event烟勋,在subscriptionsByEventType找到event對應(yīng)的value,再繼續(xù)相應(yīng)的操作筐付。

  • 我們?nèi)∠粋€(gè)subscriber的訂閱是卵惦,也就是unregister(Object subscriber).我們會(huì)在typesBySubscriber中找到該subsriber對應(yīng)的evnt。然后再由此event去subscriptionsByEventType找到一系列的subscription瓦戚,并把他們r(jià)emove沮尿。

  • EventBus訂閱了,post發(fā)布了事件较解,就會(huì)觸發(fā)invokeSubscriber()方法畜疾,訂閱者類subscriber的訂閱方法onEventXXX(),并將event作為參數(shù)傳遞進(jìn)去印衔。即調(diào)用subscription.subscriberMethod.method.invoke(subscription.subscriber, event)方法去響應(yīng)啡捶。那么訂閱者類就會(huì)收到信息

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市奸焙,隨后出現(xiàn)的幾起案子瞎暑,更是在濱河造成了極大的恐慌彤敛,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件了赌,死亡現(xiàn)場離奇詭異墨榄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)勿她,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門袄秩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人逢并,你說我怎么就攤上這事之剧。” “怎么了筒狠?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵猪狈,是天一觀的道長。 經(jīng)常有香客問我辩恼,道長雇庙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任灶伊,我火速辦了婚禮疆前,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘聘萨。我一直安慰自己竹椒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布米辐。 她就那樣靜靜地躺著胸完,像睡著了一般。 火紅的嫁衣襯著肌膚如雪翘贮。 梳的紋絲不亂的頭發(fā)上赊窥,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音狸页,去河邊找鬼锨能。 笑死,一個(gè)胖子當(dāng)著我的面吹牛芍耘,可吹牛的內(nèi)容都是我干的址遇。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼斋竞,長吁一口氣:“原來是場噩夢啊……” “哼倔约!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起窃页,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤跺株,失蹤者是張志新(化名)和其女友劉穎复濒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乒省,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡巧颈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了袖扛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砸泛。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蛆封,靈堂內(nèi)的尸體忽然破棺而出唇礁,到底是詐尸還是另有隱情,我是刑警寧澤惨篱,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布盏筐,位于F島的核電站,受9級特大地震影響砸讳,放射性物質(zhì)發(fā)生泄漏琢融。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一簿寂、第九天 我趴在偏房一處隱蔽的房頂上張望漾抬。 院中可真熱鬧,春花似錦常遂、人聲如沸纳令。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽平绩。三九已至,卻和暖如春漠另,著一層夾襖步出監(jiān)牢的瞬間馒过,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工酗钞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人来累。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓砚作,卻偏偏與公主長得像,于是被迫代替她去往敵國和親嘹锁。 傳聞我的和親對象是個(gè)殘疾皇子葫录,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容