Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 事件總線OTTO使用說明和源碼解析

Android 事件總線OTTO使用說明和源碼解析

編輯:關於Android編程

一、Otto簡單介紹

OTTO是Square推出的庫,地址:https://github.com/square/otto

先來看看otto的官方介紹

An enhanced Guava-based event bus with emphasis on Android support.Otto is an event bus designed to decouple different parts of your application while still allowing them to communicate efficiently.Forked from Guava, Otto adds unique functionality to an already refined event bus as well as specializing it to the Android platform.

OTTO基於Guava項目的Android支持庫,如果你在Android程序開發的過程中想要不同的組件之間進行有效的通信可以使用這個庫。通過otto庫可以。

 

二、Otto簡單使用

1、創建一個BUS的單例。

 

 

public class AppConfig {
private static final Bus BUS = new Bus();
public static Bus getInstance() {
    return BUS;
}
}

2、在需要使用Otto的類中注冊

 

在類創建好之後,或者需要重新注冊的時候注冊,一般在Activity的onCreate()或者onPause()方法中

 

 AppConfig.getBusInstance().register(this);

 

3、定義訂閱方法

 

 

@Subscribe
public void onWallpaperUpdate(MyObject obj) {
    //對obj進行需要的邏輯處理
}

 

4、發送消息

 

 

AppConfig.getBusInstance().post(myobj);

 

5、解綁

注意在類銷毀的時候或者暫時不需要再收消息的時候解綁,,一般在Activity的onDestroy()或者onResume()方法中

 

 AppConfig.getBusInstance().unregister(this);

 

三、Otto源碼解析

1、整體結構

otto的源碼結構非常簡單,所有類都包含在com.squareup.otto這一個包中, 不計內部類,只有9個類跟接口,分別是 AnnotatedHandlerFinder 注解解析器
Bus 總線核心類
DeadEvent 沒有接收者的事件
EventHandler 事件訂閱者
EventProducer 事件生產者
HandlerFinder 獲取接收者生產者
Produce 生產者的注解
Subscribe 訂閱者注解
ThreadEnforcer對線程進行校驗

2、Bus的關鍵屬性方法分析

(1)構造函數

  public Bus() {
    this(DEFAULT_IDENTIFIER);
  }
  public Bus(String identifier) {
    this(ThreadEnforcer.MAIN, identifier);
  }
  public Bus(ThreadEnforcer enforcer) {
    this(enforcer, DEFAULT_IDENTIFIER);
  }
  public Bus(ThreadEnforcer enforcer, String identifier) {
    this(enforcer, identifier, HandlerFinder.ANNOTATED);
  }
  Bus(ThreadEnforcer enforcer, String identifier, HandlerFinder handlerFinder) {
    this.enforcer =  enforcer;
    this.identifier = identifier;
    this.handlerFinder = handlerFinder;
  }
我們通常使用Bus()這個構造方法,在整個app中創建一個單例,這樣不但節省資源,更重要的是保證消息正常到達如果不是單例,用一個Bus進行了注冊,而用另外一個Bus發送消息,這樣訂閱的方法是無法收到消息的。 enforcer是對線程進行校驗,有兩個取值,一個是ThreadEnforcer.ANY,另一個是ThreadEnforcer.MAIN,默認值是ThreadEnforcer.MAIN,這樣只能在主線程進行消息處理。如果在非主線程注冊或者發送消息,就會拋出異常 throw new IllegalStateException("Event bus " + bus + " accessed from non-main thread " + Looper.myLooper()); 這點一定要注意:Otto默認構造方法創建的Bus實例只能在主線程調用 如果要在其他線程使用,就使用ThreadEnforcer.ANY,或者自定義ThreadEnforcer。 只在主線程使用,能保證簡潔,不混亂。但是我們實際使用中很多時候還是要跨線程通信的。 identifier是一個標識,在Bus的toString()方法中會用到。 HandlerFinder用來解析注冊的對象,默認的實現是HandlerFinder.ANNOTATED,使用注解解析

(2)對象注冊

public void register(Object object) {
    if (object == null) {
      throw new NullPointerException("Object to register must not be null.");
    }
    enforcer.enforce(this);
    Map, EventProducer> foundProducers = handlerFinder.findAllProducers(object);
    for (Class type : foundProducers.keySet()) {
      final EventProducer producer = foundProducers.get(type);
      EventProducer previousProducer = producersByType.putIfAbsent(type, producer);
      //checking if the previous producer existed
      if (previousProducer != null) {
        throw new IllegalArgumentException("Producer method for type " + type
          + " found on type " + producer.target.getClass()
          + ", but already registered by type " + previousProducer.target.getClass() + ".");
      }
      Set handlers = handlersByType.get(type);
      if (handlers != null && !handlers.isEmpty()) {
        for (EventHandler handler : handlers) {
          dispatchProducerResultToHandler(handler, producer);
        }
      }
    }
    Map, Set> foundHandlersMap = handlerFinder.findAllSubscribers(object);
    for (Class type : foundHandlersMap.keySet()) {
      Set handlers = handlersByType.get(type);
      if (handlers == null) {
        //concurrent put if absent
        Set handlersCreation = new CopyOnWriteArraySet();
        handlers = handlersByType.putIfAbsent(type, handlersCreation);
        if (handlers == null) {
            handlers = handlersCreation;
        }
      }
      final Set foundHandlers = foundHandlersMap.get(type);
      if (!handlers.addAll(foundHandlers)) {
        throw new IllegalArgumentException("Object already registered.");
      }
    }
    for (Map.Entry, Set> entry : foundHandlersMap.entrySet()) {
      Class type = entry.getKey();
      EventProducer producer = producersByType.get(type);
      if (producer != null && producer.isValid()) {
        Set foundHandlers = entry.getValue();
        for (EventHandler foundHandler : foundHandlers) {
          if (!producer.isValid()) {
            break;
          }
          if (foundHandler.isValid()) {
            dispatchProducerResultToHandler(foundHandler, producer);
          }
        }
      }
    }
  }
對象注冊首先進行了非空校驗,然後是線程的校驗,對象不可為空,不可多次注冊 在注冊對象之後,會解析出對象對應的類的生產方法和訂閱方法,訂閱者解析的結果保存在handlersByType,生產者解析的結果保存在producersByType裡,這兩個屬性定義如下
private final ConcurrentMap, Set> handlersByType =
          new ConcurrentHashMap, Set>();
  /** All registered event producers, index by event type. */
  private final ConcurrentMap, EventProducer> producersByType =
          new ConcurrentHashMap, EventProducer>();
handlersByType的key中保存了訂閱方法的入參 參數類型,vaue中保存著訂閱者具體的對象和對應方法 producersByType的key中保存了生產方法的返回值參數類型,vaue中保存著生產者具體的對象和對應方法 從源碼中我們可以發現,在解析訂閱者之後,如果有對應的生產者,會自動調用生產方法,並自動調用一次訂閱者方法。
所謂有對應生產者,就是有Produce注解的方法返回值參數類型和有Subscribe注解的方法參數相同。 這個過程涉及到以下三個方法 public void register(Object object) private void dispatchProducerResultToHandler(EventHandler handler, EventProducer producer)
protected void dispatch(Object event, EventHandler wrapper)

(3)消息發送

 public void post(Object event) {
    if (event == null) {
      throw new NullPointerException("Event to post must not be null.");
    }
    enforcer.enforce(this);

    Set> dispatchTypes = flattenHierarchy(event.getClass());

    boolean dispatched = false;
    for (Class eventType : dispatchTypes) {
      Set wrappers = getHandlersForEventType(eventType);

      if (wrappers != null && !wrappers.isEmpty()) {
        dispatched = true;
        for (EventHandler wrapper : wrappers) {
          enqueueEvent(event, wrapper);
        }
      }
    }
    if (!dispatched && !(event instanceof DeadEvent)) {
      post(new DeadEvent(this, event));
    }
    dispatchQueuedEvents();
  }
這裡主要涉及兩個屬性
 private final ThreadLocal> eventsToDispatch =
      new ThreadLocal>() {
        @Override protected ConcurrentLinkedQueue initialValue() {
          return new ConcurrentLinkedQueue();
        }
      };

  /** True if the current thread is currently dispatching an event. */
  private final ThreadLocal isDispatching = new ThreadLocal() {
    @Override protected Boolean initialValue() {
      return false;
    }
  };
當調用 public void post(Object event)這個方法之後,首先進行線程校驗,然後解析出對應的訂閱者,如果有訂閱者,將event放入隊列中, 如果沒有,就作為一個DeadEvent,對於DeadEvent注釋是這樣說的 * Wraps an event that was posted, but which had no subscribers and thus could not be delivered.
*

Subscribing a DeadEvent handler is useful for debugging or logging, as it can detect misconfigurations in a
* system's event distribution.

已經很明確了,就不再贅述。 Bus在分發消息之後循環從消息隊列中取值,這跟android的handler消息機制很像,不過bus中的循環在消息取完之後就結束了。
protected void dispatchQueuedEvents() {
    // don't dispatch if we're already dispatching, that would allow reentrancy and out-of-order events. Instead, leave
    // the events to be dispatched after the in-progress dispatch is complete.
    if (isDispatching.get()) {
      return;
    }
    isDispatching.set(true);
    try {
      while (true) {
        EventWithHandler eventWithHandler = eventsToDispatch.get().poll();
        if (eventWithHandler == null) {
          break;
        }

        if (eventWithHandler.handler.isValid()) {
          dispatch(eventWithHandler.event, eventWithHandler.handler);
        }
      }
    } finally {
      isDispatching.set(false);
    }
  }
消息隊列使用ThreadLocal保證了隊列的獨立性。同時多個線程會創建多個循環,提高了效率。發送的消息很快就就可以分發。

(4)解綁操作

public void unregister(Object object) {
    if (object == null) {
      throw new NullPointerException("Object to unregister must not be null.");
    }
    enforcer.enforce(this);
    Map, EventProducer> producersInListener = handlerFinder.findAllProducers(object);
    for (Map.Entry, EventProducer> entry : producersInListener.entrySet()) {
      final Class key = entry.getKey();
      EventProducer producer = getProducerForEventType(key);
      EventProducer value = entry.getValue();

      if (value == null || !value.equals(producer)) {
        throw new IllegalArgumentException(
            "Missing event producer for an annotated method. Is " + object.getClass()
                + " registered?");
      }
      producersByType.remove(key).invalidate();
    }
跟register類似,首先是對象非空校驗,然後是線程校驗,然後解綁,注冊跟解綁一定要成對,沒有注冊不可以解綁,解綁之後不可以直接再次解綁。 解綁主要是清理工作,減少不必要的內存,防止內存洩漏。解綁之後就不能再收到綁定對象相關的消息了。

3、AnnotatedHandlerFinder對注解的解析,構建生產者和訂閱者

解析出來的信息存放在PRODUCERS_CACHE,SUBSCRIBERS_CACHE中,
  /** Cache event bus producer methods for each class. */
  private static final ConcurrentMap, Map, Method>> PRODUCERS_CACHE =
    new ConcurrentHashMap, Map, Method>>();

  /** Cache event bus subscriber methods for each class. */
  private static final ConcurrentMap, Map, Set>> SUBSCRIBERS_CACHE =
    new ConcurrentHashMap, Map, Set>>();

結構相同,key值是解析對象對應的class,value是方法參數類型(生產者是返回數據類型,訂閱者是方法參數的數據類型)和對應方法。 解析生產者依次調用: static Map, EventProducer> findAllProducers(Object listener),
private static void loadAnnotatedProducerMethods(ClasslistenerClass,Map, Method> producerMethods) ,
private static void loadAnnotatedMethods(ClasslistenerClass,Map, Method> producerMethods, Map, Set> subscriberMethods)。
解析訂閱者依次調用: static Map, Set> findAllSubscribers(Object listener) ,
private static void loadAnnotatedSubscriberMethods(ClasslistenerClass,Map, Set> subscriberMethods) ,
private static void loadAnnotatedMethods(ClasslistenerClassMap, Method> producerMethods, Map, Set> subscriberMethods)。
最終都是調用loadAnnotatedMethods 在解析過程中,違反一些規則會拋出異常 @Subscribe注解的方法只能有一個參數 @Subscribe注解的方法必須有具體實現,不能是個接口 @Subscribe注解的方法必須是public的 @Produce注解的方法入參必須是空的 @Produce注解的方法返回值類型不能是void @Produce注解的方法必須是具體實現,不能是個接口 @Produce注解的方法必須是public的 @Produce注解的方法相同返回值類型的方法只能有一個  

四、總結

OTTO是非常輕量級的,多數實現依賴注解反射,使用過程中如果要對代碼進行混淆要特別注意。 android事件總線處理還有個很好的開源框架是EventBus,EventBus稍微重量級一些,復雜一些,對應的功能更多,對線程控制更加靈活。 用RXJAVA也可以實現事件總線,以後在做詳細說明。無論用哪個框架,歸根到底都是一種觀察者模式。可以根據項目需要選擇合適的框架。當然,通過原生的廣播,Handler機制,肯定也能實現。  
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved