EventBus 3.0源碼簡要分析

一、簡介
EventBus(3.0)、RxJava(RxAndroid)都是常用的解耦框架,目的都是爲了讓代碼中的事件處理獨立,減少類與類之間的依賴和耦合。
附上EventBus源碼地址:https://github.com/greenrobot/EventBus
二、EventBus(3.0)
(1)EventBus(3.0)介紹
首先介紹一個EventBus 3.0 和 2.x的區別
主要區別在訂閱函數的不同
EventBus2.x中只暴露了四個方法供用戶調用,分別是
  • onEvent:該事件在哪個線程發佈出來的,onEvent就會在這個線程中運行,也就是說發佈事件和接收事件線程在同一個線程。使用這個方法時,在onEvent方法中不能執行耗時操作,如果執行耗時操作容易導致事件分發延遲。
  • onEventMainThread:不論事件是在哪個線程中發佈出來的,onEventMainThread都會在UI線程中執行,接收事件就會在UI線程中運行,這個在Android中是非常有用的,因爲在Android中只能在UI線程中跟新UI,所以在onEvnetMainThread方法中是不能執行耗時操作的。
  • onEventBackgroundThread:如果事件是在UI線程中發佈出來的,那麼onEventBackground就會在子線程中運行,如果事件本來就是子線程中發佈出來的,那麼onEventBackground函數直接在該子線程中執行。
  • onEventAsync:無論事件在哪個線程發佈,都會創建新的子線程在執行onEventAsync.

EventBus3.0中必須使用註解,例如:

@Subscribe(threadMode = ThreadMode.Async, sticky = true, priority = 1)
public void firstEvent(FirstEvent event) {
    Log.e("TAG", "Async" + Thread.currentThread().getName());
}

好處在於訂閱函數可以隨便起名字,其他與2.x沒什麼不同。這裏Subscribe中的key需要解釋一下含義,Subscribe中可以傳三種值:
  • ThreadMode:這是個枚舉,有四個值,決定訂閱函數在哪個線程執行
  • PostThread:事件發送者在哪個線程就執行在哪個線程。同2.x中的onEvent方法,默認值就是這個
  • MainThread:訂閱函數一定執行在主線程。同onEventMainThread方法
  • BackgroundThread:如果是事件從子線程發出,訂閱函數就執行在那個子線程,不會創建新的子線程;如果主線程發出事件,則創建子線程。同onEventBackgroundThread方法
  • Async:一定創建子線程。同onEventAsync方法。
  • sticky:默認爲false,如果爲true,當通過postSticky發送一個事件時,這個類型的事件的最後一次事件會被緩存起來,當有訂閱者註冊時,會把之前緩存起來的這個事件直接發送給它。使用在比如事件發送者先啓動了,訂閱者還沒啓動的情況。
  • priority:默認值爲0。訂閱了同一個事件的訂閱函數,在ThreadMode值相同的前提下,收到事件的優先級。
EventBus 3.0這樣修改有一個很好的好處是名字可以隨便修改,可以根據功能命名,不會因爲同名方法造成混亂。
(2)使用
下面展示一個簡單的demo
public class MainActivity extends AppCompatActivity {
    private View mEventBusView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mEventBusView = findViewById(R.id.event_bus_view);

        EventBus.getDefault().register(this);

        mEventBusView.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                EventBus.getDefault().post("this is the event bus test!");
            }
        });
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }

    public void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }
}
使用非常簡單,因爲EventBus本來是用來解耦的,其他的還有幾種方法,具體使用可以參見官網:http://greenrobot.org/eventbus/documentation/,也可以直接參考一些博客的例子,網上都有非常多的demo。
(3)源碼分析
EventBus使用非常簡單,但是使用的時候,會有疑問了,怎麼調用的,比如上面的onMessageEvent這個接口,怎麼調用的,完全不明白。首先看一個官網給的圖,EventBus採用的是訂閱-發佈的消息模式(或者說是觀察者模式)。


其次來看一下實際怎麼實現這種消息模式的。從上面簡單的demo中,可以看到幾個關鍵的東西,register、unregister和post接口,還有一個不知道怎麼被調用的onMessageEvent接口。
a.EventBus初始化
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;
}
EventBus的線程模式有POSTING、MAIN、BACKGROUND、ASYNC四種,其中posting是表示在同一個線程調用和執行;MAIN是不管在哪個線程調用都在主線程執行;BACKGROUND表示只要不是在主線程的話,就在當前線程執行,否則用一個新線程來執行,ASYNC表示異步執行。上面的mainThreadPoster對應了MAIN模式、backgroundPoster對應了BACKGROUND模式,asyncPoster對應了ASYNC模式,那麼POSTING呢,當然不需要,因爲它是哪個線程調用,就在哪個線程執行的,不需要另外的poster。
b.EventBus的register過程
public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    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);
        }
    }

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

    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, 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);
        }
    }
}
上述的過程用簡單的語句描述吧,首先通過反射的方式,獲取要註冊的對象的所有包含Subscriber註釋的(Annotation),這裏主要是這一塊,等一下看具體的代碼。
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
然後根據不同的線程模式,同時按照不同的優先級放入到列表當中,主要是這一塊代碼。
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    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);
        }
    }

    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;
        }
    }
需要注意的是,還有一個sticky屬性的,這個是指包含了sticky屬性的方法,可以先通過postSticky方法拋出消息,暫存起來,後面再通過regiser註冊對象的時候,包含了sticky屬性的方法還可以接收到消息。
再來看剛纔提到的通過反射的方式,獲取要註冊的對象的所有包含Subscriber註釋的(Annotation)那一塊的代碼,這裏是在類SubscriberMethodFinder中實現的。只看核心的部分代碼
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");
        }
    }
}
從代碼中可以看到,它只獲取Subsciber註釋的方法,然後把構建SubscriberMethod的對象,返回結果。下面是核心部分
                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()));
                    }
有一點可能有讀者注意到的是if條件
(modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0
EventBus會首先過濾掉,abstract、static、bridge、synthetic方法,後面兩者是什麼東東,可以看一下另一篇文章:http://blog.csdn.net/newhope1106/article/details/53897631
上面的代碼也解釋了上面demo中的onMessageEvent怎麼被加進來的,下面怎麼調用也可以猜到吧。
c.post方法調用
public void post(Object event) {
    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;
        }
    }
}
遍歷所有的Subscription,然後調用對應的接口,在調用的時候,是根據參數類型來調用的。看下面的調用就可以知道,如果沒有這個參數的方法,會拋出異常。
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);
    }
}
上文提到的不同模式的線程的執行規則,如下代碼所示,POSTING模式的就在當前線程執行,MAIN模式,如果當前線程在主線程,則直接調用,否則放到通過mainThreadPoster放到主線程中執行,BACKGROUND模式則相反,ASYNC則異步執行
     private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        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);
        }
    }


d.unregister方法調用,這一塊比較簡單,直接從列表中去掉即可
public synchronized void unregister(Object subscriber) {
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        typesBySubscriber.remove(subscriber);
    } else {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}
四、總結
EventBus作爲一個非常優秀的框架,複雜都留給我框架的作者,而把簡單留給了使用者,使用只需要花大概幾分鐘的時間閱讀就可以使用這個框架,同時框架本身包含了優先級。但是有一個缺點是無差別處理,調用了post方法,會遍歷所有類型的接口來處理,一個建議是希望,在post中可以加一些規則匹配,可以自定義規則,快速找到對應方法處理,而不需要這樣遍歷所有的方法,提高效率。

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