Eventbus 使用方法和原理分析

對於 Eventbus ,相信不少 Android 小夥伴都用到過。java

一、建立事件實體類

所謂的事件實體類,就是傳遞的事件,一個組件向另外一個組件發送的信息能夠儲存在一個類中,該類就是一個事件,會被 EventBus 發送給訂閱者。新建 MessageEvent.java:數據庫

public class MessageEvent {

    private String message;

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

    public String getMessage(){
        return message;
    }
}

二、註冊和反註冊

經過如下代碼:緩存

EventBus.getDefault().register(this);網絡

便可將當前類註冊,成爲訂閱者,即對應觀察者模式的「觀察者」,一旦有事件發送過來,該觀察者就會接收到匹配的事件。一般,在類的初始化時便進行註冊,若是是 Activity 則在的 onCreate()方法內進行註冊。多線程

當訂閱者再也不須要接受事件的時候,咱們須要解除註冊,釋放內存:併發

EventBus.getDefault().unregister(this);

三、添加訂閱方法

回想觀察者模式,觀察者有着一個 update() 方法,在接收到事件的時候會調用該 update() 方法,這個方法就是一個訂閱方法。在EventBus 3.0中,聲明一個訂閱方法須要用到 @Subscribe 註解,所以在訂閱者類中添加一個有着 @Subscribe 註解的方法便可,方法名字可自定義,並且必須是public權限,其方法參數有且只能有一個,另外類型必須爲第一步定義好的事件類型(好比上面的 MessageEvent),以下所示:app

@Subscribe 
public void onEvent(AnyEventType event) {
    /* Do something */
}

完整的 MainActivity.java 文件以下所示:異步

public class MainActivity extends Activity {

    private TextView textView;
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //註冊成爲訂閱者
        EventBus.getDefault().register(this);
        textView = (TextView) findViewById(R.id.tv_text);
        button = (Button) findViewById(R.id.secondActivityBtn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }
    
    //訂閱方法,當接收到事件的時候,會調用該方法
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEvent(MessageEvent messageEvent){
        Log.d("cylog","receive it");
        textView.setText(messageEvent.getMessage());
        Toast.makeText(MainActivity.this, messageEvent.getMessage(), Toast.LENGTH_SHORT).show();
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解除註冊
        EventBus.getDefault().unregister(this);
    }
}

四、發送事件

與觀察者模式對應的,當有事件發生,須要通知觀察者的時候,被觀察者會調用 notifyObservers() 方法來通知全部已經註冊的觀察者,在 EventBus 中,對觀察者模式底層進行了封裝,咱們只須要調用如下代碼就能把事件發送出去:async

EventBus.getDefault().post(EventType eventType);

上述 EventType 就是第一步定義的事件類型。ide

五、threadMode

POSTING

默認的模式,開銷最小的模式,由於聲明爲 POSTING 的訂閱者會在發佈的同一個線程調用,發佈者在主線程那麼訂閱者也就在主線程,反之亦,避免了線程切換,若是不肯定是否有耗時操做,謹慎使用,由於多是在主線程發佈。

MAIN
主線程調用,視發佈線程不一樣處理不一樣,若是發佈者在主線程那麼直接調用(非阻塞式),若是發佈者不在主線程那麼阻塞式調用,這句話怎麼理解呢,看下面的 Log 比較清晰的理解
主線程(阻塞式):

   Log.d(TAG, "run : 1");
    EventBus.getDefault().post(text);//發送一個事件
    Log.d(TAG, "run : 2");
    EventBus.getDefault().post(text);//發送一個事件
                
                
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent1(String text) {
        Log.d(TAG, "onMessageEvent1 : ");
    }

日誌輸出

: run : 1
: onMessageEvent1 :
: run : 2
: onMessageEvent1 :

非主線程(非阻塞式):

   final String text = "長江長江我是黃河";
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "run : 1");
                EventBus.getDefault().post(text);//發送一個事件
                Log.d(TAG, "run : 2");
                EventBus.getDefault().post(text);//發送一個事件
            }
        }).start();

日誌輸出:

run : 1
run : 2
onMessageEvent1 :
onMessageEvent1 :

MAIN_ORDERED
和MAIN差很少,主線程調用,和 MAIN 不一樣的是他保證了 post 是非阻塞式的(默認走 MAIN 的非主線程的邏輯,因此能夠作到非阻塞)

BACKGROUND
在子線程調用,若是發佈在子線程那麼直接在發佈線程調用,若是發佈在主線程那麼將開啓一個子線程來調用,這個子線程是阻塞式的,按順序交付全部事件,因此也不適合作耗時任務,由於多個事件共用這一個後臺線程

ASYNC
在子線程調用,老是開啓一個新的線程來調用,適用於作耗時任務,好比數據庫操做,網絡請求等,不適合作計算任務,會致使開啓大量線程

六、原理分析:

這裏並不打算分析具體的源碼邏輯,而是我的在看了源碼以後的筆記。幫助本身更好的理解 eventbus 的實現原理,梳理清楚每一條邏輯。

想看源碼分析能夠參考這篇文章:

Android EventBus3.1.1從使用到源碼解析

  /** 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;

這裏在生成單例的時候使用了雙重檢驗,避免多線程過程當中重複建立。其次這裏使用到了,類縮而非對象鎖。

對象鎖是用來控制實例方法之間的同步,而類鎖是用來控制靜態方法(或者靜態變量互斥體)之間的同步的。

類鎖只是一個概念上的東西,並非真實存在的,他只是用來幫助咱們理解鎖定實例方法和靜態方法的區別的。
java 類可能會有不少對象,可是隻有一個 Class (字節碼)對象,也就是說類的不一樣實例之間共享該類的 Class 對象。Class 對象其實也僅僅是 1 個 java 對象,只不過有點特殊而已。
因爲每一個 java 對象都有1個互斥鎖,而類的靜態方法是須要 Class 對象。因此所謂的類鎖,只不過是 Class 對象的鎖而已。

6.2 以 class 爲 key 來存儲方法信息

例如一個 activity 裏面註冊了一個 eventbus。咱們每次進入activity 的時候,都會把 this 傳進去,而後走一遍註冊邏輯,因此你以爲內部是如何存儲註冊對象的呢?是按照 this 來的?

其實內部是經過 class 來存儲的。

public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        //subscriberMethods返回的是subscriber這個類中全部的訂閱方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);//分類保存後面有分析
            }
        }
}

經過上面代碼,咱們能夠看到會去獲取當前對象的類名,而後在經過反射的形式獲取該類的全部方法,從中找到訂閱方法,方便之後發佈消息。

若是採用對象保存,每次進入,都是一個不一樣的對象,而後經過對象再去獲取方法信息,這樣作太費力,也太耗內存了。經過類名的方式,只是第一次比較耗時,後面就方便了。

添加新方法,或者新的事件的時候,會從新編譯,從新獲取一遍新的數據的。

PS : 註冊自己仍是掛在對象上的,當對象銷燬的時候,也會進行註銷。

6.3 如何保存訂閱同一事件的不一樣類

根據事件類型,將註冊同一個事件類型的 class 放在一塊兒。

// Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;//訂閱函數參數類型 
        //這一步很簡單就是在構造函數中記錄下訂閱者和訂閱方法
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //CopyOnWriteArrayList是java.util包下的,他使用了寫時複製的方法來實現,其效率並不高,但能夠保證在多線程環境下最終(強調是最終)數據的一致性
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);//subscriptionsByEventType能夠根據參數類型來獲取到訂閱事件
        //在操做第一個訂閱事件時確定是==null的
        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;
            }
        }
        //typesBySubscriber能夠根據訂閱者來獲取到全部的訂閱方法參數類型
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);
 
        if (subscriberMethod.sticky) {//粘性事件的處理邏輯在最後再分析,由於其內容包含了post流程
            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);
            }
        }
    }

對 subscriptionsByEventType  typesBySubscriber 完成數據初始化,subscriptionsByEventType 根據參數類型存儲訂閱者和訂閱方法,typesBySubscriber 根據訂閱者存儲了全部的參數類型,subscriptionsByEventType 主要是 post 時使用,由於其存儲了訂閱者和訂閱事件這兩個參數在反射時要用到,typesBySubscriber 在反註冊時能夠根據訂閱者獲取到存儲的事件類型就能夠從 subscriptionsByEventType 中獲取到對應的訂閱者和訂閱方法釋放資源,還能夠用來判斷是否註冊。

 6.4 如何找到訂閱方法

從緩存中獲取訂閱方法列表,若是緩存中不存在則經過反射獲取到訂閱者全部的函數,遍歷再經過權限修飾符,參數長度(只容許一個參數),註解(@Subscribe) 來判斷是不是具有成爲訂閱函數的前提,具有則構建一個 SubscriberMethod (訂閱方法,其至關於一個數據實體類,包含方法,threadmode,參數類型,優先級,是否粘性事件這些參數),循環結束訂閱函數列表構建完成添加進入緩存

 

6.5 如何在子線程發佈消息後在主線程處理

HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
        super(looper);
        this.eventBus = eventBus;
        this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
        queue = new PendingPostQueue();//採用獨立隊列,與backgroundPoster一致
    }

能夠看到,HandlerPoster 自身攜帶一個 looper,主要傳入 mainLooper,就能夠處理主線程的事物了。

 

6.6 是如何調用訂閱方法的

經過反射的形式調用。

void invokeSubscriber(Subscription subscription, Object event) {
        try {
        //這裏最後說明一下subscription中包含了訂閱者和訂閱方法 event是Post的參數 這裏經過反射直接調用訂閱者的訂閱方法 完成本次通訊
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

6.7 如何肯定優先級

每次添加的時候,就會根據優先級來添加,優先級越高的,添加在最前面。 

        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //CopyOnWriteArrayList是java.util包下的,他使用了寫時複製的方法來實現,其效率並不高,但能夠保證在多線程環境下最終(強調是最終)數據的一致性
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);//subscriptionsByEventType能夠根據參數類型來獲取到訂閱事件
        //在操做第一個訂閱事件時確定是==null的
        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;
            }
}

6.8 既然存在多線程,是如何保存數據的?

public void post(Object event) {
 //currentPostingThreadState是ThreadLocal,ThreadLocal能夠解決多線程的併發訪問問題,他會爲每個線程提供一個獨立的變量副本,能夠隔離多個線程對數據的訪問衝突
        PostingThreadState postingState = currentPostingThreadState.get();
...... }

ThreadLocal 的是一個本地線程副本變量工具類。主要用於將私有線程和該線程存放的副本對象作一個映射,各個線程之間的變量互不干擾,在高併發場景下,能夠實現無狀態的調用,特別適用於各個線程依賴不通的變量值完成操做的場景。

final static class PostingThreadState {
    // 經過post方法參數傳入的事件集合
    final List<Object> eventQueue = new ArrayList<Object>(); 
    boolean isPosting; // 是否正在執行postSingleEvent()方法
    boolean isMainThread;
    Subscription subscription;
    Object event;
    boolean canceled;
    }
 Subscription(Object subscriber, SubscriberMethod subscriberMethod) {
        this.subscriber = subscriber;
        this.subscriberMethod = subscriberMethod;
        active = true;
    }

 訂閱方法的信息:

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

能夠發現,基本上全部的信息都被包含在 PostingThreadState 中了,這樣在 post 的方法中就不要額外依賴其餘數據了。

6.9  發送消息邏輯過程是怎樣的

post () 發送消息,首先得獲取當前線程的一個發送隊列。從隊列裏面依次取出 event ,根據 event.getClass()來獲取保存的訂閱者。

synchronized (this) {
        //這裏根據咱們註冊的時候總結 這個容器中裝的是訂閱者和訂閱方法,如今根據發送事件的類型來獲取到對應的訂閱者和訂閱方法這些參數是反射必需要用到的
            subscriptions = subscriptionsByEventType.get(eventClass);
        }

找到訂閱者之後,依次循環,對每一個訂閱者進行處理:

//這裏根據是否在主線程和threadmode來判斷
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeSubscriber(subscription, event);//post在什麼線程就直接調用 不須要切換線程
                break;
            case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);//若是Post在主線程直接調用,反之經過handler來切換到主線程再調用反射
                }
                break;
            case MAIN_ORDERED:
                if (mainThreadPoster != null) {//默認走這裏的邏輯和MAIN一致 事件排隊等待調用,非阻塞式
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);//若是在主線程則開啓一條線程 事件將排隊在同一條線程執行
                } else {//若是post在子線程直接在Post線程調用
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:
                asyncPoster.enqueue(subscription, event);//老是開啓線程來調用
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

調用 enqueue 後,用於切換線程來處理事件,最後仍是會經過反射的形式進行調用。

 

6.10 黏性事件如何保存和發送

主要使用場景是:當訂閱者還沒有建立,先調用 EventBus.getDefault().postSticky() 方法發送一個 sticky 事件,該事件會被 stickyEvents 緩存起來,當訂閱該事件的類調用 register() 方法時,最終會將保存的事件所有發給新註冊的訂閱者一份,所以,新的訂閱者一樣能夠收到該事。

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    //獲取subsrciberMethod傳遞的自定義EventType參數的運行時的類
    Class eventType = subscriberMethod.eventType;
    //Subscription用於綁定subscriber和sucriberMethod,一個訂閱者能夠有多個subscriberMethod
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    //根據EventType的運行時類取到該類全部的subscriptioins,subscriptionsByEventType是HashMap中的key
    CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
         subscriptions = new CopyOnWriteArrayList<>();
         //若根據EventType找不到subscriptions,則eventType做key,subscriptions做value添加到subscriptionByEventType中。
         subscriptionsByEventType.put(eventType, subscriptions);
    } else {
         if (subscriptions.contains(newSubscription)) {
         //已經存在newSubscription,拋出異常該訂閱者已經註冊,不可重複註冊同一個subscriber
             throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                     + eventType);
         }
    }

    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        //循環subscriptions,根據標記優先級的priority從高到低,將新的subscription插入到subscriptions中
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }
    //typesBySubscriber是一個HashMap,根據subscriber作key,獲取該subscriber對應的全部的訂閱事件的類型
    List> subscribedEvents = typesBySubscriber.get(subscriber);
      if (subscribedEvents == null) {
          subscribedEvents = new ArrayList<>();
          //該訂閱者以前的訂閱事件類型列表爲空,則將當前訂閱類型添加到typesBySubscriber中
          typesBySubscriber.put(subscriber, subscribedEvents);
      }
    subscribedEvents.add(eventType);
    //若是該方法被標識爲sticky事件
 if (subscriberMethod.sticky) { if (eventInheritance) { eventInheritance標識是否考慮EventType的類層次結構
              //循環全部的sticky黏性事件
              Set, Object>> entries = stickyEvents.entrySet();
              for (Map.Entry, Object> entry : entries) {
                  Class candidateEventType = entry.getKey();
                  //若是當前事件是其餘事件的同類型的或者是他們的父類
                  if (eventType.isAssignableFrom(candidateEventType)) {
                     Object stickyEvent = entry.getValue();
                     heckPostStickyEventToSubscription(newSubscription, stickyEvent);
                  }
              }
         } else {
             Object stickyEvent = stickyEvents.get(eventType);
             checkPostStickyEventToSubscription(newSubscription, stickyEvent);
         }
    }
}

從上面咱們能夠知道,最後都會調用一個方法:

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if (stickyEvent != null) {
            // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
            // --> Strange corner case, which we don't take care of here.
            postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
        }
    }

最後,也會調用到全部事件不論是不是黏性都會走的一個方法:

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
       //根據@subscriber中threadMode進行區分,POSTING爲當前線程執行,
       //MAIN爲主線程,BACKGROUND爲子進程,ASYNC爲異步執行。
       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 的思惟導圖,幫助大家更好的去理解 eventbus。 

 

相關文章
相關標籤/搜索