Android開源框架源碼鑑賞:EventBus

做者:郭孝星java

校對:郭孝星android

關於項目git

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

文章目錄跨域

  • 一 註冊訂閱者
  • 二 發佈事件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.async

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

咱們先來看一下EventBus的源碼結構,以下所示:ide

主要包含了兩個部分:

  • 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;
        }
    }
}
複製代碼

總體的流程仍是比較簡單的,以下所示:

  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核心的實現,仍是比較簡單的。

相關文章
相關標籤/搜索