EventBus 使用/架構/源碼分析

EventBus是針對Android優化的發佈-訂閱事件總線,簡化了Android組件間的通信。EventBus以其簡單易懂、優雅、開銷小等優點而備受歡迎。

github 地址:https://github.com/greenrobot/EventBus

1. 使用

1.1 gradle中引入

        api 'org.greenrobot:eventbus:3.0.0'

1.2 定義事件

定義一個類作爲事件,可以在類中定義不同的參數,發佈者賦值,訂閱者取值。
public class TestEvent {

    private String mName;


    public TestEvent(String name) {
        mName = name;
    }

    public String getEventName() {
        return mName;
    }
}

1.3 註冊事件

首先需要將當前對象(Activity/Fragment等)與EventBus綁定(一般在onCreate函數中進行註冊)

EventBus.getDefault().register(this);

接收事件的函數:

@Subscribe (threadMode = ThreadMode.MAIN, sticky = true)
public void onTestKeyEvent(TestEvent event) {
        Log.d(TAG, "onTestKeyEvent | eventName=" + event.getEventName());
        Toast.makeText(this, "test event, name=" + event.getEventName(), Toast.LENGTH_SHORT).show();
    }

這裏通過註解的方式,定義事件的類型,和回調的線程等信息。

查看EventBus jar包中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 jar包中ThreadMode定義:

a) POSTING : 回調在發佈者線程

b) MAIN : 回調在主線程

c) BACKGROUND : 回調在子線程(如果發佈在子線程者回調直接運行在該線程)

d) ASYNC : 異步回調(不回回調在發佈線程也不會回調在主線程)

 

1.4 發送事件

發佈者不需要進行註冊,只需要將事件post出去。

a)  普通事件:EventBus.getDefault().post(new TestEvent("normalEvent"));

b) 粘性事件:EventBus.getDefault().postSticky(new TestEvent("stickEvent"));

 

普通事件和粘性事件區別:

     如果發佈的是普通事件,當前如果沒有Subscriber,則後續註冊的Subscriber也不會收到該事件。

     如果發佈的是粘性事件,當前如果沒有Subscriber,內部會暫存該事件,當註冊Subscriber時,該Subscriber會立刻收到該事件。

 

2. 結構

EventBus-Publish-Subscribe

採用了典型的訂閱發佈設計模式。

3. 源碼分析

// 這裏只分析其原理和結構不會細細推敲每一行代碼

訂閱者信息封裝(Subscription):

定義了兩個成員變量,

final Object subscriber;  // 訂閱一個事件的對象
final SubscriberMethod subscriberMethod; // 訂閱的具體信息(方法名/ThreadMode/isStrick/priority)

EventBus主要成員變量:

private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
private final Map<Object, List<Class<?>>> typesBySubscriber;
private final Map<Class<?>, Object> stickyEvents;

subscriptionsByEventType:以event(即事件類)爲key,以訂閱列表(Subscription)爲value,事件發送之後,在這裏尋找訂閱者,而Subscription又是一個CopyOnWriteArrayList,這是一個線程安全的容器。你們看會好奇,Subscription到底是什麼,其實看下去就會了解的,現在先提前說下:Subscription是一個封裝類,封裝了訂閱者、訂閱方法這兩個類。
typesBySubscriber:以訂閱者類爲key,以event事件類爲value,在進行register或unregister操作的時候,會操作這個map。
stickyEvents:保存的是粘性事件

 

3.1 註冊Subscriber

註冊過程,也就是調用regester函數的執行過程(主要是通過反射將註冊者信息添加到上述講的兩個map中:typesBySubscriber、subscriptionsByEventType

regeister_

a) SubscriberMethodFinder 是專門用來查找目標對象中所有訂閱函數(帶緩存,避免同一個類多次反射查找)。反射可以獲取函數的註解內容及每個函數的返回值/修飾符,具體查看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;
        }
        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");
            }
        }
    }
查找訂閱函數
b) 將訂閱函數添加到兩個緩存map中
c) 如果訂閱函數接收的是粘性事件,則將緩存中的粘性事件回調給該訂閱函數。

上述b) c) 兩個步驟的具體代碼如下:
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);
            }
        }
    }
緩存訂閱信息

3.2 分發消息到每個Subscriber

分發過程是從subscriptionsByEventType中取Subscriber並在指定的線程中回調接收函數的過程。

posy

如何實現在不同線程中執行回調函數?

a)從訂閱信息中獲取訂閱函數回調線程。

b) 在指定線程中回調訂閱函數。

分發消息過程
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);
        }
    }

不同的的消息分發器在EventBus構造的時候初始化,下面看一下AsyncPoster的源碼如下:

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分發器繼承自Runable,核心是通過自定義的阻塞隊列維護消息,然後在EventBus定義的線程池中執行runable接口中的代碼。

EventBus中還定義了BackgroundPoster/HandlerPoster這裏不贅述。

3.3 物理類圖

類圖

 

其它細節:

上述分析只是講解了EventBus大概原理,並沒有細細分析。如,代碼中很多考慮了併發,事件優先級等

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