【Android】EventBus 3.0 源碼分析

概述

EventBus是Android中一個基於觀察者模式的事件發佈/訂閱框架,開發者可以通過極少的代碼去實現多個模塊之間的通信,主要功能是替代Intent,Handler,BroadCast 在 Fragment,Activity,Service,線程Thread之間傳遞消息。優點是開銷小,使用方便,可以很大程度上降低它們之間的耦合。 類似的庫還有Otto ,今天就帶大家一起研讀 EventBus 的源碼。

這是EventBus源碼中的介紹:

/**
 * EventBus is a central publish/subscribe event system for Android. Events are posted ({@link #post(Object)}) to the
 * bus, which delivers it to subscribers that have a matching handler method for the event type. To receive events,
 * subscribers must register themselves to the bus using {@link #register(Object)}. Once registered, subscribers
 * receive events until {@link #unregister(Object)} is called. Event handling methods must be annotated by
 * {@link Subscribe}, must be public, return nothing (void), and have exactly one parameter
 * (the event).
 *
 * @author Markus Junginger, greenrobot
 */

EventBus 是Android上的以發佈\訂閱事件爲核心的庫。事件 (event) 通過 post() 發送到總線,然後再分發到匹配事件類型的訂閱者 (subscribers) 。訂閱者只有在總線中註冊 (register) 了才能收到事件,註銷 (unrigister) 之後就收不到任何事件了。事件方法必須帶有 Subscribe 的註解,必須是 public ,沒有返回類型 void 並且只能有一個參數。

EventBus3 與之前的相比,其主要差別在於訂閱方法可以不再以onEvent 開頭了,改爲用註解

一、使用EventBus

在Gradle中添加依賴

compile 'org.greenrobot:eventbus:3.0.0'

1.1 初始化

EventBus默認有一個單例,可以通過getDefault()獲取,也可以通過EventBus.builder()構造自定義的EventBus,比如要應用我們生成好的索引時:

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

如果想把自定義的設置應用到EventBus默認的單例中,則可以用installDefaultEventBus()方法:

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();

1.2 定義事件

所有能被實例化爲Object的實例都可以作爲事件:

public class MessageEvent {
    public final String message;

    public MessageEvent(String message) {
        this.message = message;
    }
}

在最新版的eventbus 3中如果用到了索引加速,事件類的修飾符必須爲public,不然編譯時會報錯:Subscriber method must be public

1.3 監聽事件

訂閱者需要在總線上註冊和註銷自己。只有當訂閱者註冊了才能接收到事件。在Android中,通常與 Activity 和 Fragment 的生命週期綁定在一起。

之前2.x版本中有四種註冊方法,區分了普通註冊和粘性事件註冊,並且在註冊時可以選擇接收事件的優先級,這裏我們就不對2.x版本做過多的研究了。由於3.0版本將粘性事件以及訂閱事件的優先級換成了註解的實現方式,所以3.0版本中的註冊就變得簡單,只有一個register()方法即可。

//3.0版本的註冊
EventBus.getDefault().register(this);

//2.x版本的四種註冊方法
EventBus.getDefault().register(this);
EventBus.getDefault().register(this, 100);
EventBus.getDefault().registerSticky(this);
EventBus.getDefault().registerSticky(this, 100);

當我們不在需要接收事件的時候需要解除註冊unregister,2.x和3.0的解除註冊也是相同的。代碼如下:

//取消註冊
EventBus.getDefault().unregister(this);

接收到消息之後的處理方式,在2.x版本中,註冊這些消息的監聽需要區分是否監聽黏性(sticky)事件,監聽EventBus事件的模塊需要實現以onEvent開頭的方法。如今3.0改爲在方法上添加註解的形式:

//3.0版本
@Subscribe(threadMode = ThreadMode.POSTING, priority = 0, sticky = true)
public void handleEvent(DriverEvent event) {
    Log.d(TAG, event.info);
}

//2.x版本
public void onEvent(String str) {
}
public void onEventMainThread(String str) {
}
public void onEventBackgroundThread(String str) {
}

在2.x版本中只有通過onEvent開頭的方法會被註冊,而且響應事件方法觸發的線程通過onEventMainThreadonEventBackgroundThread這些方法名區分,而在3.0版本中,通過@Subscribe註解,來確定運行的線程threadMode,是否接受粘性事件sticky以及事件優先級priority,而且方法名不在需要onEvent開頭,所以又簡潔靈活了不少。

我們可以看到註解@Subscribe有三個參數,threadMode爲回調所在的線程,priority爲優先級,sticky爲是否接收黏性事件。調度單位從類細化到了方法,對方法的命名也沒有了要求,方便混淆代碼。但註冊了監聽的模塊必須有一個標註了Subscribe註解方法,不然在register時會拋出異常:

Subscriber class XXX and its super classes have no public methods with the @Subscribe annotation

1.4 發送事件

可以從代碼的任何地方調用post或者postSticky發送事件,此時註冊了的且匹配事件的訂閱者能夠接收到事件。

EventBus.getDefault().post(new MessageEvent("Hello everyone!"));

在實際項目的使用中,register和unregister通常與Activity和Fragment的生命週期相關,ThreadMode.MainThread可以很好地解決Android的界面刷新必須在UI線程的問題,不需要再回調後用Handler中轉(EventBus中已經自動用Handler做了處理),黏性事件可以很好地解決post與register同時執行時的異步問題(這個在原理中會說到),事件的傳遞也沒有序列化與反序列化的性能消耗,足以滿足我們大部分情況下的模塊間通信需求。

二、EventBus源碼跟蹤

我們通過EventBus的使用流程來跟蹤分析它的調用流程,通過我們熟悉的使用方法來深入到EventBus的實現內部並理解它的實現原理。

2.1 創建EventBus對象

先看看 getDefault() :

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

這裏就是設計模式裏我們常用的單例模式,用到了double check。保證了getDefault()得到的都是同一個實例。如果不存在實例,就調用了EventBus的構造方法:

/**
 * 構造函數可以創建多個不同的EventBus,不同的實例之間可以相互隔離,如果只想使用同一個總線,就直接使用getDefault()方法獲取單例
 * 
 * Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a
 * central bus, consider {@link #getDefault()}.
 */
public EventBus() {
    this(DEFAULT_BUILDER);
}

EventBus(EventBusBuilder builder) {
    //key:訂閱的事件,value:訂閱這個事件的所有訂閱者集合
    //private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
    subscriptionsByEventType = new HashMap<>();

    //key:訂閱者對象,value:這個訂閱者訂閱的事件集合
    //private final Map<Object, List<Class<?>>> typesBySubscriber;
    typesBySubscriber = new HashMap<>();

    //粘性事件 key:粘性事件的class對象, value:事件對象
    //private final Map<Class<?>, Object> stickyEvents;
    stickyEvents = new ConcurrentHashMap<>();

    //事件主線程處理
    mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
    //事件 Background 處理
    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;
}

什麼,既然是單例模式構造函數還是 public ?沒錯,這樣的設計是因爲不僅僅可以只有一條總線,還可以有其他的線 (bus) ,訂閱者可以註冊到不同的線上的 EventBus,通過不同的 EventBus 實例來發送數據,不同的 EventBus 是相互隔離開的,訂閱者都只會收到註冊到該線上事件。

然後我們說一下構造函數裏這三個 HasMap

  • subscriptionsByEventType 是以 eventkeysubscriber列表value,當發送 event 的時候,都是去這裏找對應的訂閱者。
  • typesBySubscriber 是以 subscriberkeyevent列表value,當 register()unregister() 的時候都是操作這個map,同時對 subscriptionsByEventType 進行對用操作。
  • stickyEvents 維護的是粘性事件,粘性事件也就是當 event 發送出去之後再註冊粘性事件的話,該粘性事件也能收到之前發送出去的 event

同時構造函數中還創建了 3 個 poster :HandlerPoster ,BackgroundPoster和AsyncPoster,這 3 個 poster 負責線程間調度,稍後的事件分發模塊我們會詳細講到。我們接着看這個構造函數中,最終運用到了builder設計模式,那麼來看看這個 EventBusBuilder 中有哪些參數:

public class EventBusBuilder {
    //線程池
    private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();
    //當調用事件處理函數異常時是否打印異常信息
    boolean logSubscriberExceptions = true;
    //當沒有訂閱者訂閱該事件時是否打印日誌
    boolean logNoSubscriberMessages = true;
    //當調用事件處理函數異常時是否發送 SubscriberExceptionEvent 事件
    boolean sendSubscriberExceptionEvent = true;
    //當沒有事件處理函數對事件處理時是否發送 NoSubscriberEvent 事件
    boolean sendNoSubscriberEvent = true;
    //是否要拋出異常,建議debug開啓
    boolean throwSubscriberException;
    //與event有繼承關係的類是否需要發送
    boolean eventInheritance = true;
    //是否忽略生成的索引(SubscriberInfoIndex)
    boolean ignoreGeneratedIndex;
    //是否嚴格的方法名校驗
    boolean strictMethodVerification;
    //線程池,async 和 background 的事件會用到
    ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
    //當註冊的時候會進行方法名的校驗(EventBus3之前方法名必須以onEvent開頭),而這個列表是不參加校驗的類的列表(EventBus3之後就沒用這個參數了)
    List<Class<?>> skipMethodVerificationForClasses;
    //維護着由EventBus生成的索引(SubscriberInfoIndex)
    List<SubscriberInfoIndex> subscriberInfoIndexes;

    EventBusBuilder() {
    }

    //賦值buidler(可用戶自定義的)給單例的EventBus,如果單例的EventBus不爲null了,則拋出異常
    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()對象來分別初始化EventBus的一些配置,註釋裏我標註了大部分比較重要的對象,這裏沒必要記住,看下面的文章時如果對某個對象不瞭解,可以再回來看看。

2.2 註冊與訂閱Register

EventBus 3.0的註冊入口只提供一個register()方法了,所以我們先來看看register()方法做了什麼:

public void register(Object subscriber) {
    //首先獲得訂閱者的class對象
    Class<?> subscriberClass = subscriber.getClass();

    //通過subscriberMethodFinder來找到訂閱者訂閱了哪些事件.返回一個SubscriberMethod對象的List
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            //訂閱
            subscribe(subscriber, subscriberMethod);
        }
    }
}

可以看到register()方法很簡潔,代碼裏的註釋也很清楚了,我們可以看出通過subscriberMethodFinder.findSubscriberMethods(subscriberClass)方法就能返回一個SubscriberMethod的對象,而SubscriberMethod裏包含了所有我們需要的接下來執行subscribe()的信息。

SubscriberMethod裏包含了什麼呢?下面是它的變量和構造函數。可以看到裏面包括訂閱類裏的具體執行方法Method對象,需要在哪個線程執行ThreadMode,事件類型eventType,優先級priority,以及是否接收粘性sticky事件。

public class SubscriberMethod {
    final Method method;        //具體的執行方法
    final ThreadMode threadMode; //執行線程
    final Class<?> eventType;   //事件類型,也就是執行方法接受的參數類型
    final int priority;         //優先級
    final boolean sticky;       //是否粘性,之後會講到
    /** Used for efficient comparison */
    String methodString;

    public SubscriberMethod(Method method, Class<?> eventType, ThreadMode threadMode, int priority, boolean sticky) {
        this.method = method;
        this.threadMode = threadMode;
        this.eventType = eventType;
        this.priority = priority;
        this.sticky = sticky;
    }

    //...省略其他代碼...
}

然後我們去看看SubscriberMethodFinder類的findSubscriberMethods()是怎麼找到訂閱方法的,最後我們再去關注subscribe()

SubscriberMethodFinder的實現

從字面理解,這個類就是訂閱者方法發現者。一句話來描述SubscriberMethodFinder類就是用來查找和緩存訂閱者響應函數的信息的類。所以我們首先要知道怎麼能獲得訂閱者響應函數的相關信息。在3.0版本中,EventBus提供了一個EventBusAnnotationProcessor註解處理器來在編譯期通過讀取@Subscribe()註解並解析,處理其中所包含的信息,然後生成java類來保存所有訂閱者關於訂閱的信息,這樣就比在運行時使用反射來獲得這些訂閱者的信息速度要快。我們可以參考EventBus項目裏的EventBusPerformance這個例子,編譯後我們可以在build文件夾裏找到這個類,MyEventBusIndex 類,當然類名是可以自定義的。我們大致看一下生成的MyEventBusIndex類是什麼樣的:

/**
 * This class is generated by EventBus, do not edit.
 */
public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscriberClassEventBusAsync.class,
                true, new SubscriberMethodInfo[]{
                new SubscriberMethodInfo("onEventAsync", TestEvent.class, ThreadMode.ASYNC),
        }));

        putIndex(new SimpleSubscriberInfo(TestRunnerActivity.class, true, new SubscriberMethodInfo[]{
                new SubscriberMethodInfo("onEventMainThread", TestFinishedEvent.class, ThreadMode.MAIN),
        }));
    }

    private static void putIndex(SubscriberInfo info) {
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }

    @Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }
}

可以看出是使用一個靜態HashMap即:SUBSCRIBER_INDEX來保存訂閱類的信息,其中包括了訂閱類的class對象,是否需要檢查父類,以及訂閱方法的信息SubscriberMethodInfo的數組,SubscriberMethodInfo中又保存了,訂閱方法的方法名,訂閱的事件類型,觸發線程,是否接收sticky事件以及優先級priority。這其中就保存了register()的所有需要的信息,如果再配置EventBus的時候通過EventBusBuilder配置:eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();來將編譯生成的MyEventBusIndex配置進去,這樣就能在SubscriberMethodFinder類中直接查找出訂閱類的信息,就不需要再利用註解判斷了,當然這種方法是作爲EventBus的可選配置,SubscriberMethodFinder同樣提供了通過註解來獲得訂閱類信息的方法,下面我們就來看findSubscriberMethods()到底是如何實現的:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    //先從METHOD_CACHE取看是否有緩存, key:保存訂閱類的類名,value:保存類中訂閱的方法數據,
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }

    //是否忽略註解器生成的MyEventBusIndex類
    if (ignoreGeneratedIndex) {
        //利用反射來讀取訂閱類中的訂閱方法信息
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        //從註解器生成的MyEventBusIndex類中獲得訂閱類的訂閱方法信息
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        //保存進METHOD_CACHE緩存
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

我們看看利用反射來讀取訂閱類中的訂閱方法信息的函數:findUsingReflection()

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
    //FindState 用來做訂閱方法的校驗和保存
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        //通過反射來獲得訂閱方法信息
        findUsingReflectionInSingleClass(findState);
        //查找父類的訂閱方法
        findState.moveToSuperclass();
    }
    //獲取findState中的SubscriberMethod(也就是訂閱方法List)並返回
    return getMethodsAndRelease(findState);
}

以及從註解器生成的MyEventBusIndex類中獲得訂閱類的訂閱方法信息: findUsingInfo()

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        //得到訂閱者信息
        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.add(subscriberMethod);
                }
            }
        } else {
            //如果沒有訂閱者信息就使用反射查找訂閱方法
            findUsingReflectionInSingleClass(findState);
        }
        //跳轉到父類中繼續查找
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}

進入到getSubscriberInfo() 方法中我們看到了從自定義索引Index獲取訂閱方法信息的操作:

private SubscriberInfo getSubscriberInfo(FindState findState) {
    //判斷FindState對象中是否有緩存的訂閱方法
    if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
        SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
        if (findState.clazz == superclassInfo.getSubscriberClass()) {
            return superclassInfo;
        }
    }
    //從註解器生成的MyEventBusIndex類中獲得訂閱類的訂閱方法信息
    if (subscriberInfoIndexes != null) {
        for (SubscriberInfoIndex index : subscriberInfoIndexes) {
            SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
            if (info != null) {
                return info;
            }
        }
    }
    return null;
}

上面我們可以看到作者使用了FindState類來做訂閱方法的校驗和保存,並通過FIND_STATE_POOL靜態數組來保存FindState對象,可以使FindState複用,避免重複創建過多的對象。最終是通過findUsingReflectionInSingleClass()來具體獲得相關訂閱方法的信息的:

//在較新的類文件,編譯器可能會添加方法。那些被稱爲BRIDGE或SYNTHETIC方法。EventBus必須忽略兩者。有修飾符沒有公開,但在Java類文件中有格式定義
private static final int BRIDGE = 0x40;
private static final int SYNTHETIC = 0x1000;
//需要忽略的修飾符
private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;


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;
    }
    //遍歷Method
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        //必須是public的方法
        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();
                        //實例化SubscriberMethod對象並添加
                        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");
        }
    }
}

關於 BRIDGESYNTHETIC ,註釋寫道:

In newer class files, compilers may add methods. Those are called bridge or synthetic methods. EventBus must ignore both. There modifiers are not public but defined in the Java class file format: http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.6-200-A.1

在較新的類文件,編譯器可能會添加方法。那些被稱爲 BRIDGE 或 SYNTHETIC 方法,EventBus 必須忽略兩者。有修飾符沒有公開,但在 Java 類文件中有格式定義。

findUsingReflectionInSingleClass方法流程是:

  1. 拿到當前 class 的所有方法;
  2. 過濾掉不是 public 和是 abstract、static、bridge、synthetic 的方法;
  3. 過濾出方法參數只有一個的方法;
  4. 過濾出被Subscribe註解修飾的方法;
  5. 將 method 方法和 event 事件添加到 findState 中;
  6. 將 EventBus 關心的 method 方法、event 事件、threadMode、priority、sticky 封裝成 SubscriberMethod 對象添加到 findState.subscriberMethods 列表中;

這裏走完,我們訂閱類的所有SubscriberMethod都已經被保存了,最後再通過getMethodsAndRelease()返回List<SubscriberMethod>。至此,所有關於如何獲得訂閱類的訂閱方法信息即:SubscriberMethod對象就已經完全分析完了,下面我們來看subscribe()是如何實現的。

subscribe()方法的實現

//必須在同步代碼塊裏調用
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    //獲取訂閱的事件類型
    Class<?> eventType = subscriberMethod.eventType;
    //創建Subscription對象
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    //從subscriptionsByEventType裏檢查是否已經添加過該Subscription,如果添加過就拋出異常,也就是每個類只能有一個函數響應同一種事件類型
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }
    //根據優先級priority來添加Subscription對象
    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;
        }
    }
    //將訂閱者對象以及訂閱的事件保存到typesBySubscriber裏.
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);
    //如果接收sticky事件,立即分發sticky事件
    if (subscriberMethod.sticky) {
        //eventInheritance 表示是否分發訂閱了響應事件類父類事件的方法
        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);
        }
    }
}

以上就是所有註冊過程,現在再來看這張圖就會特別清晰EventBusregister()過程了:

到這裏,訂閱流程就走完了。接下來我們在看事件分發的流程。

2.3 發送事件Post

我們知道發送事件是通過post() 方法進行廣播的,比如第一節我們例子中提到的EventBus.getDefault().post(new MessageEvent("Hello everyone!")); 接下來我們進入這個post()方法一窺究竟:

public void post(Object event) {
    //得到當前線程的Posting狀態.
    PostingThreadState postingState = currentPostingThreadState.get();
    //獲取當前線程的事件隊列
    List<Object> eventQueue = postingState.eventQueue;
    eventQueue.add(event);

    if (!postingState.isPosting) {
        // 記錄當前發送線程是否爲主線程
        postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
        postingState.isPosting = true;
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            //處理隊列,一直髮送完所有事件
            while (!eventQueue.isEmpty()) {
                //發送單個事件
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

首先是通過currentPostingThreadState.get()方法來得到當前線程PostingThreadState的對象,爲什麼是說當前線程?我們來看看currentPostingThreadState的實現:

private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
    @Override
    protected PostingThreadState initialValue() {
        return new PostingThreadState();
    }
};

其實現是返回一個 PostingThreadState 對象,而 PostingThreadState 類的結構如下,封裝的是當前線程的 post 信息,包括事件隊列、是否正在分發中、是否在主線程、訂閱者信息、事件實例、是否取消。

/** For ThreadLocal, much faster to set (and get multiple values). */
final static class PostingThreadState {
    final List<Object> eventQueue = new ArrayList<Object>();
    boolean isPosting;
    boolean isMainThread;
    Subscription subscription;
    Object event;
    boolean canceled;
}

綜上,currentPostingThreadState的實現是一個包含了PostingThreadStateThreadLocal對象,關於ThreadLocal張濤的這篇文章解釋的很好:ThreadLocal 是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,而這段數據是不會與其他線程共享的。 其內部原理是通過生成一個它包裹的泛型對象的數組,在不同的線程會有不同的數組索引值,通過這樣就可以做到每個線程通過get() 方法獲取的時候,取到的只能是自己線程所對應的數據。 所以這裏取到的就是每個線程的PostingThreadState狀態.接下來我們來看postSingleEvent()

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    //是否觸發訂閱了該事件(eventClass)的父類,以及接口的類的響應方法
    if (eventInheritance) {
        //查找eventClass類所有的父類以及接口
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        //循環postSingleEventForEventType
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            //只要右邊有一個爲true,subscriptionFound就爲true
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        //post單個
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    //如果沒發現
    if (!subscriptionFound) {
        if (logNoSubscriberMessages) {
            Log.d(TAG, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            //發送一個NoSubscriberEvent事件,如果我們需要處理這種狀態,接收這個事件就可以了
            post(new NoSubscriberEvent(this, event));
        }
    }
}

lookupAllEventTypes() 就是查找該事件的所有父類,返回所有的該事件的父類的 class 。它通過循環和遞歸一起用,將一個類的父類(接口)全部添加到全局靜態變量 eventTypes 集合中。跟着上面的代碼的註釋,我們可以很清楚的發現是在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(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    //根據接收該事件的訂閱方法約定的ThreadMode決定分配到哪個線程執行
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING:
            invokeSubscriber(subscription, event);
            break;
        case MAIN:
            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);
    }
}

總結上面的代碼就是,首先從subscriptionsByEventType裏獲得所有訂閱了這個事件的Subscription列表,然後在通過postToSubscription()方法來分發事件,在postToSubscription()通過不同的threadMode在不同的線程裏invoke()訂閱者的方法,ThreadMode共有四類:

  1. PostThread:默認的 ThreadMode,表示在執行 Post 操作的線程直接調用訂閱者的事件響應方法,不論該線程是否爲主線程(UI 線程)。當該線程爲主線程時,響應方法中不能有耗時操作,否則有卡主線程的風險。適用場景:對於是否在主線程執行無要求,但若 Post 線程爲主線程,不能耗時的操作
  2. MainThread:在主線程中執行響應方法。如果發佈線程就是主線程,則直接調用訂閱者的事件響應方法,否則通過主線程的 Handler 發送消息在主線程中處理——調用訂閱者的事件響應函數。顯然,MainThread類的方法也不能有耗時操作,以避免卡主線程。適用場景:必須在主線程執行的操作
  3. BackgroundThread:在後臺線程中執行響應方法。如果發佈線程不是主線程,則直接調用訂閱者的事件響應函數,否則啓動唯一的後臺線程去處理。由於後臺線程是唯一的,當事件超過一個的時候,它們會被放在隊列中依次執行,因此該類響應方法雖然沒有PostThread類和MainThread類方法對性能敏感,但最好不要有重度耗時的操作或太頻繁的輕度耗時操作,以造成其他操作等待。適用場景:操作輕微耗時且不會過於頻繁,即一般的耗時操作都可以放在這裏;
  4. Async:不論發佈線程是否爲主線程,都使用一個空閒線程來處理。和BackgroundThread不同的是,Async類的所有線程是相互獨立的,因此不會出現卡線程的問題。適用場景:長耗時操作,例如網絡訪問

這裏我們先看看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);
    }
}

實際上就是通過反射調用了訂閱者的訂閱函數並把event對象作爲參數傳入。然後我們就又遇到了在EventBus構造函數中初始化的3個Poster:HandlerPoster(也就是代碼中的mainThreadPoster對象) ,BackgroundPosterAsyncPoster,這 3 個 poster 負責線程間調度。我們分別來看看:

# HandlerPoster

final class HandlerPoster extends Handler {
    //隊列,即將執行的Post
    private final PendingPostQueue queue;
    //一個Post最大的在HandleMessage中的時間
    private final int maxMillisInsideHandleMessage;
    private final EventBus eventBus;
    //handler是否運行起來了
    private boolean handlerActive;

    //EventBus的構造函數中初始化了mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
    //注意此處的Looper.getMainLooper()便指定了主線程的Looper
    HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
        super(looper);
        this.eventBus = eventBus;
        this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
        queue = new PendingPostQueue();
    }

    void enqueue(Subscription subscription, Object event) {
        //PendingPost維護了一個可以複用PendingPost對象的複用池
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            //加入到隊列中
            queue.enqueue(pendingPost);
            //如果handleMessage沒有運行起來
            if (!handlerActive) {
                handlerActive = true;
                //發送一個空消息,讓handleMessage運行起來
                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
                PendingPost pendingPost = queue.poll();
                if (pendingPost == null) {
                    synchronized (this) {
                        // Check again, this time in synchronized
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
                            handlerActive = false;
                            return;
                        }
                    }
                }
                //調用eventBus的方法,分發消息
                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;
        }
    }
}

我們有必要回看EventBus的構造函數中初始化了mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);的代碼。注意這行代碼中傳入的第二個參數Looper.getMainLooper()便指定了主線程的Looper,保證了這個HandlerPoster的運行在主線程。

然後PendingPost 的數據結構是這樣的:

final class PendingPost {
    Object event;//事件
    Subscription subscription;//訂閱
    PendingPost next;//與隊列的數據結構有關,指向下一個節點
}

其中 PendingPost 維護着一個可以複用PendingPost對象的複用池,通過 obtainPendingPost(Subscription, Object) 方法複用,通過 releasePendingPost(PendingPost ) 方法回收。

handleMessage() 中有一個死循環,這個死循環不停的從隊列中拿數據,然後通過 EventBus.invokeSubscriber() 分發出去。每分發完一次比對一下時間,如果超過了 maxMillisInsideHandleMessage ,那麼發送空 message再次進入到 handlerMessage 中且退出本次循環。

# BackgroundPoster

/**
 * Posts events in background.
 * @author Markus
 */
 //我們注意到它實現了Runable接口
final class BackgroundPoster implements Runnable {

    private final PendingPostQueue queue;
    private final EventBus eventBus;

    private volatile boolean executorRunning;

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

    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            //加入到隊列中
            queue.enqueue(pendingPost);
            if (!executorRunning) {
                executorRunning = true;
                //把自己這個Runable拋入線程池開始運行
                eventBus.getExecutorService().execute(this);
            }
        }
    }

    @Override
    public void run() {
        try {
            try {
                while (true) {
                    //從隊列中取出PendingPost,此處的1000表示如果隊列爲空就暫停1000毫秒再取
                    PendingPost pendingPost = queue.poll(1000);
                    if (pendingPost == null) {
                        synchronized (this) {
                            // Check again, this time in synchronized
                            pendingPost = queue.poll();
                            if (pendingPost == null) {
                                executorRunning = false;
                                return;
                            }
                        }
                    }
                    //調用eventBus的方法,分發消息
                    eventBus.invokeSubscriber(pendingPost);
                }
            } catch (InterruptedException e) {
                Log.w("Event", Thread.currentThread().getName() + " was interruppted", e);
            }
        } finally {
            executorRunning = false;
        }
    }

}

同理 BackgroundPoster ,只不過 HandlerPoster 是在 handlerMessage 中進行分發操作,而 BackgroundPoster 是在 Runnablerun 方法中將所有隊列中的消息取出進行分發,直到取完爲止。

# AsyncPoster

/**
 * Posts events in background.
 * @author Markus
 */
 //它也實現Runable接口
class AsyncPoster implements Runnable {

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

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

}

AsyncPoster 雖然也是在 Runnablerun 方法中取出隊列中的消息,但是隻取一個。不論發佈線程是否爲主線程,都使用一個空閒線程來處理。和BackgroundThread不同的是,Async類的所有線程是相互獨立的,因此不會出現卡線程的問題。適用場景:長耗時操作,例如網絡訪問

可以看到,不同的Poster會在post事件時,調度相應的事件隊列PendingPostQueue,讓每個訂閱者的回調方法收到相應的事件,並在其註冊的Thread中運行。而這個事件隊列是一個鏈表,由一個個PendingPost組成,其中包含了事件,事件訂閱者,回調方法這三個核心參數,以及需要執行的下一個PendingPost。至此post()流程就結束了,整體流程圖如下:

2.4 解除註冊Unregister

看完了上面的分析,解除註冊就相對容易了,解除註冊只要調用unregister()方法即可。實現如下:

public synchronized void unregister(Object subscriber) {
    //通過typesBySubscriber來取出這個subscriber訂閱者訂閱的事件類型,
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        //分別解除每個訂閱了的事件類型
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        //從typesBySubscriber移除subscriber
        typesBySubscriber.remove(subscriber);
    } else {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

然後接着看unsubscribeByEventType()方法的實現:

private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    //subscriptionsByEventType裏拿出這個事件類型的訂閱者列表.
    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--;
            }
        }
    }
}

最終分別從typesBySubscribersubscriptions裏分別移除訂閱者以及相關信息即可。

2.5 註解Subscribe

最後我們來看一下EventBus中的這個Subscribe註解定義:


@Documented
@Retention(RetentionPolicy.RUNTIME)    //運行時註解
@Target({ElementType.METHOD})        //用來修飾方法
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;

    /**
     * If true, delivers the most recent sticky event (posted with
     * {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
     */
    boolean sticky() default false;

    /** Subscriber priority to influence the order of event delivery.
     * Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
     * others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
     * delivery among subscribers with different {@link ThreadMode}s! */
    int priority() default 0;
}

我們可以看到EventBus使用的這個註解Subscribe運行時註解(RetentionPolicy.RUNTIME)。

爲什麼需要定義成運行時而不是編譯時註解呢?我們先看一下三種不同時機的註解:

/**
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在運行時有效(即運行時保留)
*/
@Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.SOURCE)
@Retention(RetentionPolicy.CLASS)

@Retention定義了該Annotation被保留的時間長短:某些Annotation僅出現在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因爲Annotation與class在使用上是被分離的)。

因爲EventBus的register()方法中需要通過反射獲得註冊類中通過註解聲明的訂閱方法,也就意味着必須在運行時保留註解信息,以便能夠反射得到這些方法。所以這個Subcribe註解必須是運行時註解。大家有疑惑的可以自己寫個Demo嘗試一下使用反射得到某個類中方法的編譯時註解信息,一定會拋出NullPointerException異常

三、EventBus原理分析

在平時使用中我們不需要關心EventBus中對事件的分發機制,但要成爲能夠快速排查問題的老司機,我們還是得熟悉它的工作原理,下面我們就透過UML圖來學習一下。

3.1 核心架構

EventBus的核心工作機制透過作者Blog中的這張圖就能很好地理解:

訂閱者模塊需要通過EventBus訂閱相關的事件,並準備好處理事件的回調方法,而事件發佈者則在適當的時機把事件post出去,EventBus就能幫我們搞定一切。在架構方面,EventBus 3.0與之前稍老版本有不同,我們直接看架構圖:

EventBus 3.0架構圖

爲了方便理解或者對比,順便也放一張2.x老版本的結構圖吧:

EventBus 2.x老版本結構圖

雖然更新了3.0,但是整體上的設計還是可以用上面的類圖來分析,從類圖上我們可以看到大部分類都是依賴於EventBus的,上部分主要是訂閱者相關信息,中間是 EventBus 類,下面是發佈者發佈事件後的調用。

根據UML圖,我們先看核心類EventBus,其中subscriptionByEventType是以事件的類爲key,訂閱者的回調方法爲value的映射關係表。也就是說EventBus在收到一個事件時,就可以根據這個事件的類型,在subscriptionByEventType中找到所有監聽了該事件的訂閱者及處理事件的回調方法。而typesBySubscriber則是每個訂閱者所監聽的事件類型表,在取消註冊時可以通過該表中保存的信息,快速刪除subscriptionByEventType中訂閱者的註冊信息,避免遍歷查找。註冊事件、發送事件和註銷都是圍繞着這兩個核心數據結構來展開。上面的Subscription可以理解爲每個訂閱者與回調方法的關係,在其他模塊發送事件時,就會通過這個關係,讓訂閱者執行回調方法。

回調方法在這裏被封裝成了SubscriptionMethod,裏面保存了在需要反射invoke方法時的各種參數,包括優先級,是否接收黏性事件和所在線程等信息。而要生成這些封裝好的方法,則需要SubscriberMethodFinder,它可以在regster時得到訂閱者的所有回調方法,並封裝返回給EventBus。而右邊的加速器模塊,就是爲了提高SubscriberMethodFinder的效率,這裏就不再囉嗦。

至此EventBus 3.0的架構就分析完了,與之前EventBus老版本最明顯的區別在於:分發事件的調度單位從訂閱者,細化成了訂閱者的回調方法。也就是說每個回調方法都有自己的優先級,執行線程和是否接收黏性事件,提高了事件分發的靈活程度,接下來我們在看核心功能的實現時更能體現這一點。

3.2 register

簡單來說就是:根據訂閱者的類來找回調方法,把訂閱者和回調方法封裝成關係,並保存到相應的數據結構中,爲隨後的事件分發做好準備,最後處理黏性事件。

註冊訂閱流程

  1. 根據訂閱者來找到訂閱方法和事件,封裝成 SubscriberMehod
  2. 循環每個 SubscriberMethod
  3. 通過事件得到該事件的所有訂閱者列表,再根據優先級插入到 subscriptionsByEventType 的所有訂閱者列表中
  4. 通過訂閱者得到該訂閱者的所有事件列表,再將事件添加到 typeBySubscriber 的所以事件列表中
  5. 是否是粘性事件
  6. 是的話進行分發,post此事件給當前訂閱者,不是的話不管
  7. 結束本次循環,跳到 2

3.3 post

總的來說就是分析事件,得到所有監聽該事件的訂閱者的回調方法,並利用反射來invoke方法,實現回調。

發送流程

  1. currentPostingThreadState 中得到當前線程的 PostThreadState 信息
  2. 將此事件添加到 PostPostThreadState 的事件隊列中
  3. 判斷是否再分發
  4. 不是的話,循環隊列,是的話跳 7
  5. 判斷是個需要繼承關係
  6. 是的話,循環得到父類,不是的話跳 7
  7. 查找該事件的訂閱者,循環訂閱者
  8. 根據 ThreadMoth 發送事件
  9. 結束本次循環訂閱者,跳 7
  10. 結束本次循環隊列,跳 4

在源代碼中爲了保證post執行不會出現死鎖,等待和對同一訂閱者發送相同的事件,增加了很多線程保護鎖和標誌位,值得我們每個開發者學習。

3.4 unregister

註銷就比較簡單了,把在註冊時往兩個數據結構中添加的訂閱者信息刪除即可:

註銷流程

至此大家對EventBus的運行原理應該有了一定的瞭解,雖然看起來像是一個複雜耗時的自動機,但大部分時候事件都是一瞬間就能分發到位的,而大家關心的性能問題反而是發生在註冊EventBus的時候,因爲需要遍歷監聽者的所有方法去找到回調的方法。作者也提到運行時註解的性能在Android上並不理想,爲了解決這個問題,作者纔會以索引的方式去生成回調方法表,也就是在EventBus 3.0中引入了EventBusAnnotationProcessor(註解分析生成索引)技術,大大提高了EventBus的運行效率。關於索引技術的源碼分析,大家可以參考騰訊Bugly的這邊文章:老司機教你 “飆” EventBus 3

四、缺點與問題

一直以來,EventBus被大家吐槽的一大問題就是代碼混淆問題。

4.1 混淆問題

混淆作爲版本發佈必備的流程,經常會鬧出很多奇奇怪怪的問題,且不方便定位,尤其是EventBus這種依賴反射技術的庫。通常情況下都會把相關的類和回調方法都keep住,但這樣其實會留下被人反編譯後破解的後顧之憂,所以我們的目標是keep最少的代碼。

首先,因爲EventBus 3棄用了反射的方式去尋找回調方法,改用註解的方式。作者的意思是在混淆時就不用再keep住相應的類和方法。但是我們在運行時,卻會報java.lang.NoSuchFieldError: No static field POSTING。網上給出的解決辦法是keep住所有eventbus相關的代碼:

-keep class de.greenrobot.** {*;}

其實我們仔細分析,可以看到是因爲在SubscriberMethodFinder的findUsingReflection方法中,在調用Method.getAnnotation()時獲取ThreadMode這個enum失敗了,所以我們只需要keep住這個enum就可以了(如下)。

-keep public enum org.greenrobot.eventbus.ThreadMode { public static *; }

這樣就能正常編譯通過了,但如果使用了索引加速,是不會有上面這個問題的。因爲在找方法時,調用的不是findUsingReflection,而是findUsingInfo。但是使用了索引加速後,編譯後卻會報新的錯誤:Could not find subscriber method in XXX Class. Maybe a missing ProGuard rule?

這就很好理解了,因爲生成索引GeneratedSubscriberIndex是在代碼混淆之前進行的,混淆之後類名和方法名都不一樣了(上面這個錯誤是方法無法找到),得keep住所有被Subscribe註解標註的方法:

-keepclassmembers class * {
    @de.greenrobot.event.Subscribe <methods>;
}

所以又倒退回了EventBus2.4時不能混淆onEvent開頭的方法一樣的處境了。所以這裏就得權衡一下利弊:使用了註解不用索引加速,則只需要keep住EventBus相關的代碼,現有的代碼可以正常的進行混淆。而使用了索引加速的話,則需要keep住相關的方法和類。

4.2 跨進程問題

目前EventBus只支持跨線程,而不支持跨進程。如果一個app的service起到了另一個進程中,那麼註冊監聽的模塊則會收不到另一個進程的EventBus發出的事件。這裏可以考慮利用IPC做映射表,並在兩個進程中各維護一個EventBus,不過這樣就要自己去維護register和unregister的關係,比較繁瑣,而且這種情況下通常用廣播會更加方便,大家可以思考一下有沒有更優的解決方案。

4.3 事件環路問題

在使用EventBus時,通常我們會把兩個模塊相互監聽,來達到一個相互回調通信的目的。但這樣一旦出現死循環,而且如果沒有相應的日誌信息,很難定位問題。所以在使用EventBus的模塊,如果在回調上有環路,而且回調方法複雜到了一定程度的話,就要考慮把接收事件專門封裝成一個子模塊,同時考慮避免出現事件環路。

五、總結

EventBus不論從使用方式和實現方式上都是非常值得我們學習的開源項目,可以說是目前消息通知裏最好用的項目。但是業內對EventBus的主要爭論點是在於EventBus使用反射會出現性能問題,實際上在EventBus裏我們可以看到不僅可以使用註解處理器預處理獲取訂閱信息,EventBus也會將訂閱者的方法緩存到METHOD_CACHE裏避免重複查找,所以只有在最後invoke()方法的時候會比直接調用多出一些性能損耗。

而且相比舊版的2.x,現在新版的EventBus 3.0,訂閱者已經沒有固定的處理事件的方法了,onEventonEventMainThreadonEventBackgroundThreadonEventAsync都沒有了,現在支持處理事件的方法名自定義,但必須public,只有一個參數,然後使用註解@Subscribe來標記該方法爲處理事件的方法,ThreadMode和priority也通過該註解來定義。在subscriberMethodFinder中,通過反射的方式尋找事件方法。使用註解,用起來才更爽。

當然,EventBus並不是重構代碼的唯一之選。作爲觀察者模式的“同門師兄弟”——RxJava,作爲功能更爲強大的響應式編程框架,可以輕鬆實現EventBus的事件總線功能(RxBus)。但畢竟大型項目要接入RxJava的成本高,複雜的操作符需要開發者投入更多的時間去學習。所以想在成熟的項目中快速地重構、解耦模塊,EventBus依舊是我們的不二之選。


參考資料

發佈了103 篇原創文章 · 獲贊 255 · 訪問量 56萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章