EventBus之高效使用

EventBus之高效使用

說起 EventBus,作爲一名 Android 開發者,應該不會太陌生,但是我們大部分都會根據官方文檔直接進行使用,其實還有一種比較高效的使用方式。就是不使用註解的方式,在編譯時期,對相關注冊方法進行註冊。

這其實就相當於用空間換時間的一種常規操作了。這裏附上 官方源碼官方文檔 的地址。

先來貼一張官方文檔中的圖解,讓大家對 EventBus 的工作機制現有一個宏觀上的回憶。

在這裏插入圖片描述

常規使用

先來看看常規的使用方式。

1、接入EventBus

 implementation 'org.greenrobot:eventbus:3.2.0'

2、使用

//1、創建訂閱事件
class DataModel(val msg: String)
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //2、註冊訂閱
        EventBus.getDefault().register(this)
    }

    //3、創建接收發布的事件的方法
    @Subscribe(threadMode = org.greenrobot.eventbus.ThreadMode.MAIN, sticky = true, priority = 2)
    fun dataReceive(dataModel: DataModel) {
        Toast.makeText(this, dataModel.msg, Toast.LENGTH_LONG).show()
    }

    fun post(view: View) {
        //4、發佈事件
        EventBus.getDefault().post(DataModel("send success"))
    }

    override fun onDestroy() {
        //5、取消訂閱
        EventBus.getDefault().unregister(this)
        super.onDestroy()
    }

}

使用方式很簡單,基本上和官方介紹的一樣,爲了方便就在這裏聚合了一下。
下面來分析這種方式,是怎麼實現訂閱者與發佈者之間進行信息交互的。

常規使用之源碼分析

註冊訂閱者

在分析具體源碼之前,先來看看幾個相關的輔助類

1、Subscribe 作用於訂閱事件方法的註解類

//運行時
@Retention(RetentionPolicy.RUNTIME)
//目標作用於方法
@Target({ElementType.METHOD})
public @interface Subscribe {
    //線程模式,有當線程、主線程、子線程、異步線程等。
    ThreadMode threadMode() default ThreadMode.POSTING;

    //是否爲粘性事件,就是可以先進行發佈,後進行註冊,註冊時候會立刻收到發佈的事件
    boolean sticky() default false;

    //接受的優先級,數值越大優先級越高
    int priority() default 0;
}

2、Subscription、SubscriberMethod 訂閱方法的封裝類

final class Subscription {
    //訂閱的對象,例如上面的 MainActivity 對象(這裏需要注意,不是類,而是具體的對象)
    final Object subscriber;
    //註冊類中的訂閱方法(與對象無關,解析相關類中的訂閱方法)
    final SubscriberMethod subscriberMethod;
}
public class SubscriberMethod {
    //訂閱的方法名
    final Method method;
    //執行的線程環境
    final ThreadMode threadMode;
    //訂閱事件類型(訂閱方法中的參數類型)
    final Class<?> eventType;
    //優先級
    final int priority;
    //是否爲粘性事件
    final boolean sticky;
    //用於比較兩個方法是否相同,具體在繼承中會用到
    String methodString;
}

基於上面的使用方法,我們可以直接從第二步的註冊訂閱事件的 register() 方法來進行源碼的跟蹤。

先來看看註冊事件

public class EventBus {
    //訂閱方法的集合:<訂閱類型,該類型的多有方法集合>,方便用於事件分發
    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
    //訂閱方法的集合:<訂閱者對象,該類中的所有註冊方法>,方便用於去掉訂閱
    private final Map<Object, List<Class<?>>> typesBySubscriber;
    //所有的粘性事件集合:<訂閱事件類型,事件對象>,用於後續新事件註冊後的粘性事件分發
    private final Map<Class<?>, Object> stickyEvents;

    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        //從註冊類中找出所有訂閱事件的相關方法
        //下文在分析 SubscriberMethodFinder 這個輔助類,這裏先跳過,看註冊的主流程
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //拿到當前訂閱事件type的訂閱集合
        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) {
            ......
            //如果是粘性事件,則判斷是否需要進行派發
            //這也就是爲什麼可以先發布事件,後面進行註冊也可以收到發佈事件的原因了
            //每次新註冊方法時都會判斷是否需要進行粘性事件的分發
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            ......
        }
    }
    private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if (stickyEvent != null) {
            postToSubscription(newSubscription, stickyEvent, isMainThread());
        }
    }
    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        ......
        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);
        }
    }
}

下面來看下上文中略過的 SubscriberMethodFinder 輔助類。

這個類的作用就是從傳入的註冊類中找出訂閱方法的集合並返回。

class SubscriberMethodFinder {
    private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
    //從緩存中查找是否曾經註冊過,如果有則直接返回
    private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
    //這裏就是上文中註冊方法調用的方法,根據訂閱的 class 尋找該類中的所有訂閱方法
    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

        if (ignoreGeneratedIndex) {
            //使用反射尋找,由於沒有進行任何相關配置,所以默認會使用反射進行查找,所以效率相對來說比較底下
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            //使用添加的信息尋找
            //這裏後文分析高效使用時,在進行分析
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        if (subscriberMethods.isEmpty()) {
            //如果註冊的類中沒有訂閱方法,則直接拋出異常。這就有點坑,容易出bug
            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;
        }
    }
    private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        //循環遍歷當前類和父類,通過反射查找相應的訂閱方法
        while (findState.clazz != null) {
            //查找當前類的訂閱方法
            findUsingReflectionInSingleClass(findState);
            //查找完畢後,將當前 class 定位到父類
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }
    private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // 拿到該類中的所有方法,不包括父類的方法
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            try {
                // 拿到該類中的所有方法,包括父類的方法
                methods = findState.clazz.getMethods();
            } catch (LinkageError error) { 
                ......
            }
            findState.skipSuperClasses = true;
        }
        // 遍歷所有方法,找到符合目標的訂閱方法
        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 註解屬性,如果沒有則不是訂閱方法
                    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)) {
                    ......
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                ......
            }
        }
    }
}

到這裏,註冊的流程基本上就完了。上面的註釋寫的基本上很詳細了,也是從上到下的一個流程。

發佈訂閱事件

註冊完成後,就是接受訂閱事件了,以發佈事件爲入口,看看事件發佈後,訂閱的方法是如何接受到發佈的訂閱事件。

public class EventBus {
    public void post(Object event) {
        //用於判斷在當前線程的分發狀態
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        // 將發佈事件加入隊列中
        eventQueue.add(event);

        if (!postingState.isPosting) {
            postingState.isMainThread = isMainThread();
            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;
            }
        }
    }
    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        ......
        postSingleEventForEventType(event, postingState, eventClass);
        ......
    }
    private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            // 根據事件的類型,拿到所有訂閱該事件的方法
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null && !subscriptions.isEmpty()) {
            for (Subscription subscription : subscriptions) {
                ......
                // 遍歷所有訂閱該事件的方法,並進行分發
                postToSubscription(subscription, event, postingState.isMainThread);
                ......
            }
            return true;
        }
        return false;
    }
    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        ......
        // 這裏會進行相應的線程切換,就不做過多解析
        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);
        }
    }
}

發佈事件到這就結束了,就是從訂閱的集合中拿到訂閱的相關方法並進行調用,就完事了。

解註冊訂閱者

最後就是解註冊了,相比分發就更爲簡單了,沒什麼說的,就是從集合中找出,刪除完事。

public class EventBus {
    public synchronized void unregister(Object subscriber) {
        // 拿到解註冊類中訂閱的所有方法
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            for (Class<?> eventType : subscribedTypes) {
                // 遍歷所有方法,根據事件類型,從 subscriptionsByEventType 集合中移除相應的訂閱事件
                unsubscribeByEventType(subscriber, eventType);
            }
            // 移除
            typesBySubscriber.remove(subscriber);
        } else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }
    private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
        List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions != null) {
            int size = subscriptions.size();
            for (int i = 0; i < size; i++) {
                Subscription subscription = subscriptions.get(i);
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }
}

以上就是我們正常使用 EventBus 的全過程解析了,有沒有豁然開朗。其實原理並沒有多麼深奧,只要靜下心看看就可以理解。更多需要注意的就是那些異常的處理以及細節的考慮了,這些纔是這個項目的精華所在。

高效使用

既然題目是高效使用,那這部分纔是要說的重點了,上面哪些相信大家都已經耳熟能詳了,主要是總結一下。

在介紹使用之前,先看看一組數據對比,根據官方作者自述,這種方式可以進一步提升 app 的運行效率。因爲在其編譯時期就爲其建立了一份索引庫,而不是在運行時纔去建立。相當於一種空間換時間,來提高 app 的運行效率

在這裏插入圖片描述

下面來看看怎麼使用

1、在項目根目錄中的 build 文件中新增 android-apt 插件引用

dependencies {
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}

2、在項目的 build 文件中使用插件

apply plugin: 'kotlin-kapt'

kapt {
    arguments {
        arg('eventBusIndex', 'com.silence.eventbusdemo.EventBusIndex')
    }
}
dependencies {
    implementation 'org.greenrobot:eventbus:3.2.0'
    kapt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}

配置完成後點擊 Build -> Rebuild Project 。重新 build 項目後,會在 build/generated/source/kapt/debug/com/silence/eventbusdemo 目錄下生成一個 EventBusIndex 類。

這個類比較簡單。就是在編譯期間找到所有的訂閱方法,然後構造相關訂閱方法的信息,保存起來。在進行事件發佈時,直接從這裏面找就可以了。

public class EventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

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

        putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("dataReceive", DataModel.class, ThreadMode.MAIN, 2, true),
        }));

    }

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

3、在 Application 中引用生成的類

//別忘了在 manifest 文件中聲明 aplication
class DemoApplication: Application() {
    override fun onCreate() {
        super.onCreate()
        EventBus.builder().addIndex(EventBusIndex()).installDefaultEventBus()
    }
}

4、使用和普通的正常使用一樣,這裏就不贅述了

高效使用之源碼分析

還記得上面跳過的那個獲取訂閱事件方法嗎,有一處沒有分析,這裏就分析一下。

class SubscriberMethodFinder {
    
    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        if (ignoreGeneratedIndex) {
            //使用反射尋找,由於沒有進行任何相關配置,所以默認會使用反射進行查找,所以效率相對來說比較底下
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            //使用添加的信息尋找,相關配置完成後會走到這裏
            subscriberMethods = findUsingInfo(subscriberClass);
        }
    }
    private List<SubscriberMethod> findUsingInfo(Class<?> 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);
    }
    private SubscriberInfo getSubscriberInfo(FindState findState) {
        ......
        for (SubscriberInfoIndex index : subscriberInfoIndexes) {
            // 根據訂閱類,從編譯時生成的集合中找出訂閱方法的信息
            SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
            if (info != null) {
                return info;
            }
        }
        ......
        return null;
    }
}

到這,普通版和進化版就介紹完畢了。有興趣的同學可以看看那個插件是怎麼生成相關類的,這裏就不貼了,官網源碼上就有。

手擼簡易版 CustomEventBus

爲了加深印象,這裏 100 多行手擼了一個 簡易版 CustomEventBus。僅供學習,不能再項目中使用昂。

這裏在附上 CustomeEventBus 源碼。

在這裏插入圖片描述

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