Android開發事件總線之EventBus運用和框架原理深入理解


[Android]事件總線之EventBus的使用背景

  在我們的android項目開發過程中,經常會有各個組件如activity,fragment和service之間,各個線程之間的通信需求;項目中用的最多的是Android框架的廣播機制,android的廣播機制是基於系統的Binder機制實現IPC或者進程內部的通信,而Binder這種IPC機制相比於Linux原有的機制來說具有,性能更好、安全性更高和易用性更好的特點,所以android系統中很多系統事件都是基於廣播的方式來發送,如開機廣播、電量低的提醒廣播等。然而在一個android的進程內部,各個組件、子線程之間通信如果使用廣播的話,那就有種殺雞用牛刀的趕腳。。。

[Android]進程內部通信之EventBus

  EventBus是greenrobot開發的發布/訂閱事件總線組件,也是基於觀察者模式,不同點在於,EventBus框架解耦了事件的發送和訂閱模塊,事件對象的分發交由EventBus來處理,下面的框架圖可以清晰的看到這一點。

[Android]EventBus 3.0基本使用方法

一、定義事件類

作為事件的發布者,需要定義所發布的事件的類:

public class MessageEvent {
	private String msg;
	public MessageEvent(String msg) {
    this.msg = msg;
	}

	public String getMsg() {
    	return msg;
	}

	public void setMsg(String msg) {
    	this.msg = msg;
	}
}

二、注冊/取消注冊響應事件

作為事件的訂閱者,需要把響應事件的對象注冊到EventBus當中:

EventBus.getDefault().register(obj)

當不需要處理某個類型的事件的時候,取消對這個事件的監聽:

EventBus.getDefault().unregister(obj)

三、聲明和注釋訂閱方法,選擇指定線程模式

  作為事件的訂閱者,需要定義事件的響應方法,方法名稱可以隨意取,方法的形參類型,必須和監聽的事件對象類型一致:

@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
	Toast.makeText(this, event.getMsg (),
	Toast.LENGTH_SHORT).show();
}

3.1 四種線程模式

事件訂閱者可以通過注解的方式選擇處理事件的方法所在的線程:

PostThread:如果事件處理函數指定了線程模型為PostThread,那么事件的發布和接收處理會在同一個線程當中。

BackgroundThread:如果事件處理函數指定了線程模型為BackgroundThread,那么如果事件是在UI線程中發布出來的,那么該事件處理函數就會在新的子線程中運行,如果事件發布本來就是非UI線程中發布出來 的,那么該事件處理函數直接在發布事件的線程中執行。

MainThread:如果事件處理函數指定了線程模型為MainThread,那么不論事件對象是在哪個線程中發布出來的,該事件處理函數都會在UI線程中執行。

Async:如果事件處理函數指定了線程模型為Async,那么無論事件在哪個線程發布,該事件處理函數都會在新建的子線程中執行。

3.2 黏性事件

通過注解的方式設置sticky為true,那么事件處理函數則可以處理上一次的事件對象:

@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)

四、EventBus 3.0源碼詳解

4.1 注冊流程 

/**
* Registers the given subscriber to receive events. 	Subscribers must call {@link #unregister(Object)} once they
* are no longer interested in receiving events.
* <p/>
* Subscribers have event handling methods that must be 	annotated by {@link Subscribe}.
* The {@link Subscribe} annotation also allows configuration 	like {@link
* ThreadMode} and priority.
*/
public void register(Object subscriber) {
//通過注冊的對象得到其類的class對象
Class<?> subscriberClass = subscriber.getClass();
//通過類的class對象得到此對象的訂閱方法列表
List<SubscriberMethod> subscriberMethods = 	subscriberMethodFinder.findSubscriberMethods(subscriberClass);
	synchronized (this) {
    	for (SubscriberMethod subscriberMethod :subscriberMethods) {
        	//線程同步,遍歷訂閱方法列表,注冊每一個訂閱方法
        	subscribe(subscriber, subscriberMethod);
    	}
	}
}

代碼subscriberMethodFinder.findSubscriberMethods(subscriberClass)獲取訂閱方法列表具體如下:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
	//在緩存中查找此class對象對應的訂閱方法列表
  	List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
  	if (subscriberMethods != null) {
      return subscriberMethods;
  	}
  //是否忽略注解器生成的MyEventBusIndex類
  if (ignoreGeneratedIndex) {
    	//通過反射機制得到訂閱者類class對象對應的訂閱事件方法列表
    	subscriberMethods = findUsingReflection(subscriberClass);
  } else {
  		//從注解器生成的MyEventBusIndex類中獲得訂閱類的訂閱方法列表
    	subscriberMethods = findUsingInfo(subscriberClass);
	}
  if (subscriberMethods.isEmpty()) {
      throw new EventBusException("Subscriber " + subscriberClass
              + " and its super classes have no public methods with the @Subscribe annotation");
  } else {
      //緩存此class對象的訂閱方法列表
      METHOD_CACHE.put(subscriberClass, subscriberMethods);
      return subscriberMethods;
  }
}

通過反射機制獲取訂閱方法列表:

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
  FindState findState = prepareFindState();
  findState.initForSubscriber(subscriberClass);
  while (findState.clazz != null) {
      //遍歷當前class對象和其父類中的訂閱方法
      findUsingReflectionInSingleClass(findState);
      findState.moveToSuperclass();
  }
  return getMethodsAndRelease(findState);
}

findUsingReflectionInSingleClass方法:

private void findUsingReflectionInSingleClass(FindState findState) {
	Method[] methods;
	try {
    	// This is faster than getMethods, especially when subscribers are fat classes like Activities
    methods = findState.clazz.getDeclaredMethods();
	} catch (Throwable th) {
    // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
    methods = findState.clazz.getMethods();
    findState.skipSuperClasses = true;
	}
	//遍歷此類的方法
	for (Method method : methods) {
    	int modifiers = method.getModifiers();
    	if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
        	Class<?>[] parameterTypes = method.getParameterTypes();
        	//形參只有一個的函數
        	if (parameterTypes.length == 1) { 
        		//得到此函數的注解信息對象
        		Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
            	if (subscribeAnnotation != null) {
                Class<?> eventType = parameterTypes[0];
                if (findState.checkAdd(method, eventType)) {
                    ThreadMode threadMode = subscribeAnnotation.threadMode();
                    findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                            subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                }
            }
       	 } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException("@Subscribe method " + methodName +
                    "must have exactly 1 parameter but has " + parameterTypes.length);
        	}
    	} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
        String methodName = method.getDeclaringClass().getName() + "." + method.getName();
        throw new EventBusException(methodName +
                " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
    	}
	}
}

至此,我們得到了所有的訂閱函數列表,下一步,會對每一個訂閱函數進行注冊:

// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
	Class<?> eventType = subscriberMethod.eventType;
	Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
	//通過監聽的事件對象得到其對應的監聽者對象列表
	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();
	for (int i = 0; i <= size; i++) {
    	//遍歷監聽者列表,根據訂閱方法的優先級將新的監聽對象插入到列表的指定位置,即訂閱方法的注冊
    	if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
        	subscriptions.add(i, newSubscription);
        	break;
    	}
	}
	//通過訂閱者對象得到其對應的監聽事件類型列表
	List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
	if (subscribedEvents == null) {
    	subscribedEvents = new ArrayList<>();
    	typesBySubscriber.put(subscriber, subscribedEvents);
	}
	//添加事件類型對象到此監聽對象對應的事件對象列表當中
	subscribedEvents.add(eventType);
	//在這里處理黏性事件
	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);
    }
}

事件的注冊流程實際是從監聽者對象和消息事件兩個維度,將對方分別添加到自己對應的列表當中,具體可以通過以下流程圖總結:

4.2發布流程

/** Posts the given event to the event bus. */
public void post(Object event) {
	//通過ThreadLocal機制得到當前線程的postingState對象
	PostingThreadState postingState = currentPostingThreadState.get();
	List<Object> eventQueue = postingState.eventQueue;
	//在此線程的eventQueue中添加此事件對象
	eventQueue.add(event);
	if (!postingState.isPosting) {
    	//判斷當前線程是否UI線程
    	postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
    	postingState.isPosting = true;
    	if (postingState.canceled) {
        	throw new EventBusException("Internal error. Abort state was not reset");
    	}
    	try {
        while (!eventQueue.isEmpty()) {
            //遍歷此線程消息隊列,處理消息隊列中的消息事件
            postSingleEvent(eventQueue.remove(0), postingState);
        }
    	} finally {
        postingState.isPosting = false;
        postingState.isMainThread = false;
    	}
	}
}

ThreadLocal機制可以存儲各個線程的局部數據;
postSingleEvent函數處理此線程消息隊列中的消息事件:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
		Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
	if (eventInheritance) {
    	//得到此事件類的所有父類和接口
    	List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
    	int countTypes = eventTypes.size();
    	for (int h = 0; h < countTypes; h++) {
        	Class<?> clazz = eventTypes.get(h);
        	//通過事件類得到其對應的訂閱者對象列表,將事件對象分發到相應的訂閱函數中處理,至此實現了事件消息的傳遞
        	subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
    	}
	} else {
    	//通過事件類得到其對應的訂閱者對象列表,將事件對象分發到相應的訂閱函數中處理,至此實現了事件消息的傳遞
    	subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
   }
	if (!subscriptionFound) {
    	if (logNoSubscriberMessages) {
        Log.d(TAG, "No subscribers registered for event " + eventClass);
    	}
    	if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
            eventClass != SubscriberExceptionEvent.class) {
        	post(new NoSubscriberEvent(this, event));
    	}
	}
}

事件消息對象具體的分發函數:postToSubscription

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 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);
	}
}

至此,我們完成了事件消息對象的分發流程,以下流程圖來總結post的過程:

4.3 取消注冊流程

/** Unregisters the given subscriber from all event classes. */
public synchronized void unregister(Object subscriber) {
	//根據訂閱者對象得到其對應的事件類型列表
	List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
	if (subscribedTypes != null) {
    	for (Class<?> eventType : subscribedTypes) {
        	//遍歷事件類型列表,得到每個事件類型對應的訂閱者對象列表,遍歷這個列表,如果是此觀察者對象,則從列表中刪除
        	unsubscribeByEventType(subscriber, eventType);
    	}
    	//typesBySubscriber中刪除此訂閱者對象
    	typesBySubscriber.remove(subscriber);
	} else {
    	Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
	}
}

unsubscribeByEventType函數:

/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
	//根據事件類型得到其對應的訂閱者對象列表
	List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
	if (subscriptions != null) {
    	int size = subscriptions.size();
    	for (int i = 0; i < size; i++) {
        	Subscription subscription = subscriptions.get(i);
        	//遍歷列表,如果找到此訂閱者對象則從列表中刪除
        	if (subscription.subscriber == subscriber) {
            	subscription.active = false;
            	subscriptions.remove(i);
            	i--;
            	size--;
        	}
    	}
	}
}

取消注冊的流程總結如下:

1.通過觀察者類對象通過MAP表得到其對應的事件類class對象列表.

2.遍歷list列表,通過事件類class對象得到其在MAP表中對應的觀察者類對象列表。

3.遍歷此觀察者對象列表,判斷如果列表中存在需要取消的注冊觀察者對象,則從對象列表中刪除此觀察者對象。

4.從第1步中得到的MAP對象中刪除以取消注冊的觀察者對象為key的映射項目。

5.完成unregister過程。

五、相似功能框架的比較

和EventBus具有相似功能的組件有Square推出的Otto,Android系統的LocalBroadCast,共同點是這三個框架都是進程內通信,方便進程內各個組件和子線程間的通信。

Ottootto采用了注解的方式完成注冊,Otto更多使用場景應該就是在主線程中,因為其不像EventBus可以選擇不同的線程模式。

LocalBroadCast是Android在Android Support Library中提供的,用於進程內部通信框架,其主要目的是保證廣播在進程內部傳遞,確保通訊的安全行,其內部通過handler的方式實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM