手寫一個(gè)簡(jiǎn)化版的EventBus

EventBus相信很多人都很熟悉,雖然現(xiàn)在谷歌官方出了JetPack來替代,但EventBus的一些設(shè)計(jì)思路還是值得借鑒的。下面就來寫一個(gè)簡(jiǎn)單的EventBus案例

其實(shí)EventBus原理并不難,就是維護(hù)了幾個(gè)數(shù)組,然后根據(jù)對(duì)應(yīng)的key找到對(duì)應(yīng)的注冊(cè)對(duì)象,通過放射的方式調(diào)用對(duì)應(yīng)的方法。

EventBus3.0之前和之后有比較大的區(qū)別,最大的差別在于3.0之后通過apt再編譯期間生成一個(gè)引用對(duì)象,這樣做很大程度上提高了性能。

最簡(jiǎn)單的使用
//注冊(cè)事件
EventBus.getDefault().register(this);

//注冊(cè)方法
@Subscribe
public void event(BaseEventBusBeaan message) {
  LogUtils.d("EventBusActivity event");
}

//發(fā)送事件
EventBus.getDefault().post(new BaseEventBusBeaan("123", new Bundle()));

//回收
EventBus.getDefault().unregister(this);

post流程

首先我們應(yīng)該理清我們的需求,我們需要的是在 post一個(gè)對(duì)象出去的時(shí)候,所有注冊(cè)監(jiān)聽了這個(gè)對(duì)象的類都能接收到這個(gè)通知,于是這里應(yīng)該需要一個(gè)數(shù)組來存儲(chǔ)數(shù)據(jù)。

//post出去的對(duì)象為key,一個(gè)注冊(cè)者Subscription的list作為value
private Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

//這個(gè)Subscription包括下面參數(shù)
public class Subscription {

    final Object subscriber;  //activity或者fragment
    final SubscriberMethod subscriberMethod;  

    public Subscription(Object subscriber, SubscriberMethod subscriberMethod) {
        this.subscriber = subscriber;
        this.subscriberMethod = subscriberMethod;
    }
}

public class SubscriberMethod {

    private String methodName; // 訂閱方法名
    private Method method; // 訂閱方法,用于最后的自動(dòng)執(zhí)行訂閱方法
    private ThreadMode threadMode; // 線程模式
    private Class<?> eventType; // 事件對(duì)象Class,如:UserInfo.class
}

有了subscriptionsByEventType之后,我們就可以根據(jù)post()的發(fā)送的事件來查找所有注冊(cè)者,再遍歷list,逐一反射。

public void post(Object event) {
  postSingleEventForEventType(event, event.getClass());
}

//遍歷所有的訂閱者,發(fā)送對(duì)應(yīng)的事件
private void postSingleEventForEventType(Object event, Class<?> eventClass) {
  CopyOnWriteArrayList<Subscription> subscriptions;

  synchronized (this) {
    subscriptions = subscriptionsByEventType.get(eventClass);
  }
  if (subscriptions != null && !subscriptions.isEmpty()) {
    for (Subscription subscription : subscriptions) {
        invokeSubscriber(subscription, event);
    }
  }
}

//這里暫時(shí)不考慮線程的問題
private void invokeSubscriber(Subscription subscription, Object event) {
  try {
    subscription.subscriberMethod.getMethod().invoke(subscription.subscriber, event);
  } catch (Exception e) {
    e.printStackTrace();
  }
}

上述就是一個(gè)簡(jiǎn)化版的post過程.

Register流程

上述的post還差一個(gè)很關(guān)鍵的地方,就是subscriptionsByEventType數(shù)據(jù)的來源,我們很自然的就該想到是在register的過程中。

再回來看看subscriptionsByEventType的key和value,發(fā)現(xiàn)這些值大都能從下面這樣的函數(shù)中取得。

@Subscribe
public void event(BaseEventBusBeaan message) {
  LogUtils.d("EventBusActivity event");
}

所以我們需要遍歷類中的所有方法,找到所有@Subscribe注釋過的函數(shù),并保存下來。

這里采用的是apt方案,在編譯過程中遍歷所有類,尋找所有@Subscribe注釋過的函數(shù),并將其按照一定格式保存下來,其結(jié)果會(huì)生成類似以下這樣的類。

//具體的生成過程不再這里贅述,想要了解的可以自己看文末的代碼
//編譯過程中將所有 @Subscribe注釋過的方法保存到SUBSCRIBER_INDEX數(shù)組中。
//key為函數(shù)所屬的類,比如MainActivity,value則是一個(gè)對(duì)象,保存一個(gè)數(shù)據(jù)的集合。
public final class MyEventBusIndex implements SubscriberInfoIndex {
  private static final Map<Class, SubscriberInfo> SUBSCRIBER_INDEX;

  static {
    SUBSCRIBER_INDEX = new HashMap<Class,SubscriberInfo>();
    putIndex(new SimpleSubscriberInfo(EventBusActivity2.class,
            new SubscriberMethod[] {
                    new SubscriberMethod(EventBusActivity2.class, "event", BaseEventBusBeaan.class, ThreadMode.POSTING, 0, false),
                    new SubscriberMethod(EventBusActivity2.class, "sticky", UserInfo.class, ThreadMode.POSTING, 2, true),
                    new SubscriberMethod(EventBusActivity2.class, "sticky2", UserInfo.class, ThreadMode.POSTING, 2, true)}
                    ));
  }

  private static void putIndex(SubscriberInfo info) {
    SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
  }

  @Override
  public SubscriberInfo getSubscriberInfo(Class subscriberClass) {
    return SUBSCRIBER_INDEX.get(subscriberClass);
  }
}

有了MyEventBusIndex之后,開始register流程.

public void register(Object subscriber) {
  Class<?> subscriberClass = subscriber.getClass();
  List<SubscriberMethod> subscriberMethods = findSubscriberMethods(subscriberClass);
    
  //這個(gè)循環(huán)是生成subscriptionsByEventType對(duì)象的關(guān)鍵,
  for (SubscriberMethod method : subscriberMethods) {
    subscribe(subscriber, method);
  }
}

//1.根據(jù)subscriberClass先從methodBySubscriber緩存中找
private List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
  List<SubscriberMethod> subscriberMethods = methodBySubscriber.get(subscriberClass);
  if (subscriberMethods != null) return subscriberMethods;

  subscriberMethods = findByAPT(subscriberClass);
  if (subscriberMethods != null) {
    methodBySubscriber.put(subscriberClass, subscriberMethods);
  }

  return subscriberMethods;
}

//2.接著從subscriberInfoIndex查找,subscriberInfoIndex這個(gè)對(duì)象就是上文中提到的MyEventBusIndex的對(duì)象
private List<SubscriberMethod> findByAPT(Class<?> subscriberClass) {
  if (subscriberInfoIndex == null) {
    throw new RuntimeException("未添加索引文件");
  }
  SubscriberInfo subscriberInfo = subscriberInfoIndex.getSubscriberInfo(subscriberClass);
  if (subscriberInfo != null) return Arrays.asList(subscriberInfo.getSubscriberMethods());
  return null;
}

接著開始遍歷subscriberMethods(因?yàn)槊總€(gè)訂閱者不一定只有一個(gè)方法添加了@Subscribe注解)

for (SubscriberMethod method : subscriberMethods) {
  subscribe(subscriber, method);
}

//在這里就可以生成post過程中所需要的 subscriptionsByEventType 數(shù)據(jù)了。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
  Class<?> eventType = subscriberMethod.getEventType();
  CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
  if (subscriptions == null) {
    subscriptions = new CopyOnWriteArrayList<>();
    subscriptionsByEventType.put(eventType, subscriptions);
  }

  Subscription subscription = new Subscription(subscriber, subscriberMethod);
  subscriptions.add(i, subscription);

  //訂閱者類型集合,unregister的時(shí)候用到
  List<Class<?>> subscribeEvents = typeBySubscriber.get(subscriber);
  if (subscribeEvents == null) {
    subscribeEvents = new ArrayList<>();
    typeBySubscriber.put(subscriber, subscribeEvents);
  }
  subscribeEvents.add(eventType);
}

到了這里,其實(shí)一個(gè)簡(jiǎn)單的流程就已經(jīng)通了。

總結(jié)一下大概的流程

  1. 通過apt在編譯期將所有被 @Subscribe注解的函數(shù)添加到MyEventBusIndex對(duì)象中。
  2. register過程中生成subscriptionsByEventType的數(shù)據(jù)。
  3. post過程中通過subscriptionsByEventType數(shù)據(jù)查找對(duì)應(yīng)的函數(shù),然后再通過反射的方式調(diào)用。

優(yōu)先級(jí)的問題

這個(gè)問題也十分簡(jiǎn)單,只需要在插入數(shù)據(jù)的時(shí)候,做下優(yōu)先級(jí)判斷即可。

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
  Class<?> eventType = subscriberMethod.getEventType();
  CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
  if (subscriptions == null) {
    subscriptions = new CopyOnWriteArrayList<>();
    subscriptionsByEventType.put(eventType, subscriptions);
  }

  Subscription subscription = new Subscription(subscriber, subscriberMethod);
  
  //根據(jù)優(yōu)先級(jí)插隊(duì)
  int size = subscriptions.size();
  for (int i = 0; i <= size; i++) {
    if (i == size || subscriberMethod.getPriority() > subscriptions.get(i).subscriberMethod.getPriority()) {
      if (!subscriptions.contains(subscription)) subscriptions.add(i, subscription);
      break;
    }
  }

  //訂閱者類型集合,unregister的時(shí)候用到
  List<Class<?>> subscribeEvents = typeBySubscriber.get(subscriber);
  if (subscribeEvents == null) {
    subscribeEvents = new ArrayList<>();
    typeBySubscriber.put(subscriber, subscribeEvents);
  }
  subscribeEvents.add(eventType);
}

粘性事件

普通事件是先注冊(cè),后發(fā)送。而粘性事件相反,是先發(fā)送,后注冊(cè)。

我們只需要調(diào)換一下順序即可。在發(fā)送的時(shí)候?qū)⑹录鎯?chǔ)下來,然后在register的時(shí)候去檢查有沒有合適的事件

public void postSticky(Object event) {
  stickyEvents.put(event.getClass(), event);
}

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        ....
    
    //檢查是否有合適的事件可以觸發(fā)
    sticky(subscriberMethod, eventType, subscription);
}

private void sticky(SubscriberMethod subscriberMethod, Class<?> eventType, Subscription subscription) {
  if (subscriberMethod.isSticky()) {
    Object event = stickyEvents.get(eventType);
    if (event != null) {
      postToSubscription(subscription, event);
    }
  }
}

最后加上postToSubscription的代碼。

private void postToSubscription(final Subscription subscription, final Object event) {
  switch (subscription.subscriberMethod.getThreadMode()) {
    case POSTING: // 訂閱、發(fā)布在同一線程
      invokeSubscriber(subscription, event);
      break;
    case MAIN:
      //事件發(fā)送方是主線程
      if (Looper.myLooper() == Looper.getMainLooper()) {
        invokeSubscriber(subscription, event);
      } else {
        //事件發(fā)送方是子線程
        handler.post(new Runnable() {
          @Override
          public void run() {
            invokeSubscriber(subscription, event);
          }
        });
      }
      break;
    case ASYNC:
      //發(fā)送方在主線程
      if (Looper.myLooper() == Looper.getMainLooper()) {
        executorService.execute(new Runnable() {
          @Override
          public void run() {
            invokeSubscriber(subscription, event);
          }
        });
      } else {
        invokeSubscriber(subscription, event);
      }
      break;
  }
}

private void invokeSubscriber(Subscription subscription, Object event) {
  try {
    subscription.subscriberMethod.getMethod().invoke(subscription.subscriber, event);
  } catch (Exception e) {
    e.printStackTrace();
  }
}

點(diǎn)擊查看 源代碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 簡(jiǎn)介 我們知道,Android應(yīng)用主要是由4大組件構(gòu)成。當(dāng)我們進(jìn)行組件間通訊時(shí),由于位于不同的組件,通信方式相對(duì)麻...
    Whyn閱讀 589評(píng)論 0 1
  • 轉(zhuǎn):http://www.itdecent.cn/p/d9516884dbd4[https://www.jian...
    enchanted1107閱讀 592評(píng)論 0 0
  • EventBus用法及源碼解析目錄介紹1.EventBus簡(jiǎn)介1.1 EventBus的三要素1.2 EventB...
    楊充211閱讀 2,040評(píng)論 0 4
  • EventBus是一個(gè)Android開源庫(kù),其使用發(fā)布/訂閱模式,以提供代碼間的松耦合。EventBus使用中央通...
    壯少Bryant閱讀 718評(píng)論 0 4
  • EventBus是在Android中使用到的發(fā)布-訂閱事件總線框架,基于觀察者模式,將事件的發(fā)送者和接收者解耦,簡(jiǎn)...
    BrotherTree閱讀 460評(píng)論 0 1

友情鏈接更多精彩內(nèi)容