Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android EventBus框架(二)之源碼簡單解析

Android EventBus框架(二)之源碼簡單解析

編輯:關於Android編程

上一篇,我們基本知道了EventBus的使用步驟,接下來先簡單研究一下其中的源碼。在分析源碼之前,我們先回顧一下Java反射的知識點:

JAVA反射機制

基本定義:

JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。

Sun為提供的關於反射機制中的類:

java.lang.Class;
java.lang.reflect.Constructor;
java.lang.reflect.Field;
java.lang.reflect.Method;
java.lang.reflect.Modifier;
我們可以通過反射機制訪問java對象的屬性,方法,構造方法等。
(1)getDeclaredMethods()
獲取所有的方法
(2)getDeclaredMethod(“方法名”,參數類型.class,……)
獲得特定的方法
(3)getFields()則是返回類型中的所有公有屬性
(4)getDeclaredField( )
獲得特定的屬性

JAVA反射的常規使用步驟

(1)得到要調用類的class
(2)得到要調用的類中的方法(Method)
(3)方法調用(invoke)

如下:

//實體類
public class Student {  
    private int age;  
    private String name;  
    public int getAge() {  
        return age;  
    }  
    public void setAge(int age) {  
        this.age = age;  
    }  
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  

    public static void toString(int age,String name){  
        System.out.println("大家好,我叫"+name+",今年"+age+"歲");  
    }  

//通過反射調用toString()方法
Class cls = Class.forName("chb.test.reflect.Student");  
Method m = cls.getDeclaredMethod("toString",new Class[]{int.class,String.class});  
m.invoke(cls.newInstance(),20,"chb")

反射的基本知識我們知曉了,再來看看EventBus的一些基本知識:
首先對EventBus的有一個大局的認識,參考官方的介紹圖如下:

這裡寫圖片描述

EventBus中的觀察者通常有四種訂閱函數

(就是某件事情發生被調用的方法)
(1)、onEvent
(2)、onEventMainThread
(3)、onEventBackground
(4)、onEventAsync
這四種訂閱函數都是使用onEvent開頭的,它們的功能稍有不同,在介紹不同之前先介紹兩個概念:
(1)告知觀察者事件發生時通過EventBus.post函數實現,這個過程叫做事件的發布;
(2)觀察者被告知事件發生叫做事件的接收,是通過下面的四種訂閱函數實現的。

1 onEvent:如果使用onEvent作為訂閱函數,那麼該事件在哪個線程發布出來的,onEvent就會在這個線程中運行,也就是說發布事件和接收事件線程在同一個線程。使用這個方法時,在onEvent方法中不能執行耗時操作,如果執行耗時操作容易導致事件分發延遲。
2 onEventMainThread:如果使用onEventMainThread作為訂閱函數,那麼不論事件是在哪個線程中發布出來的,onEventMainThread都會在UI線程中執行,接收事件就會在UI線程中運行,這個在Android中是非常有用的,因為在Android中只能在UI線程中跟新UI,所以在onEvnetMainThread方法中是不能執行耗時操作的。
3 onEvnetBackground:如果使用onEventBackgrond作為訂閱函數,那麼如果事件是在UI線程中發布出來的,那麼onEventBackground就會在子線程中運行,如果事件本來就是子線程中發布出來的,那麼onEventBackground函數直接在該子線程中執行。
4 onEventAsync:使用這個函數作為訂閱函數,那麼無論事件在哪個線程發布,都會創建新的子線程在執行onEventAsync.

源碼分析

我們分析源碼先從程序的入口開始,EventBus的入口是注冊時開始, 即

EventBus.getDefault().register(this);

EventBus.getDefault()返回的是一個EventBus的實例,是一個單例模式:

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

這裡實現單例采用了雙重校驗模式,提高的效率,也能防止並發可能產生的問題,同時defaultInstance采用了volatile 來修飾,避免了編譯期相關值的修改。

然後看看注冊方法:register

 public void register(Object subscriber) {
 //這裡subscriber一般是this,在上篇文章中就指MainActivity.this
        Class subscriberClass = subscriber.getClass();
        List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);//findSubscriberMethods()顧名思義就是通過類名去找類中的方法
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

我們再來看看findSubscriberMethods(subscriberClass)方法:

 List findSubscriberMethods(Class subscriberClass) {
        List subscriberMethods = METHOD_CACHE.get(subscriberClass);
        //其中METHOD_CACHE是一個緩存的ConcurrentHashMap,Map, List> METHOD_CACHE = new ConcurrentHashMap<>();該Map以訂閱的類名為key,訂閱函數列表為value;

        if (subscriberMethods != null) {
            return subscriberMethods;
            //首先判斷緩存中是否已經包含該訂閱類(即是否已經訂閱過);
        }
//第一次進來subscriberMethods肯定是Null
        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            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.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }

執行findUsingReflection(subscriberClass)後,會去執行findUsingReflectionInSingleClass方法,我們再看看####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);//獲取添加了@Subscribe注解的函數
                    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");
            }
        }
    }

這樣就完成了EventBus訂閱者的注冊;

我們回到register方法:

for (SubscriberMethod subscriberMethod : subscriberMethods) {  
           subscribe(subscriber, subscriberMethod, sticky, priority);  
       } 

對每一個訂閱方法,對其調用subscribe方法,進入該方法看看到底干了什麼:

subscribe方法:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {  
        subscribed = true;  
        //從訂閱方法中拿到訂閱事件的類型  
        Class eventType = subscriberMethod.eventType;  
        //通過訂閱事件類型,找到所有的訂閱(Subscription),訂閱中包含了訂閱者,訂閱方法  
        CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);  
        //創建一個新的訂閱  
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);  
        //將新建的訂閱加入到這個事件類型對應的所有訂閱列表  
        if (subscriptions == null) {  
            //如果該事件目前沒有訂閱列表,那麼創建並加入該訂閱  
            subscriptions = new CopyOnWriteArrayList();  
            subscriptionsByEventType.put(eventType, subscriptions);  
        } else {  
            //如果有訂閱列表,檢查是否已經加入過  
            for (Subscription subscription : subscriptions) {  
                if (subscription.equals(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 || newSubscription.priority > subscriptions.get(i).priority) {  
                subscriptions.add(i, newSubscription);  
                break;  
            }  
        }  
        //將這個訂閱事件加入到訂閱者的訂閱事件列表中  
        List> subscribedEvents = typesBySubscriber.get(subscriber);  
        if (subscribedEvents == null) {  
            subscribedEvents = new ArrayList>();  
            typesBySubscriber.put(subscriber, subscribedEvents);  
        }  
        subscribedEvents.add(eventType);  
        //這個是對粘性事件的,暫時不討論  
        if (sticky) {  
            Object stickyEvent;  
            synchronized (stickyEvents) {  
                stickyEvent = stickyEvents.get(eventType);  
            }  
            if (stickyEvent != null) {  
                postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());  
            }  
        }  
    } 

好了,到這裡差不多register方法分析完了,大致流程就是這樣的,我們總結一下:
1、找到被注冊者中所有的訂閱方法。
2、依次遍歷訂閱方法,找到EventBus中eventType對應的訂閱列表,然後根據當前訂閱者和訂閱方法創建一個新的訂閱加入到訂閱列表
3、找到EvnetBus中subscriber訂閱的事件列表,將eventType加入到這個事件列表。

所以對於任何一個訂閱者,我們可以找到它的 訂閱事件類型列表,通過這個訂閱事件類型,可以找到在訂閱者中的訂閱函數。

register分析完了就分析一下post吧,這個分析完了,EventBus的原理差不多也完了…

post(Object event)

public void post(Object event) {  
        //這個EventBus中只有一個,差不多是個單例吧,具體不用細究  
        PostingThreadState postingState = currentPostingThreadState.get();  
        List

ost裡面沒有什麼具體邏輯,它的功能主要是調用postSingleEvent完成的,進入到這個函數看看吧:

postSingleEvent(Object event, PostingThreadState postingState)

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {  
       Class eventClass = event.getClass();  
    //找到eventClass對應的事件,包含父類對應的事件和接口對應的事件  
       List> eventTypes = findEventTypes(eventClass);  
       boolean subscriptionFound = false;  
       int countTypes = eventTypes.size();  
       for (int h = 0; h < countTypes; h++) {  
           Class clazz = eventTypes.get(h);  
           CopyOnWriteArrayList subscriptions;  
           synchronized (this) {  
            //找到訂閱事件對應的訂閱,這個是通過register加入的(還記得嗎....)  
               subscriptions = subscriptionsByEventType.get(clazz);  
           }  
           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;  
                   }  
               }  
               subscriptionFound = true;  
           }  
       }  
    //如果沒有訂閱發現,那麼會Post一個NoSubscriberEvent事件  
       if (!subscriptionFound) {  
           Log.d(TAG, "No subscribers registered for event " + eventClass);  
           if (eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) {  
               post(new NoSubscriberEvent(this, event));  
           }  
       }  
   }  

這個方法有個核心方法 postToSubscription方法,進入看看吧

postToSubscription

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {  
        //第一個參數就是傳入的訂閱,第二個參數就是對於的分發事件,第三個參數非常關鍵:是否在主線程  
        switch (subscription.subscriberMethod.threadMode) {  
        //這個threadMode是怎麼傳入的,仔細想想?是不是根據onEvent,onEventMainThread,onEventBackground,onEventAsync決定的?  
        case PostThread:  
            //直接在本線程中調用訂閱函數  
            invokeSubscriber(subscription, event);  
            break;  
        case MainThread:  
            if (isMainThread) {  
                //如果直接在主線程,那麼直接在本現場中調用訂閱函數  
                invokeSubscriber(subscription, event);  
            } else {  
                //如果不在主線程,那麼通過handler實現在主線程中執行,具體我就不跟蹤了  
                mainThreadPoster.enqueue(subscription, event);  
            }  
            break;  
        case BackgroundThread:  
            if (isMainThread) {  
                //如果主線程,創建一個runnable丟入線程池中  
                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);  
        }  
    } 

小結

直接反射調用;也就是說在當前的線程直接調用該方法;
(1)case MainThread:
首先去判斷當前如果是UI線程,則直接調用;否則: mainThreadPoster.enqueue(subscription, event);把當前的方法加入到隊列,然後直接通過handler去發送一個消息,在handler的handleMessage中,去執行我們的方法。說白了就是通過Handler去發送消息,然後執行的。
(2)case BackgroundThread:
如果當前非UI線程,則直接調用;如果是UI線程,則將任務加入到後台的一個隊列,最終由Eventbus中的一個線程池去調用
executorService = Executors.newCachedThreadPool();。
(3) case Async:將任務加入到後台的一個隊列,最終由Eventbus中的一個線程池去調用;線程池與BackgroundThread用的是同一個。
這麼說BackgroundThread和Async有什麼區別呢?
BackgroundThread中的任務,一個接著一個去調用,中間使用了一個布爾型變量handlerActive進行的控制。
Async則會動態控制並發。

因此,從完整的源碼分析來看:register就是把當前類中匹配的方法,存入一個map,而post會根據實參(訂閱事件的類型(由類名決定))去map查找進行反射調用。其實如果不用發布者,訂閱者,事件,總線這幾個詞或許更好理解,以後大家提起EventBus,也可以說,就是在一個單例內部維持著一個map對象存儲了一堆的方法;post無非就是根據參數去查找方法,進行反射調用。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved