Android開源框架原始碼鑑賞:EventBus

蘇策發表於2018-03-27

作者:郭孝星

校對:郭孝星

關於專案

Android Open Framework analysis專案主要用來分析Android平臺主流開源框架的原始碼與原理實現。

文章目錄

  • 一 註冊訂閱者
  • 二 釋出事件Event
  • 三 接收事件Event
  • 四 取消註冊訂閱者

EventBus是一個Android/Java平臺基於訂閱與釋出的通訊框架,可以用於Activities, Fragments, Threads, Services等元件的通訊,也可以用於多執行緒通訊。

EventBus在應用裡的應用是十分廣泛的,那麼除了EventBus這種應用通訊方式外,還有哪些手段呢??

  • BroadcastReceiver/LocalBroadcastReceiver:跨域廣播和局域廣播,跨域廣播可以用來做跨程式通訊。局域廣播也是基於Handler實現,可以用來在應用內通訊。
  • Handler:這個方式的弊端在於通訊訊息難以管理。
  • 介面回撥:介面回撥的好處是比較清晰明顯,但是如果涉及到大量頁面的跳轉或者通訊場景比較複雜,這種方式就變得難以維護,耦合較高。

相當於這些方式EventBus的優點在於使用簡單,事件的訂閱者和釋出者解耦,但是它也有有自己的問題,例如大量Event類的管理,這個我們後續會說。

Event bus for Android and Java that simplifies communication between Activities, Fragments, Threads, Services, etc. Less code, better quality.

  • 官方網站:https://github.com/greenrobot/EventBus
  • 原始碼版本:3.1.1

我們先來看一下EventBus的原始碼結構,如下所示:

Android開源框架原始碼鑑賞:EventBus

主要包含了兩個部分:

  • eventbus:核心庫。
  • eventbus-annotation-processor:註解處理部分。

我們先來一個簡單的Demo,從Demo入手分析事件的訂閱和釋出流程。

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

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

        findViewById(R.id.btn_post_event).setOnClickListener(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // 訂閱事件
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 取消訂閱s事件
        EventBus.getDefault().unregister(this);
    }

    // 接收事件Event
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEvent(Event event) {
        Toast.makeText(this, event.getMessage(), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_post_event:
                // 釋出事件Event
                EventBus.getDefault().post(new Event("Event Message"));
                break;
        }
    }
}
複製程式碼

整體的流程還是比較簡單的,如下所示:

Android開源框架原始碼鑑賞:EventBus
  1. 註冊訂閱者。
  2. 釋出事件Event。
  3. 接收事件Event。
  4. 取消註冊訂閱者。

我們具體來看一下。

一 註冊訂閱者

訂閱事件是通過以下方法來完成的:

EventBus.getDefault().register(this);
複製程式碼

getDefault()用來獲取EventBus例項,當然你也可以通過EventBusBuilder自己構建例項。

public class EventBus {
    
    public void register(Object subscriber) {
        // 1. 獲取訂閱者的類名。
        Class<?> subscriberClass = subscriber.getClass();
        // 2. 查詢當前訂閱者的所有響應函式。
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            // 3. 迴圈每個事件響應函式
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
 
}
複製程式碼

SubscriberMethod用來描述onEvent()這些方法的資訊,包含方法名、執行緒、Class型別、優先順序、是否是粘性事件。

整個函式的呼叫流程所示:

  1. 獲取訂閱者的類名。
  2. 查詢當前訂閱者的所有響應函式。
  3. 迴圈每個事件響應函式

接著呼叫subscribe()進行事件註冊,如下所示:

public class EventBus {
     
     // 訂閱者佇列
     private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
     // 後續準備取消的事件佇列
     private final Map<Object, List<Class<?>>> typesBySubscriber;
     // 粘性事件佇列
     private final Map<Class<?>, Object> stickyEvents;
    
      private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
          // 事件型別(xxxEvent)
          Class<?> eventType = subscriberMethod.eventType;
          Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
          // 1. 獲取該事件型別的所有訂閱者資訊。
          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();
          // 2. 按照事件優先順序將其插入訂閱者列表中。
          for (int i = 0; i <= size; i++) {
              if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                  subscriptions.add(i, newSubscription);
                  break;
              }
          }
  
          // 3. 得到當前訂閱者訂閱的所有事件佇列,存放在typesBySubscriber中,用於後續取消事件訂閱。
          List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
          if (subscribedEvents == null) {
              subscribedEvents = new ArrayList<>();
              typesBySubscriber.put(subscriber, subscribedEvents);
          }
          subscribedEvents.add(eventType);
  
          // 4. 是否是粘性事件,如果是粘性事件,則從stickyEvents佇列中取出最後一個該型別的事件傳送給訂閱者。
          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);
              }
          }
      }  
}
複製程式碼

Subscription包含了訂閱者subscriber和訂閱函式subscriberMethod兩個資訊。

該方法的呼叫流程如下所示:

  1. 獲取該事件型別的所有訂閱者資訊。
  2. 按照事件優先順序將其插入訂閱者列表中。
  3. 得到當前訂閱者訂閱的所有事件佇列,存放在typesBySubscriber中,用於後續取消事件訂閱。
  4. 是否是粘性事件,如果是粘性事件,則從stickyEvents佇列中取出最後一個該型別的事件傳送給訂閱者。

二 釋出事件Event

傳送事件Event是通過以下方法完成的,如下所示:

EventBus.getDefault().post(new Event("Event Message"));
複製程式碼
public class EventBus {
    
    public void post(Object event) {
        // 1. 獲取當前執行緒的PostingThreadState物件,該物件包含事件佇列,儲存在ThreadLocal中。
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        // 2. 將當前事件加入到該執行緒的事件佇列中。
        eventQueue.add(event);

        // 3. 判斷事件是否在分發中。如果沒有則遍歷事件佇列進行實際分發。
        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()) {
                    // 4. 進行事件分發。
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    } 
}
複製程式碼

PostingThreadState用來描述傳送事件的執行緒的相關狀態資訊,包含事件佇列,是否是主執行緒、訂閱者、事件Event等資訊。

  1. 獲取當前執行緒的PostingThreadState物件,該物件包含事件佇列,儲存在ThreadLocal中。
  2. 將當前事件加入到該執行緒的事件佇列中。
  3. 判斷事件是否在分發中。如果沒有則遍歷事件佇列進行實際分發。
  4. 進行事件分發。

然後呼叫postSingleEvent()進行事件分發。

public class EventBus {
    
    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        // 1. 如果事件允許繼承,則查詢該事件型別的所有父類和介面,依次進行迴圈。
        if (eventInheritance) {
            
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            for (int h = 0; h < countTypes; h++) {
                Class<?> clazz = eventTypes.get(h);
                // 2. 查詢該事件的所有訂閱者。
                subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
            }
        } else {
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        if (!subscriptionFound) {
            if (logNoSubscriberMessages) {
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }
}
複製程式碼

該方法主要做了以下事情:

  1. 如果事件允許繼承,則查詢該事件型別的所有父類和介面,依次進行迴圈。
  2. 查詢該事件的所有訂閱者。

然後呼叫postSingleEventForEventType()方法查詢當前事件的所有訂閱者,如下所示:

public class EventBus {
    
    private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            // 1. 獲取當前事件的所有訂閱者。
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null && !subscriptions.isEmpty()) {
            // 2. 遍歷所有訂閱者。
            for (Subscription subscription : subscriptions) {
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false;
                try {
                    // 3. 根據訂閱者所線上程,呼叫事件響應函式onEvent()。
                    postToSubscription(subscription, event, postingState.isMainThread);
                    aborted = postingState.canceled;
                } finally {
                    postingState.event = null;
                    postingState.subscription = null;
                    postingState.canceled = false;
                }
                if (aborted) {
                    break;
                }
            }
            return true;
        }
        return false;
    }    
}
複製程式碼

該方法主要做了以下事情:

  1. 獲取當前事件的所有訂閱者。
  2. 遍歷所有訂閱者。
  3. 根據訂閱者所線上程,呼叫事件響應函式onEvent()。

呼叫postToSubscription()方法根據訂閱者所線上程,呼叫事件響應函式onEvent(),這便涉及到接收事件Event的處理了,我們接著來看。

三 接收事件Event

public class EventBus {
    
     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 MAIN_ORDERED:
                   if (mainThreadPoster != null) {
                       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 {
                       invokeSubscriber(subscription, event);
                   }
                   break;
               case ASYNC:
                   asyncPoster.enqueue(subscription, event);
                   break;
               default:
                   throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
           }
       }    
}
複製程式碼
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(Event event) {
    Toast.makeText(this, event.getMessage(), Toast.LENGTH_SHORT).show();
}
複製程式碼

如上所示,onEvent函式上是可以加Subscribe註解了,該註解標明瞭onEvent()函式在哪個執行緒執行。主要有以下幾個執行緒:

  • PostThread:預設的 ThreadMode,表示在執行 Post 操作的執行緒直接呼叫訂閱者的事件響應方法,不論該執行緒是否為主執行緒(UI 執行緒)。當該執行緒為主執行緒 時,響應方法中不能有耗時操作,否則有卡主執行緒的風險。適用場景:對於是否在主執行緒執行無要求,但若 Post 執行緒為主執行緒,不能耗時的操作;
  • MainThread:在主執行緒中執行響應方法。如果釋出執行緒就是主執行緒,則直接呼叫訂閱者的事件響應方法,否則通過主執行緒的 Handler 傳送訊息在主執行緒中處理— —呼叫訂閱者的事件響應函式。顯然,MainThread類的方法也不能有耗時操作,以避免卡主執行緒。適用場景:必須在主執行緒執行的操作;
  • BackgroundThread:在後臺執行緒中執行響應方法。如果釋出執行緒不是主執行緒,則直接呼叫訂閱者的事件響應函式,否則啟動唯一的後臺執行緒去處理。由於後臺執行緒 是唯一的,當事件超過一個的時候,它們會被放在佇列中依次執行,因此該類響應方法雖然沒有PostThread類和MainThread類方法對效能敏感,但最好不要有重度耗 時的操作或太頻繁的輕度耗時操作,以造成其他操作等待。適用場景:操作輕微耗時且不會過於頻繁,即一般的耗時操作都可以放在這裡;
  • Async:不論釋出執行緒是否為主執行緒,都使用一個空閒執行緒來處理。和BackgroundThread不同的是,Async類的所有執行緒是相互獨立的,因此不會出現卡執行緒的問 題。適用場景:長耗時操作,例如網路訪問。

這裡我執行緒執行和EventBus的成員變數對應,它們都實現了Runnable與Poster介面,Poster介面定義了事件排隊功能,這些本質上都是個Runnable,放線上程池裡執行,如下所示:

private final Poster mainThreadPoster; private final BackgroundPoster backgroundPoster; private final AsyncPoster asyncPoster; private final SubscriberMethodFinder subscriberMethodFinder; private final ExecutorService executorService;

四 取消註冊訂閱者

取消註冊訂閱者呼叫的是以下方法:

EventBus.getDefault().unregister(this);
複製程式碼

具體如下所示:

public class EventBus {
    
    public synchronized void unregister(Object subscriber) {
        
        // 1. 獲取當前訂閱者訂閱的所有事件型別。
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            // 2. 遍歷事件佇列,解除事件註冊。
            for (Class<?> eventType : subscribedTypes) {
                unsubscribeByEventType(subscriber, eventType);
            }
            // 3. 移除事件訂閱者。
            typesBySubscriber.remove(subscriber);
        } else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

}
複製程式碼

取消註冊訂閱者的流程也十分簡單,如下所示:

  1. 獲取當前訂閱者訂閱的所有事件型別。
  2. 遍歷事件佇列,解除事件註冊。
  3. 移除事件訂閱者。

當猴呼叫unsubscribeByEventType()移除訂閱者,如下所示:

public class EventBus {
    
     private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
         // 1. 獲取所有訂閱者資訊。
         List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
         if (subscriptions != null) {
             // 2. 遍歷訂閱者
             int size = subscriptions.size();
             for (int i = 0; i < size; i++) {
                 Subscription subscription = subscriptions.get(i);
                 // 3. 移除該訂閱物件。
                 if (subscription.subscriber == subscriber) {
                     subscription.active = false;
                     subscriptions.remove(i);
                     i--;
                     size--;
                 }
             }
         }
     }
}
複製程式碼

以上便是EventBus核心的實現,還是比較簡單的。

相關文章