EventBus源碼分析

EventBus是在Android中使用到的發(fā)布-訂閱事件總線框架,基于觀察者模式羽德,將事件的發(fā)送者和接收者解耦几莽,簡化了組件之間的通信。

源碼clone地址

https://github.com/greenrobot/EventBus.git

我們從以下幾個方面著手分析EventBus源碼流程:

  • 角色解釋
  • 構造函數(shù)
  • Subscribe注解
  • 注冊事件訂閱方法
  • 取消注冊
  • 發(fā)送事件
  • 粘性事件
角色解釋

image.png

Event:事件宅静,又叫消息章蚣,其實就是一個對象,可以是網(wǎng)絡請求返回的字符串,也可以是某個開關狀態(tài)等等纤垂。事件類型EventType是指事件所屬的Class矾策。
事件分為一般事件和Sticky事件,相對于一般事件峭沦,Sticky事件不同之處在于贾虽,當事件發(fā)布后,再有訂閱者開始訂閱該類型事件吼鱼,依然能收到該類型事件的最近一個Sticky事件蓬豁。

Subscriber:事件訂閱者。在EventBus 3.0以后菇肃,事件處理的方法可以隨便取名地粪,但是需要添加一個注解@Subscribe,并且要指定線程模式(默認為POSTING)

Publisher:事件發(fā)布者琐谤◇〖迹可以在任意線程任意位置,通過post(MessageEvent)方法發(fā)送事件斗忌≈世瘢可以自己實例化EventBus對象,但一般使用EventBus.getDefault()就可以飞蹂。

構造函數(shù)

我們看看new EventBus在做什么

public EventBus() {
        this(DEFAULT_BUILDER);
    }

這里DEFAULT_BUILDER是默認的EventBusBuilder,用來構造EventBus

private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

this調(diào)用了EventBus另一個構造函數(shù)完成它相關屬性的初始化

EventBus(EventBusBuilder builder) {
        logger = builder.getLogger();
        // subscriptionsByEventType是一個HashMap翻屈,保存了以eventType為key,Subscription對象集合為value的鍵值對
        subscriptionsByEventType = new HashMap<>();
        // typesBySubscribere也是一個HashMap陈哑,保存了以當前要注冊類的對象為key,注冊類中訂閱事件的方法的參數(shù)類型的集合為value的鍵值對
        typesBySubscriber = new HashMap<>();
        // stickyEvents就是發(fā)送粘性事件時伸眶,保存了事件類型和對應事件的集合
        stickyEvents = new ConcurrentHashMap<>();
        // 當前線程為主線程
        mainThreadSupport = builder.getMainThreadSupport();
        // 主線程
        mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
        // 后臺線程
        backgroundPoster = new BackgroundPoster(this);
        // 異步線程
        asyncPoster = new AsyncPoster(this);
        // 如支持注解生成器惊窖,引入索引,則記錄的是索引數(shù)
        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;
}

所以我們也可以通過如下方式進行事件注冊

EventBus.builder().eventInheritance(true).build().register(this);

通常我們使用更簡便的方式進行注冊

EventBus.getDefault().register(this);

看一下getDefault()方法

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

可以看出getDefault()是一個單例方法厘贼,而且使用了DCL雙重檢查模式

DCL優(yōu)點是資源利用率高界酒,第一次執(zhí)行getInstance時單例對象才被實例化,效率高嘴秸。缺點是第一次加載時反應稍慢一些毁欣,在高并發(fā)環(huán)境下也有一定的缺陷,雖然發(fā)生的概率很小岳掐。DCL雖然在一定程度解決了資源的消耗和多余的同步凭疮,線程安全等問題,但是他還是在某些情況會出現(xiàn)失效的問題串述,也就是DCL失效执解,在《java并發(fā)編程實踐》一書建議用靜態(tài)內(nèi)部類單例模式來替代DCL。

附:靜態(tài)內(nèi)部類單例模式

public class EventBus {
    private EventBus(){
    }
    public static EventBus getInstance(){
        return EventBusHolder.sInstance;
    }
    private static class EventBusHolder {
        private static final EventBus sInstance = new EventBus();
    }
}

第一次加載EventBus類時并不會初始化sInstance纲酗,只有第一次調(diào)用getInstance方法時虛擬機加載EventBusHolder 并初始化sInstance 衰腌,這樣不僅能確保線程安全也能保證EventBus類的唯一性新蟆,所以推薦使用靜態(tài)內(nèi)部類單例模式。

Subscribe注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    // 指定事件訂閱方法的線程模式右蕊,即在那個線程執(zhí)行事件訂閱方法處理事件琼稻,默認為POSTING
    ThreadMode threadMode() default ThreadMode.POSTING;
    // 是否支持粘性事件,默認為false
    boolean sticky() default false;
    // 指定事件訂閱方法的優(yōu)先級尤泽,默認為0欣簇,如果多個事件訂閱方法可以接收相同事件的,則優(yōu)先級高的先接收到事件
    int priority() default 0;
}

@Retention按生命周期來劃分可分為3類

  • RetentionPolicy.SOURCE:注解只保留在源文件坯约,當Java文件編譯成class文件的時候熊咽,注解被遺棄;
  • RetentionPolicy.CLASS:注解被保留到class文件闹丐,但jvm加載class文件時候被遺棄横殴,這是默認的生命周期;
  • RetentionPolicy.RUNTIME:注解不僅被保存到class文件中卿拴,jvm加載class文件之后衫仑,仍然存在;
    首先要明確生命周期長度 SOURCE < CLASS < RUNTIME 堕花,所以前者能作用的地方后者一定也能作用文狱。一般如果需要在運行時去動態(tài)獲取注解信息,那只能用 RUNTIME 注解缘挽;如果要在編譯時進行一些預處理操作瞄崇,比如生成一些輔助代碼(如 ButterKnife),就用 CLASS注解壕曼;如果只是做一些檢查性的操作苏研,比如 @Override 和 @SuppressWarnings,則可選用 SOURCE 注解

@Target說明了Annotation所修飾的對象范圍腮郊,具體包括:

  • CONSTRUCTOR:用于描述構造器
  • FIELD:用于描述域
  • LOCAL_VARIABLE:用于描述局部變量
  • METHOD:用于描述方法
  • PACKAGE:用于描述包
  • PARAMETER:用于描述參數(shù)
  • TYPE:用于描述類摹蘑、接口(包括注解類型) 或enum聲明

在使用Subscribe注解時可以根據(jù)需求指定threadMode、sticky轧飞、priority三個屬性衅鹿。
其中threadMode屬性有如下幾個可選值:

POSTING
如果使用事件處理函數(shù)指定了線程模型為POSTING,那么該事件在哪個線程發(fā)布出來的过咬,事件處理函數(shù)就會在這個線程中運行塘安,也就是說發(fā)布事件和接收事件在同一個線程。在線程模型為POSTING的事件處理函數(shù)中盡量避免執(zhí)行耗時操作援奢,因為它會阻塞事件的傳遞兼犯,甚至有可能會引起ANR。

MAIN
如在主線程(UI線程)發(fā)送事件,則直接在主線程處理事件切黔;
如果在子線程發(fā)送事件砸脊,則先將事件入隊列,然后通過 Handler 切換到主線程纬霞,依次處理事件

MAIN_ORDERED
無論在哪個線程發(fā)送事件凌埂,都先將事件入隊列,然后通過 Handler 切換到主線程诗芜,依次處理事件

BACKGROUND
如果在主線程發(fā)送事件瞳抓,則先將事件入隊列,然后通過線程池依次處理事件伏恐;
如果在子線程發(fā)送事件孩哑,則直接在發(fā)送事件的線程處理事件;
在此事件處理函數(shù)中禁止進行UI更新操作

ASYNC
無論事件在哪個線程發(fā)布翠桦,該事件處理函數(shù)都會在新建的子線程中執(zhí)行(將事件入隊列掠手,然后通過線程池處理)探橱,同樣半哟,此事件處理函數(shù)中禁止進行UI更新操作

注冊事件訂閱方法

注冊事件方法如下:

EventBus.getDefault().register(this);

EventBus.java類中的register方法:

public void register(Object subscriber) {
        // 得到當前要注冊類的Class對象
        Class<?> subscriberClass = subscriber.getClass();
        // 根據(jù)Class查找當前類中訂閱了事件的方法集合渺绒,即使用了Subscribe注解、有public修飾符斗幼、一個參數(shù)的方法
        // SubscriberMethod類主要封裝了符合條件方法的相關信息:
        // Method對象澎蛛、線程模式、事件類型蜕窿、優(yōu)先級谋逻、是否是粘性事等
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            // 循環(huán)遍歷訂閱了事件的方法集合,以完成注冊
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

可以發(fā)現(xiàn)渠羞,register方法主要做了兩件事情:

  • 查找當前類中訂閱了事件的方法集合
  • 循環(huán)遍歷方法集合斤贰,完成注冊

看下findSubscriberMethods方法做了什么

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        // METHOD_CACHE是一個ConcurrentHashMap智哀,直接保存了subscriberClass和對應SubscriberMethod的集合次询,以提高注冊效率,賦值重復查找瓷叫。
        // 從緩存中獲取SubscriberMethod集合
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
        // 由于使用了默認的EventBusBuilder屯吊,則ignoreGeneratedIndex屬性默認為false,即是否忽略注解生成器
        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        //在獲得subscriberMethods以后摹菠,如果訂閱者中不存在@Subscribe注解并且為public的訂閱方法盒卸,則會拋出異常。
        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;
        }
    }

首先從緩存中查找次氨,如果找到了就立馬返回蔽介。如果緩存中沒有的話,則根據(jù) ignoreGeneratedIndex 選擇如何查找訂閱方法,ignoreGeneratedIndex表示是否忽略注解器生成MyEventBusIndex虹蓄。
最后犀呼,找到訂閱方法后,放入緩存薇组,以免下次繼續(xù)查找外臂。
ignoreGeneratedIndex 默認就是false,可以通過EventBusBuilder來設置它的值律胀。
在項目中經(jīng)常通過EventBus單例模式來獲取默認的EventBus對象宋光,也就是ignoreGeneratedIndex為false的情況,這種情況調(diào)用了findUsingInfo方法炭菌。

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        // 初始狀態(tài)下findState.clazz就是subscriberClass
        while (findState.clazz != null) {
            // getSubscriberInfo方法獲取訂閱者信息罪佳,主要執(zhí)行兩個判斷
            // 當前類沒有繼承父類的訂閱方法,則去判斷是否配置了索引娃兽,如沒有則null
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    // checkAdd()方法用來判斷FindState的anyMethodByEventType map是否已經(jīng)添加過以當前eventType為key的鍵值對菇民,沒添加過則返回true
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                //通過反射來查找訂閱方法
                findUsingReflectionInSingleClass(findState);
            }
            // 修改findState.clazz為subscriberClass的父類Class,即向上遍歷父類
            findState.moveToSuperclass();
        }
        // 查找到的方法保存在了FindState實例的subscriberMethods集合中投储。
        // 使用subscriberMethods構建一個新的List<SubscriberMethod>
        // 釋放掉findState
        return getMethodsAndRelease(findState);
    }

通過getSubscriberInfo方法來獲取訂閱者信息第练。
如果當前類集成了父類的訂閱方法,則返回當前類的訂閱方法玛荞。否則往下繼續(xù)走娇掏。
在我們開始查找訂閱方法的時候并沒有忽略注解器為我們生成的索引MyEventBusIndex。
如果我們通過EventBusBuilder配置了MyEventBusIndex勋眯,便會獲取到subscriberInfo婴梧,調(diào)用subscriberInfo的getSubscriberMethods方法便可以得到訂閱方法相關的信息,這個時候就不在需要通過注解進行獲取訂閱方法客蹋。
上面條件判斷都false的情況下塞蹭,getSubscriberInfo返回null,接下來便會執(zhí)行findUsingReflectionInSingleClass方法讶坯,將訂閱方法保存到findState中番电。
最后再通過getMethodsAndRelease方法對findState做回收處理并反回訂閱方法的List集合。
這里出現(xiàn)了一個FindState類辆琅,它是SubscriberMethodFinder的內(nèi)部類漱办,用來輔助查找訂閱事件的方法。

FindState
為什么要使用FindState呢婉烟?首先是面向對象封裝的采用娩井,那么看看它給我們提供了哪些方法?

// 用來初始化傳入訂閱類
void initForSubscriber(Class<?> subscriberClass) {
    ......
}
// 檢查方法信息,保證獲取的訂閱方法都是合法
boolean checkAdd(Method method, Class<?> eventType) {
    ......
}
// 檢查方法信息,保證獲取的訂閱方法都是合法
private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
    ......
}
// 查看父類中的訂閱方法
void moveToSuperclass() {
    ......
}

緩存的使用
使用java的似袁,應該要知道頻繁地創(chuàng)建對象洞辣,是非常消耗資源的咐刨,在jvm垃圾回收時候,會出現(xiàn)內(nèi)存抖動的問題扬霜。所以所宰,我們在這里,一定要注意緩存的使用畜挥。

上文中提到的中間器FindState仔粥,就采用了緩存:

private static final int POOL_SIZE = 4;
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];

指定了FindState的緩存大小為4,并使用一維的靜態(tài)數(shù)組蟹但,所以這里需要注意線程同步的問題:

private FindState prepareFindState() {
        synchronized (FIND_STATE_POOL) {
            for (int i = 0; i < POOL_SIZE; i++) {
                FindState state = FIND_STATE_POOL[i];
                if (state != null) {
                    FIND_STATE_POOL[i] = null;
                    return state;
                }
            }
        }
        return new FindState();
    }

這段是用來獲取FindState, 可以看到的是對這段緩存的獲取使用了synchronized關鍵字躯泰,來將緩存中FindState的獲取,變?yōu)橥綁K华糖。
而在subscriberMethod的獲取的同時麦向,則對FindState的緩存做了添加的操作,同樣是也必須是同步代碼塊:

private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
        List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
        findState.recycle();
        synchronized (FIND_STATE_POOL) {
            for (int i = 0; i < POOL_SIZE; i++) {
                if (FIND_STATE_POOL[i] == null) {
                    FIND_STATE_POOL[i] = findState;
                    break;
                }
            }
        }
        return subscriberMethods;
    }

另外客叉,EventBus也對subsciberMethod的獲取诵竭,也做了緩存的操作,這樣進行SubscriberMethod查找的時候兼搏,則優(yōu)先進行緩存的查找:

    private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();

這里卵慰,使用的是數(shù)據(jù)結構是ConcurrentHashMap,就可以不必寫大量的同步代碼塊了

HashMap,Hashtable與ConcurrentHashMap都是實現(xiàn)的哈希表數(shù)據(jù)結構佛呻,在隨機讀取的時候效率很高裳朋。
Hashtable實現(xiàn)同步是利用synchronized關鍵字進行鎖定的,其是針對整張哈希表進行鎖定的吓著,即每次鎖住整張表讓線程獨占鲤嫡,在線程安全的背后是巨大的浪費。
ConcurrentHashMap和Hashtable主要區(qū)別就是圍繞著鎖的粒度進行區(qū)別以及如何區(qū)鎖定绑莺。
ConcurrentHashMap的實現(xiàn)方式暖眼,單獨鎖住每一個桶(segment).ConcurrentHashMap將哈希表分為16個桶(默認值),諸如get(),put(),remove()等常用操作只鎖當前需要用到的桶,而size()才鎖定整張表纺裁。
原來只能一個線程進入诫肠,現(xiàn)在卻能同時接受16個寫線程并發(fā)進入。
所以对扶,才有了并發(fā)性的極大提升

具體的查找過程在findUsingReflectionInSingleClass()方法区赵,它主要通過反射查找訂閱事件的方法

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;
        }
        // 循環(huán)遍歷當前類的方法惭缰,篩選出符合條件的
        for (Method method : methods) {
            // 獲得方法的修飾符
            int modifiers = method.getModifiers();
            // 如果是public類型浪南,但非abstract、static等
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                // 獲得當前方法所有參數(shù)的類型
                Class<?>[] parameterTypes = method.getParameterTypes();
                // 如果當前方法只有一個參數(shù)
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    // 如果當前方法使用了Subscribe注解
                    if (subscribeAnnotation != null) {
                        // 得到該參數(shù)的類型
                        Class<?> eventType = parameterTypes[0];
                        // checkAdd()方法用來判斷FindState的anyMethodByEventType map是否已經(jīng)添加過以當前eventType為key的鍵值對漱受,沒添加過則返回true
                        if (findState.checkAdd(method, eventType)) {
                            // 得到Subscribe注解的threadMode屬性值络凿,即線程模式
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            // 創(chuàng)建一個SubscriberMethod對象骡送,并添加到subscriberMethods集合
                            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");
            }
        }
    }

至此,我們將register方法中的findSubscriberMethods流程分析完畢絮记,并獲取到了當前類中訂閱了事件的方法集合摔踱。
我們再看一下register方法內(nèi)容:

public void register(Object subscriber) {
        // 得到當前要注冊類的Class對象
        Class<?> subscriberClass = subscriber.getClass();
        // 根據(jù)Class查找當前類中訂閱了事件的方法集合,即使用了Subscribe注解怨愤、有public修飾符派敷、一個參數(shù)的方法
        // SubscriberMethod類主要封裝了符合條件方法的相關信息:
        // Method對象、線程模式撰洗、事件類型篮愉、優(yōu)先級、是否是粘性事等
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            // 循環(huán)遍歷訂閱了事件的方法集合差导,以完成注冊
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

訂閱者的訂閱過程執(zhí)行subscribe方法

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        // 得到當前訂閱了事件的方法的參數(shù)類型
        Class<?> eventType = subscriberMethod.eventType;
        // 根據(jù)訂閱者和訂閱方法構造一個訂閱事件
        // Subscription類保存了要注冊的類對象以及當前的subscriberMethod訂閱方法
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        // 獲取當前訂閱事件中Subscription的List集合
        // subscriptionsByEventType是一個HashMap试躏,保存了以eventType為key,Subscription對象集合為value的鍵值對
        // 先查找subscriptionsByEventType是否存在以當前eventType為key的值
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        // 如果不存在,則創(chuàng)建一個subscriptions设褐,并保存到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);
            }
        }

        int size = subscriptions.size();
        // 遍歷訂閱事件颠蕴,找到優(yōu)先級比subscriptions中訂閱事件小的位置,然后插進去
        for (int i = 0; i <= size; i++) {
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }

        // typesBySubscribere也是一個HashMap助析,保存了以當前要注冊類的對象為key犀被,注冊類中訂閱事件的方法的參數(shù)類型的集合為value的鍵值對
        // 通過訂閱者獲取該訂閱者所訂閱事件的集合
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            // 不存在則創(chuàng)建一個subscribedEvents,并保存到typesBySubscriber
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        // 將當前的訂閱事件添加到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);
            }
        }
    }

訂閱的代碼主要就做了兩件事
第一件事是將我們的訂閱方法和訂閱者封裝到subscriptionsByEventType和typesBySubscriber中外冀,subscriptionsByEventType是我們投遞訂閱事件的時候弱判,就是根據(jù)我們的EventType找到我們的訂閱事件,從而去分發(fā)事件锥惋,處理事件的昌腰;typesBySubscriber在調(diào)用unregister(this)的時候,根據(jù)訂閱者找到EventType膀跌,又根據(jù)EventType找到訂閱事件遭商,從而對訂閱者進行解綁。
第二件事捅伤,如果是粘性事件的話劫流,就立馬投遞、執(zhí)行丛忆。

取消注冊

EventBus取消注冊祠汇,在生命周期onDestroy方法中執(zhí)行

EventBus.getDefault().unregister(this);

看一下unregister方法

public synchronized void unregister(Object subscriber) {
        // 得到當前注冊類對象 對應的 訂閱事件方法的參數(shù)類型 的集合
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            // 遍歷參數(shù)類型集合,釋放之前緩存的當前類中的Subscription
            for (Class<?> eventType : subscribedTypes) {
                unsubscribeByEventType(subscriber, eventType);
            }
            // 刪除以subscriber為key的鍵值對
            typesBySubscriber.remove(subscriber);
        } else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

繼續(xù)看unsubscribeByEventType方法

private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
        // 得到當前參數(shù)類型對應的Subscription集合
        List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions != null) {
            int size = subscriptions.size();
            // 遍歷Subscription集合
            for (int i = 0; i < size; i++) {
                Subscription subscription = subscriptions.get(i);
                // 如果當前subscription對象對應的注冊類對象 和 要取消注冊的注冊類對象相同熄诡,則刪除當前subscription對象
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }

取消注冊流程很簡單可很,typesBySubscriber我們在訂閱者注冊的過程中講到過這個屬性,他根據(jù)訂閱者找到EventType凰浮,然后根據(jù)EventType和訂閱者來得到訂閱事件來對訂閱者進行解綁我抠。
所以在unregister()方法中苇本,釋放了typesBySubscriber、subscriptionsByEventType中緩存的資源

發(fā)送事件

當發(fā)送一個事件的時候菜拓,我們可以通過如下方式:

EventBus.getDefault().post("Hello World!")

可以看到瓣窄,發(fā)送事件就是通過post()方法完成的:

public void post(Object event) {
        // currentPostingThreadState是一個PostingThreadState類型的ThreadLocal
        // PostingThreadState保存著事件隊列和線程狀態(tài)信息
        PostingThreadState postingState = currentPostingThreadState.get();
        // 獲取事件隊列,并將當前要發(fā)送的事件插入到事件隊列中
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);
        // isPosting默認為false
        if (!postingState.isPosting) {
            // 是否為主線程
            postingState.isMainThread = isMainThread();
            // isPosting設置為true
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                // 遍歷事件隊列
                while (!eventQueue.isEmpty()) {
                    // 發(fā)送單個事件
                    // eventQueue.remove(0)纳鼎,從事件隊列移除事件
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

首先從PostingThreadState對象中取出事件隊列俺夕,然后再將當前的事件插入到事件隊列當中。最后將隊列中的事件依次交由postSingleEvent方法進行處理贱鄙,并移除該事件啥么。

來看看postSingleEvent方法里做了什么:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        // eventInheritance默認為true,表示是否向上查找事件的父類
        if (eventInheritance) {
            // 查找當前事件類型的Class贰逾,連同當前事件類型的Class保存到集合
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            // 遍歷Class集合悬荣,繼續(xù)處理事件
            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表示是否向上查找事件的父類,它的默認值為true,可以通過在EventBusBuilder中來進行配置疙剑。
當eventInheritance為true時氯迂,則通過lookupAllEventTypes找到所有的父類事件并存在List中,然后通過postSingleEventForEventType方法對事件逐一處理言缤。
如果eventInheritance為false嚼蚀,則直接交由postSingleEventForEventType方法對事件進行處理

看下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;
                // 記錄對應的subscription
                postingState.subscription = subscription;
                boolean aborted = false;
                try {
                    // 最終的事件處理
                    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;
    }

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

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        // 判斷訂閱事件方法的線程模式
        switch (subscription.subscriberMethod.threadMode) {
            // 默認的線程模式管挟,在哪個線程發(fā)送事件就在哪個線程處理事件
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            // 在主線程處理事件
            case MAIN:
                // 如果在主線程發(fā)送事件轿曙,則直接在主線程通過反射處理事件
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    // 如果是在子線程發(fā)送事件,則將事件入隊列僻孝,通過Handler切換到主線程執(zhí)行處理事件
                    // mainThreadPoster 不為空
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            // 無論在那個線程發(fā)送事件导帝,都先將事件入隊列,然后通過 Handler 切換到主線程穿铆,依次處理事件您单。
            // mainThreadPoster 不為空
            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:
                // 如果在主線程發(fā)送事件,則先將事件入隊列荞雏,然后通過線程池依次處理事件
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    // 如果在子線程發(fā)送事件虐秦,則直接在發(fā)送事件的線程通過反射處理事件
                    invokeSubscriber(subscription, event);
                }
                break;
            // 無論在那個線程發(fā)送事件,都將事件入隊列凤优,然后通過線程池處理悦陋。
            case ASYNC:
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

可以看到,postToSubscription()方法就是根據(jù)訂閱事件方法的線程模式筑辨、以及發(fā)送事件的線程來判斷如何處理事件俺驶。
處理方式主要有兩種:
第一種是在相應線程直接通過invokeSubscriber()方法,用反射來執(zhí)行訂閱事件的方法挖垛,這樣發(fā)送出去的事件就被訂閱者接收并做相應處理了痒钝。
第二種是先將事件入隊列(其實底層是一個List),然后做進一步處理痢毒,我們以mainThreadPoster.enqueue(subscription, event)為例簡單的分析下送矩,其中mainThreadPoster是HandlerPoster類的一個實例

public class HandlerPoster extends Handler implements Poster {
    private final PendingPostQueue queue;
    private boolean handlerActive;
    ......
    public void enqueue(Subscription subscription, Object event) {
        // 用subscription和event封裝一個PendingPost對象
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            // 入隊列
            queue.enqueue(pendingPost);
            if (!handlerActive) {
                handlerActive = true;
                // 發(fā)送開始處理事件的消息,handleMessage()方法將被執(zhí)行哪替,完成從子線程到主線程的切換
                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();
            // 死循環(huán)遍歷隊列
            while (true) {
                // 出隊列
                PendingPost pendingPost = queue.poll();
                ......
                // 進一步處理pendingPost
                eventBus.invokeSubscriber(pendingPost);
                ......
            }
        } finally {
            handlerActive = rescheduled;
        }
    }
}

所以HandlerPoster的enqueue()方法主要就是將subscription栋荸、event對象封裝成一個PendingPost對象,然后保存到隊列里凭舶,之后通過Handler切換到主線程晌块,在handleMessage()方法將中將PendingPost對象循環(huán)出隊列,交給invokeSubscriber()方法進一步處理

void invokeSubscriber(PendingPost pendingPost) {
        Object event = pendingPost.event;
        Subscription subscription = pendingPost.subscription;
        // 釋放pendingPost引用的資源
        PendingPost.releasePendingPost(pendingPost);
        if (subscription.active) {
            // 用反射來執(zhí)行訂閱事件的方法
            invokeSubscriber(subscription, event);
        }
    }

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

這個方法很簡單帅霜,主要就是從pendingPost中取出之前保存的event匆背、subscription,然后用反射來執(zhí)行訂閱事件的方法身冀,又回到了第一種處理方式钝尸。所以mainThreadPoster.enqueue(subscription, event)的核心就是先將將事件入隊列,然后通過Handler從子線程切換到主線程中去處理事件搂根。
backgroundPoster.enqueue()和asyncPoster.enqueue也類似珍促,內(nèi)部都是先將事件入隊列,然后再出隊列剩愧,但是會通過線程池去進一步處理事件

粘性事件

一般情況猪叙,我們使用 EventBus 都是準備好訂閱事件的方法,然后注冊事件仁卷,最后在發(fā)送事件穴翩,即要先有事件的接收者。但粘性事件卻恰恰相反锦积,我們可以先發(fā)送事件藏否,后續(xù)再準備訂閱事件的方法、注冊事件充包。
由于這種差異副签,我們分析粘性事件原理時,先從事件發(fā)送開始基矮,發(fā)送一個粘性事件通過如下方式:

EventBus.getDefault().postSticky("Hello World!");

來看postSticky()方法是如何實現(xiàn)的:

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

postSticky()方法主要做了兩件事淆储,先將事件類型和對應事件保存到stickyEvents中,方便后續(xù)使用家浇;然后執(zhí)行post(event)繼續(xù)發(fā)送事件本砰,這個post()方法就是之前發(fā)送的post()方法。所以钢悲,如果在發(fā)送粘性事件前点额,已經(jīng)有了對應類型事件的訂閱者舔株,及時它是非粘性的,依然可以接收到發(fā)送出的粘性事件还棱。
發(fā)送完粘性事件后载慈,再準備訂閱粘性事件的方法,并完成注冊珍手。核心的注冊事件流程還是我們之前的register()方法中的subscribe()方法办铡,前邊分析subscribe()方法時,有一段沒有分析的代碼琳要,就是用來處理粘性事件的寡具。

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        // 得到當前訂閱了事件的方法的參數(shù)類型
        Class<?> eventType = subscriberMethod.eventType;
        // 根據(jù)訂閱者和訂閱方法構造一個訂閱事件
        // Subscription類保存了要注冊的類對象以及當前的subscriberMethod訂閱方法
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);

        // 粘性事件處理
        // 如果當前訂閱事件的方法的Subscribe注解的sticky屬性為true,即該方法可接受粘性事件
        if (subscriberMethod.sticky) {
            // 默認為true稚补,表示是否向上查找事件的父類
            if (eventInheritance) {
                // stickyEvents就是發(fā)送粘性事件時童叠,保存了事件類型和對應事件
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    Class<?> candidateEventType = entry.getKey();
                    // 如果candidateEventType是eventType的子類或自身類
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        // 獲得對應的事件
                        Object stickyEvent = entry.getValue();
                        // 處理粘性事件
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                // 獲得對應的事件
                Object stickyEvent = stickyEvents.get(eventType);
                // 處理粘性事件
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }

可以看到,處理粘性事件就是在 EventBus 注冊時课幕,遍歷stickyEvents拯钻,如果當前要注冊的事件訂閱方法是粘性的,并且該方法接收的事件類型和stickyEvents中某個事件類型相同或者是其父類撰豺,則取出stickyEvents中對應事件類型的具體事件粪般,做進一步處理。

繼續(xù)看checkPostStickyEventToSubscription()處理方法

 private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if (stickyEvent != null) {
            // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
            // --> Strange corner case, which we don't take care of here.
            postToSubscription(newSubscription, stickyEvent, isMainThread());
        }
    }

最終還是通過postToSubscription()方法完成粘性事件的處理污桦,這就是粘性事件的整個處理流程

參考

EventBus 官方
EventBus 原理解析
EventBus3.0源碼解析
老司機教你 “飆” EventBus 3

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亩歹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子凡橱,更是在濱河造成了極大的恐慌小作,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稼钩,死亡現(xiàn)場離奇詭異顾稀,居然都是意外死亡,警方通過查閱死者的電腦和手機坝撑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門静秆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人巡李,你說我怎么就攤上這事抚笔。” “怎么了侨拦?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵殊橙,是天一觀的道長。 經(jīng)常有香客問我,道長膨蛮,這世上最難降的妖魔是什么叠纹? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮制肮,結果婚禮上冒窍,老公的妹妹穿的比我還像新娘款慨。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布屁使。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音堂淡,去河邊找鬼覆履。 笑死伟众,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的仑鸥。 我是一名探鬼主播疮胖,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼汹族,長吁一口氣:“原來是場噩夢啊……” “哼法绵!你這毒婦竟也來了酪碘?” 一聲冷哼從身側響起朋譬,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎枕屉,沒想到半個月后威恼,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡箫措,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年馏谨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片附迷。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡惧互,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出喇伯,到底是詐尸還是另有隱情喊儡,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布稻据,位于F島的核電站艾猜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捻悯。R本人自食惡果不足惜匆赃,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望今缚。 院中可真熱鬧算柳,春花似錦、人聲如沸姓言。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽何荚。三九已至囱淋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間餐塘,已是汗流浹背妥衣。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工称鳞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留冈止,地道東北人熙暴。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓掂器,卻偏偏與公主長得像国瓮,于是被迫代替她去往敵國和親乃摹。 傳聞我的和親對象是個殘疾皇子孵睬,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

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

  • EventBus源碼分析(一) EventBus官方介紹為一個為Android系統(tǒng)優(yōu)化的事件訂閱總線,它不僅可以很...
    蕉下孤客閱讀 4,033評論 4 42
  • 1.Event概述 1.1EventBus是什么蹈集? EventBus是一個使用發(fā)布-訂閱模式(觀察者模式)進行松耦...
    官先生Y閱讀 329評論 0 0
  • 前面對EventBus 的簡單實用寫了一篇廓潜,相信大家都會使用,如果使用的還不熟呻畸,或者不夠6伤为,可以花2分鐘瞄一眼:h...
    gogoingmonkey閱讀 318評論 0 0
  • EventBus EventBus是一個為Android和Java平臺設計的發(fā)布/訂閱事件總線 EventBus有...
    瓶子里的王國閱讀 829評論 1 6
  • 殘陽黃葉月如溝, 一縷清風掃枯葉位衩, 鳥雀翩翩歌悠揚熔萧, 霧里看花冷相隨, 難捱天涯相思苦贮缕, 愁云紛擾情愫漫感昼, 烈酒一...
    焦志歡閱讀 288評論 2 4