消息總線那些事兒

項目到了一定階段會出現(xiàn)一種甜蜜的負擔:業(yè)務的不斷發(fā)展與人員的流動性越來越大,代碼維護與測試回歸流程越來越繁瑣。這個時候需要對項目進行兩方面的重構:

1.分層操作,方便復用

2.模塊解耦,減少影響

小英團隊經過多次調研之后有如下成果:

分層操作

下圖是項目的分層模型:

ProgectUI:界面展示層,包括各個Activity,F(xiàn)ragment頁面,以及相對復雜的一些UI組件等

Bussiness:實際業(yè)務層,比如:用戶點擊登錄按鈕,去執(zhí)行登錄的操作

BussinessService:業(yè)務服務層,對外提供數(shù)據(jù)服務。比如:用戶信息模塊對外提供用戶相關的所有信息

Interface層:網(wǎng)絡請求與數(shù)據(jù)緩存層;將用戶的網(wǎng)絡接口單獨作為一層,并根據(jù)實際需要設置是否進行緩存。

DbCache層:數(shù)據(jù)庫與數(shù)據(jù)模型轉換層;所有數(shù)據(jù)庫操作都使用DbCache;

CoreService層:功能同F(xiàn)rameWork層,但是較重,故拆出;CoreService與FrameWork層具備業(yè)務無關性、通用性;主要有:分發(fā)器,Hybrid,熱修復以及埋點等

FrameWork層:基礎的技術組件(網(wǎng)絡庫,圖片庫等)、三方服務封裝以及通用UI等;

以用戶信息模塊為例,介紹一下具體的實現(xiàn)過程:

ProgectUI對應NewLoginActivity:界面展示層,登錄頁面,通過ChrLoginView與業(yè)務邏輯解耦

Bussiness對應QuickLoginPresenter:實際業(yè)務層,用戶注冊、登錄、忘記密碼等操作;依賴View與Module,處理實際的業(yè)務邏輯

BussinessService對應UserInstance:業(yè)務服務層.存儲賬號信息與用戶信息。通過實現(xiàn)接口UserInfoInterface,向外提供用戶信息服務。

Interface對應ApiService:登錄相關的后臺接口。

FrameWork對應ApiUtils:封裝網(wǎng)絡庫Retrofit,代理網(wǎng)絡請求

模塊解耦

總體采用依賴注入的方式將服務的實現(xiàn)與使用分離:

UserInstance維護用戶模塊所有的信息,并通過UserInfoInterface與其他模塊進行隔離

ServiceManager負責維護各個模塊服務的注冊并提供訪問的接口

UserBean實體bean作為數(shù)據(jù)通信的格式,負責統(tǒng)計用戶模塊所有的信息

EventBus消息總線負責向外提供本模塊的方法調用

總體思想:依賴注入負責對外暴露數(shù)據(jù);EventBus負責對外暴露回調方法。

接下來就來介紹本文的主體:EventBus

簡單介紹

EventBus-Android端事件發(fā)布/訂閱框架,特點如下:

簡化組件之間的通信。Android常用的通信方式:Broadcast、Listener、靜態(tài)變量以及通過Handler進行線程之間的通信等。都可以統(tǒng)一使用EventBus。

簡化代碼。不同于使用Listener通信方式,層層傳遞,模塊之間耦合嚴重。EventBus使用非常簡單并且無耦合

高性能。框架中采用緩存、池化技術、細粒度鎖、索引加速等方式使開發(fā)者無需關注安全性能方面的問題

依賴包不足50k

高級屬性:線程模型、優(yōu)先級以及是否接收粘性事件等

總線的工作機制如圖:

訂閱者通過EventBus訂閱相關事件,并準備好回調方法

發(fā)布者將事件發(fā)送給post出去,EventBus負責通知訂閱者

極簡使用

分為五步:導入依賴、初始化總線、定義事件、注冊訂閱者、發(fā)送事件

導入依賴

項目中Module的build.gradle中添加依賴:

compile 'org.greenrobot:eventbus:3.0.0’

如果不需要索引加速,直接跳到第二步。

索引加速使用到編譯時注解,所以需要在項目gradle中添加apt編譯插件:

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8’

在Module中申請apt插件生成索引的完整類名

apply plugin: 'com.neenbedankt.android-apt'

apt {

arguments {

eventBusIndex “com.chinahrMyEventBusIndex"

}

}

Module中引入EventBusAnnotationProcessor:

apt 'org.greenrobot:eventbus-annotation-processor:3.0.1’

編譯項目之后,就可以在\app\build\generate\source\apt下看到生成的索引類

初始化總線

EventBus可以通過getDefault()方法獲取單例

EventBus.getDefault().register(this);

也可以通過EventBus.builder()去構造自定義的EventBus。另外還可以通過Bulder.installDefaultEventBus()修改默認的單例

EventBus.builder().eventInheritance(true).installDefaultEventBus();

如果需要索引加速,將編譯時生成的Index通過Builder添加進去

EventBus.getDefault().builder().addIndex(new MyEventBusIndex());

定義事件

所有可以被實例化成object的類都可以作為事件

public class MyEvent {

}

注冊訂閱者

訂閱事件的類中執(zhí)行register()

EventBus.getDefault().register(this);

并在監(jiān)聽事件的回調方法上添加注解@SubScribe,可配置屬性:方法執(zhí)行的線程模型,分發(fā)的優(yōu)先級,是否接收粘性事件

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

public ?void onEventLogin(MyEvent myEvent){

Toast.makeText(MainActivity.this,"登錄成功",Toast.LENGTH_LONG).show();

}

為了防止內存泄露,在總線中對訂閱者進行注銷,比如在Activity的OnDestroy()中:

EventBus.getDefault().unregister(this);

發(fā)送事件

調用post(myEvent)或postSticky(myEvent)

EventBus.getDefault().post(new MyEvent());

以上完成了消息訂閱/發(fā)布的整個流程。

接下來將會說明框架內部的結構:

整體框架

四部分組成,如圖:

數(shù)據(jù)元素

框架主要涉及這些數(shù)據(jù)元素:訂閱者Subscriber,方法主體Method,事件event,事件類型eventType,線程模型ThreadMode,方法優(yōu)先級priority,是否接收粘性事件sticky

SubscriberMethod與訂閱者subscriber組合成Subscription,即訂閱方法。

一個方法的執(zhí)行需要方法主體(Method),調用方法的對象(Subscriber),參數(shù)(事件event)

post(event)之后結合Subscription就可以完成Method的調用。

EventBus

框架的門面,維護三個Map,并負責分發(fā)事件與執(zhí)行訂閱方法。

Map, CopyOnWriteArrayList>subscriptionsByEventType.事件類型與訂閱方法列表的對應關系。注冊事件,發(fā)送事件都是在操作此map.

Map>>typesBySubscriber.訂閱者與訂閱事件類型的對應關系。為了方便注銷訂閱者。

Map, Object>stickyEvents.粘性事件列表

調度器

回調方法通過四種方式進行分發(fā),即線程模型ThreadMode:

POSTING:默認模式,直接在當前線程執(zhí)行

MAIN:如果當前是主線程,直接執(zhí)行;如果不是主線程,通過handler發(fā)送到主線程執(zhí)行。

BACKGROUND:如果是主線程,交給backgroundPoster去調度;如果不是主線程就直接執(zhí)行。

ASYN:交給asyncPoster調度。asyncPoster會直接在線程池當中開啟一個線程執(zhí)行。

索引加速

編譯時,apt插件通過EventBusAnnotationProcessor提取注解@Subscribe生成索引MyEventBusIndex.索引內部維護一個Map,輔助EventBus查找方法信息

Map, SubscriberInfo>SUBSCRIBER_INDEX.訂閱者的類型與回調方法列表對應關系

整個流程一句話總結:在訂閱者準備好處理事件的回調方法之后,EventBus根據(jù)訂閱者對象經過反射或者索引加速獲取回調方法的信息,接收發(fā)布的事件event,按照指定的線程模型執(zhí)行回調方法。

EventBus內部設計十分精致,對于編程技能的提高有非常大的幫助。接下來介紹:

設計思想

作為一個框架主要有四方面的設計:門面、調度器、線程安全、性能調優(yōu)等

門面

1.EventBus類,門面模式中的門面類對外提供集中化和簡化的溝通管道。總線的所有操作(注冊訂閱者,發(fā)送事件,調度執(zhí)行,注銷訂閱者等)被封裝到EventBus中,有效地屏蔽實現(xiàn)細節(jié),使用和維護起來非常方便。

public classEventBus {

/**

* Registers the given subscriber to receive events. Subscribers must call {@link#unregister(Object)} once they

* are no longer interested in receiving events.

*

* Subscribers have event handling methods that must be annotated by {@linkSubscribe}.

* The {@linkSubscribe} annotation also allows configuration like {@link

* ThreadMode} and priority.

*/

public void register(Object subscriber) {Class subscriberClass = subscriber.getClass();

List subscriberMethods =subscriberMethodFinder.findSubscriberMethods(subscriberClass);

synchronized(this) {

for(SubscriberMethod subscriberMethod : subscriberMethods) {

subscribe(subscriber, subscriberMethod);

}

}

}

/** Posts the given event to the event bus. */

public voidpost(Object event) {

PostingThreadState postingState =currentPostingThreadState.get();

List eventQueue = postingState.eventQueue;

eventQueue.add(event);

if(!postingState.isPosting) {

postingState.isMainThread= Looper.getMainLooper() == Looper.myLooper();

postingState.isPosting=true;

if(postingState.canceled) {

throw newEventBusException("Internal error. Abort state was not reset");

}

try{

while(!eventQueue.isEmpty()) {

postSingleEvent(eventQueue.remove(0), postingState);

}

}finally{

postingState.isPosting=false;

postingState.isMainThread=false;

}

}

}

voidinvokeSubscriber(Subscription subscription, Object event) {

try{

subscription.subscriberMethod.method.invoke(subscription.subscriber, event);

}catch(InvocationTargetException e) {

handleSubscriberException(subscription, event, e.getCause());

}catch(IllegalAccessException e) {

throw newIllegalStateException("Unexpected exception", e);

}

}

/** Unregisters the given subscriber from all event classes. */

public synchronized void unregister(Object subscriber) {List> subscribedTypes =typesBySubscriber.get(subscriber);

if(subscribedTypes !=null) {

for(Class eventType : subscribedTypes) {

unsubscribeByEventType(subscriber, eventType);

}

typesBySubscriber.remove(subscriber);

}else{

Log.w(TAG,"Subscriber to unregister was not registered before: "+ subscriber.getClass());

}

}

}

2.EventBus對象的構建,采用volatile實例與雙重檢查加鎖方式保證線程安全。

static volatileEventBusdefaultInstance;

/** Convenience singleton for apps using a process-wide EventBus instance. */

public staticEventBus getDefault() {

if(defaultInstance==null) {

synchronized(EventBus.class) {

if(defaultInstance==null) {

defaultInstance=newEventBus();

}

}

}

returndefaultInstance;

}

3.采用Builder模式輔助構建實例,避免因構造參數(shù)多帶來的構造器繁多,即避免重復重疊構造器模式;也避免了采用javaBean封裝參數(shù)帶來的修改一致性問題。

public classEventBus {

private static finalEventBusBuilderDEFAULT_BUILDER=newEventBusBuilder();

EventBus(EventBusBuilder builder) {

subscriptionsByEventType=newHashMap<>();

typesBySubscriber=newHashMap<>();

stickyEvents=newConcurrentHashMap<>();

mainThreadPoster=newHandlerPoster(this, Looper.getMainLooper(),10);

backgroundPoster=newBackgroundPoster(this);

asyncPoster=newAsyncPoster(this);

indexCount= builder.subscriberInfoIndexes!=null? builder.subscriberInfoIndexes.size() :0;

subscriberMethodFinder=newSubscriberMethodFinder(builder.subscriberInfoIndexes,

builder.strictMethodVerification, builder.ignoreGeneratedIndex);

logSubscriberExceptions= builder.logSubscriberExceptions;

logNoSubscriberMessages= builder.logNoSubscriberMessages;

sendSubscriberExceptionEvent= builder.sendSubscriberExceptionEvent;

sendNoSubscriberEvent= builder.sendNoSubscriberEvent;

throwSubscriberException= builder.throwSubscriberException;

eventInheritance= builder.eventInheritance;

executorService= builder.executorService;

}

public staticEventBusBuilder builder() {

return newEventBusBuilder();

}

}

public classEventBusBuilder {

/** Default: true */

publicEventBusBuilder logSubscriberExceptions(booleanlogSubscriberExceptions) {

this.logSubscriberExceptions= logSubscriberExceptions;

return this;

}

/** Builds an EventBus based on the current configuration. */

publicEventBus build() {

return newEventBus(this);

}

}

4.構造方法采用public,可以構建在項目中對不同的子模塊創(chuàng)建消息總線。Builder中提供修改默認單例的方法installDefaultEventBus(),是全局單例更加靈活。

public classEventBus {

/**

* Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a

* central bus, consider {@link#getDefault()}.

*/

publicEventBus() {

this(DEFAULT_BUILDER);

}

}

public classEventBusBuilder {

* Installs the default EventBus returned by {@linkEventBus#getDefault()} using this builders' values. Must be

* done only once before the first usage of the default EventBus.

*

*@throwsEventBusException if there's already a default EventBus instance in place

*/

publicEventBus installDefaultEventBus() {

synchronized(EventBus.class) {

if(EventBus.defaultInstance!=null) {

throw newEventBusException("Default instance already exists."+

" It may be only set once before it's used the first time to ensure consistent behavior.");

}

EventBus.defaultInstance= build();returnEventBus.defaultInstance;

}

}

}

總結一句話:EvntBus作為框架的門面,采用雙檢鎖,builder模式,構造參數(shù)public,封裝流程細節(jié),使得總線可以靈活配置與統(tǒng)一管理。

數(shù)據(jù)結構Map化

利用對象的class屬性作為Map的key值可以使代碼更加簡潔。這也是只需要傳入一個對象就可以驅動整個流程運行起來的關鍵。

private finalMap, CopyOnWriteArrayList>subscriptionsByEventType;

private finalMap>>typesBySubscriber;

private finalMap, Object>stickyEvents;

// Must be called in synchronized block

private voidsubscribe(Object subscriber, SubscriberMethod subscriberMethod) {

…………

…………

…………

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).

Set, Object>> entries =stickyEvents.entrySet();

for(Map.Entry, 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);

}

}

}

public voidpostSticky(Object event) {

synchronized(stickyEvents) {

stickyEvents.put(event.getClass(), event);

}

// Should be posted after it is putted, in case the subscriber wants to remove immediately

post(event);

}

這個可以借鑒到組件化設計當中的服務管理器ServiceManager,維護一個以服務接口類為key,實現(xiàn)類作為value的Map。簡化注冊調用方法。

public classServiceManager {

Map serviceMap=new HashMap<>();static volatileServiceManagerdefaultInstance;

public staticServiceManager ?getInstance(){

if(defaultInstance==null){

synchronized(ServiceManager.class){

if(defaultInstance==null){

defaultInstance=newServiceManager();

}

}

}

returndefaultInstance;

}

public voidregister(Object serviceInstance){

serviceMap.put(serviceInstance.getClass().getInterfaces()[0],serviceInstance);

}

publicTgetService(Class serviceType){

return(T)serviceMap.get(serviceType);

}

}

public interfaceUserinfoInterface {

publicUserBean getUserInfo();

}

注冊服務

public classUserInstanceimplementsUserinfoInterface {

UserInstance(){

ServiceManager.getInstance().register(this);

}

@Override

publicUserBean getUserInfo() {

return newUserBean();

}

}

其他模塊調用用戶信息:

public classResumeInstance {

voiddoSomeThing(){

UserinfoInterface userinfoInterface=ServiceManager.getInstance().getService(UserinfoInterface.class);

if(userinfoInterface!=null)

userinfoInterface.getUserInfo();

}

}

調度器

Android的線程有個特點:主線程不能被阻塞,UI的更新位于主線程,耗時操作如網(wǎng)絡處理在后臺線程

框架采用下面的方式進行調度:

每個調度器都維護一個待處理方法隊列PendingPostQueue。值得一提的是:poll(intmaxMillisToWait)出隊時如果當前隊列為空,會釋放當前對象的鎖,等待隊列填充。這個功能將在下面性能分析時解釋

final classPendingPostQueue {

privatePendingPosthead;

privatePendingPosttail;

synchronized voidenqueue(PendingPost pendingPost) {

if(pendingPost ==null) {

throw newNullPointerException("null cannot be enqueued");

}

if(tail!=null) {

tail.next= pendingPost;

tail= pendingPost;

}else if(head==null) {

head=tail= pendingPost;

}else{

throw newIllegalStateException("Head present, but no tail");

}

notifyAll();

}

synchronizedPendingPost poll() {

PendingPost pendingPost =head;

if(head!=null) {

head=head.next;

if(head==null) {

tail=null;

}

}

returnpendingPost;

}

synchronizedPendingPost poll(intmaxMillisToWait)throwsInterruptedException {

if(head==null) {

wait(maxMillisToWait);

}

returnpoll();

}

}

HandlerPoster負責在主線程中處理事件,顯然它是Handler的子類

final classHandlerPosterextendsHandler {

private finalPendingPostQueuequeue;

private final intmaxMillisInsideHandleMessage;

private finalEventBuseventBus;

private booleanhandlerActive;

HandlerPoster(EventBus eventBus, Looper looper,intmaxMillisInsideHandleMessage) {

super(looper);

this.eventBus= eventBus;

this.maxMillisInsideHandleMessage= maxMillisInsideHandleMessage;

queue=newPendingPostQueue();

}

voidenqueue(Subscription subscription, Object event) {

PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);

synchronized(this) {

queue.enqueue(pendingPost);

if(!handlerActive) {

handlerActive=true;

if(!sendMessage(obtainMessage())) {

throw newEventBusException("Could not send handler message");

}

}

}

}

@Override

public voidhandleMessage(Message msg) {

booleanrescheduled =false;

try{

longstarted = SystemClock.uptimeMillis();

while(true) {

PendingPost pendingPost =queue.poll();

if(pendingPost ==null) {

synchronized(this) {

// Check again, this time in synchronized

pendingPost =queue.poll();

if(pendingPost ==null) {

handlerActive=false;

return;

}

}

}

eventBus.invokeSubscriber(pendingPost);

longtimeInMethod = SystemClock.uptimeMillis() - started;

if(timeInMethod >=maxMillisInsideHandleMessage) {

if(!sendMessage(obtainMessage())) {

throw newEventBusException("Could not send handler message");

}

rescheduled =true;

return;

}

}

}finally{

handlerActive= rescheduled;

}

}

}

BackgroundPoster繼承自Runnable,某一時段內待處理都會在BackgroundPoster的run方法中排隊處理。BackgroundPoster正在被線程池執(zhí)行時,executorRunning==true,此時發(fā)布的事件只會進隊列,不會再次調用線程池的execute方法。事件全部處理后才退出死循環(huán),設置executorRunning=fasle,此后再發(fā)布事件才會在線程池中開辟一個新線程。

上文提到(PendingPostQueue.poll(int))在隊列為空的時候等待隊列中添加元素。以及executorRunning標示位的使用,都是為了重用當前對象。

final classBackgroundPosterimplementsRunnable {

private finalPendingPostQueuequeue;

private finalEventBuseventBus;

private volatile booleanexecutorRunning;

BackgroundPoster(EventBus eventBus) {

this.eventBus= eventBus;

queue=newPendingPostQueue();

}

public voidenqueue(Subscription subscription, Object event) {

PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);

synchronized(this) {

queue.enqueue(pendingPost);

if (!executorRunning) {

executorRunning = true;

eventBus.getExecutorService().execute(this);

}

}

}

@Override

public voidrun() {

try{

try{

while(true) {

PendingPost pendingPost = queue.poll(1000);

if(pendingPost ==null) {

synchronized(this) {

// Check again, this time in synchronized

pendingPost =queue.poll();

if(pendingPost ==null) {

executorRunning=false;

return;

}

}

}

eventBus.invokeSubscriber(pendingPost);

}

}catch(InterruptedException e) {

Log.w("Event", Thread.currentThread().getName() +" was interruppted", e);

}

}finally{

executorRunning=false;

}

}

}

AyncPoster很簡單,來了事件就在線程池中開辟線程執(zhí)行。

classAsyncPosterimplementsRunnable {

private finalPendingPostQueuequeue;

private finalEventBuseventBus;

AsyncPoster(EventBus eventBus) {

this.eventBus= eventBus;

queue=newPendingPostQueue();

}

public voidenqueue(Subscription subscription, Object event) {

PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);

queue.enqueue(pendingPost);

eventBus.getExecutorService().execute(this);

}

@Override

public voidrun() {

PendingPost pendingPost =queue.poll();

if(pendingPost ==null) {

throw newIllegalStateException("No pending post available");

}

eventBus.invokeSubscriber(pendingPost);

}

}

總體一句話:三種方式的線程模型與一個方法鏈表組成一個非常標準Android版線程調度器。

線程安全

框架在應對多線程方面做了很多設計

1.粒度鎖

public classEventBus {

private finalMap, CopyOnWriteArrayList>subscriptionsByEventType;

private finalMap>>typesBySubscriber;

private finalMap, Object>stickyEvents;

public voidregister(Object subscriber) {

Class subscriberClass = subscriber.getClass();

List subscriberMethods =subscriberMethodFinder.findSubscriberMethods(subscriberClass);

synchronized (this) {

for (SubscriberMethod subscriberMethod : subscriberMethods) {

subscribe(subscriber, subscriberMethod);

}

}

}

private booleanpostSingleEventForEventType(Object event, PostingThreadState postingState, Class eventClass) {

CopyOnWriteArrayList subscriptions;

synchronized (this) {

subscriptions = subscriptionsByEventType.get(eventClass);

}

…………

}

public booleanhasSubscriberForEvent(Class eventClass) {

List> eventTypes =lookupAllEventTypes(eventClass);

if(eventTypes !=null) {

intcountTypes = eventTypes.size();

for(inth =0; h < countTypes; h++) {

Class clazz = eventTypes.get(h);

CopyOnWriteArrayList subscriptions;

synchronized (this) {

subscriptions = subscriptionsByEventType.get(clazz);

}

if(subscriptions !=null&& !subscriptions.isEmpty()) {

return true;

}

}

}

return false;

}

}

只在使用到需要同步的數(shù)據(jù)結構時,才去synchronized(this),獲取當前對象鎖。另外,對于在部分流程才會使用的數(shù)據(jù)結構并不會占用當前對象鎖,比如:stickyEvents,但問題就出在這里,stickyEvents是ConcurrentHashMap線程安全的,并且粒度比當前對象小。

public classEventBus {

private finalMap, Object>stickyEvents;

public voidpostSticky(Object event) {

synchronized (stickyEvents) {

stickyEvents.put(event.getClass(), event);

}

// Should be posted after it is putted, in case the subscriber wants to remove immediately

post(event);

}

publicTgetStickyEvent(Class eventType) {

synchronized (stickyEvents) {

return eventType.cast(stickyEvents.get(eventType));

}

}

EventBus(EventBusBuilder builder) {

subscriptionsByEventType=newHashMap<>();

typesBySubscriber=newHashMap<>();

stickyEvents = new ConcurrentHashMap<>();

mainThreadPoster=newHandlerPoster(this, Looper.getMainLooper(),10);

backgroundPoster=newBackgroundPoster(this);

asyncPoster=newAsyncPoster(this);

indexCount= builder.subscriberInfoIndexes!=null? builder.subscriberInfoIndexes.size() :0;

subscriberMethodFinder=newSubscriberMethodFinder(builder.subscriberInfoIndexes,

builder.strictMethodVerification, builder.ignoreGeneratedIndex);

logSubscriberExceptions= builder.logSubscriberExceptions;

logNoSubscriberMessages= builder.logNoSubscriberMessages;

sendSubscriberExceptionEvent= builder.sendSubscriberExceptionEvent;

sendNoSubscriberEvent= builder.sendNoSubscriberEvent;

throwSubscriberException= builder.throwSubscriberException;

eventInheritance= builder.eventInheritance;

executorService= builder.executorService;

}

2.數(shù)據(jù)結構的合理使用。

方法列表采用CopyOnWriteArrayList,CopyOnWrite容器即寫時復制的容器。通俗的理解是當往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,復制出一個新的容器,然后新的容器里添加元素,添加完元素之后,再將原容器的引用指向新的容器。這樣做的好處是可以對CopyOnWrite容器進行并發(fā)的讀,而不需要加鎖,因為當前容器不會添加任何元素。所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。缺點就是會造成內存占用雙份,以及數(shù)據(jù)一致性問題(CopyOnWrite容器只能保證數(shù)據(jù)的最終一致性,不能保證數(shù)據(jù)的實時一致性。)考慮到方法只會在注冊的時候進行添加,因此采用偏緩存性質的數(shù)據(jù)結構更適合于當前的應用場景。

線程狀態(tài)信息采用ThreadLocal類型進行保存,ThreadLocal會為每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數(shù)據(jù)的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。

public classEventBus {

private finalMap,CopyOnWriteArrayList>subscriptionsByEventType;

private finalThreadLocalcurrentPostingThreadState=newThreadLocal() {

@Override

protectedPostingThreadState initialValue() {

return newPostingThreadState();

}

};

總結一句話:通過synchronized與ThreadLocal的數(shù)據(jù)類型,構建出線程安全以及簡潔優(yōu)美的代碼。

高性能

1.索引加速

總線注冊時只傳入通過注解標記回調方法的訂閱者對象。EventBus把獲取訂閱方法的過程放在編譯時,避免運行時反射帶來的性能問題

@SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe")

@SupportedOptions(value = {"eventBusIndex", "verbose"})

public class EventBusAnnotationProcessor extends AbstractProcessor {

/** Found subscriber methods for a class (without superclasses). 被注解表示的方法信息 */

private final ListMap methodsByClass = new ListMap<>();

private final Set classesToSkip = new HashSet<>(); // checkHasErrors檢查出來的異常方法

@Override

public boolean process(Set annotations, RoundEnvironment env) {

Messager messager = processingEnv.getMessager();

try {

String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);

if (index == null) { // 如果沒有在gradle中配置apt的argument,編譯就會在這里報錯

messager.printMessage(Diagnostic.Kind.ERROR, "No option " + OPTION_EVENT_BUS_INDEX +

" passed to annotation processor");

return false;

}

/** ... */collectSubscribers(annotations, env, messager);// 根據(jù)注解拿到所有訂閱者的回調方法信息checkForSubscribersToSkip(messager, indexPackage);// 篩掉不符合規(guī)則的訂閱者

if (!methodsByClass.isEmpty()) {createInfoIndexFile(index);// 生成索引類

}

/** 打印錯誤 */

}

/** 下面這些方法就不再貼出具體實現(xiàn)了,我們了解它們的功能就行 */

private void collectSubscribers // 遍歷annotations,找出所有被注解標識的方法,以初始化methodsByClass

private boolean checkHasNoErrors // 過濾掉static,非public和參數(shù)大于1的方法

private void checkForSubscribersToSkip // 檢查methodsByClass中的各個類,是否存在非public的父類和方法參數(shù)

/** 下面這三個方法會把methodsByClass中的信息寫到相應的類中 */

private void writeCreateSubscriberMethods

private void createInfoIndexFile

private void writeIndexLines

}

獲取所有訂閱者的回調方法信息之后,生成Index。運行時就可直接使用Map去查找回調方法。

packagecom.example.wangbinlong.myapplication;

importorg.greenrobot.eventbus.meta.SimpleSubscriberInfo;

importorg.greenrobot.eventbus.meta.SubscriberMethodInfo;

importorg.greenrobot.eventbus.meta.SubscriberInfo;

importorg.greenrobot.eventbus.meta.SubscriberInfoIndex;

importorg.greenrobot.eventbus.ThreadMode;

importjava.util.HashMap;

importjava.util.Map;

/** This class is generated by EventBus, do not edit. */

public classMyEventBusIndeximplementsSubscriberInfoIndex {

private static final Map, SubscriberInfo>SUBSCRIBER_INDEX;

static {

SUBSCRIBER_INDEX= new HashMap, SubscriberInfo>();

putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {

new SubscriberMethodInfo("onLogin", EventBusManager.LoginEvent.class, ThreadMode.MAIN),

}));

putIndex(new SimpleSubscriberInfo(SecondActivity.Inner.class, true, new SubscriberMethodInfo[] {

new SubscriberMethodInfo("onStickyEvent", EventBusManager.StickEvent.class, ThreadMode.MAIN, 0, true),

}));

}

private static voidputIndex(SubscriberInfo info) {

SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);

}

@Override

publicSubscriberInfo getSubscriberInfo(Class subscriberClass) {

SubscriberInfo info =SUBSCRIBER_INDEX.get(subscriberClass);

if(info !=null) {

returninfo;

}else{

return null;

}

}

}

總結一句話:如果項目中通過反射可以獲取的對應關系,可以通過獲取編譯時注解的方式進行索引加速

2.池化技術

EventBus的設計非常精致,但是有一個明顯的缺陷:產生很多中間對象。為了最大限度地減少影響,項目中多處使用緩存,對象池。

METHOD_CACHE緩存訂閱者類型與回調方法的列表,避免重復查找

classSubscriberMethodFinder {

List findSubscriberMethods(Class subscriberClass) {

List subscriberMethods =METHOD_CACHE.get(subscriberClass);

if(subscriberMethods !=null) {

returnsubscriberMethods;

}

if(ignoreGeneratedIndex) {

subscriberMethods = findUsingReflection(subscriberClass);

}else{

subscriberMethods = findUsingInfo(subscriberClass);

}

if(subscriberMethods.isEmpty()) {

throw newEventBusException("Subscriber "+ subscriberClass

+" and its super classes have no public methods with the @Subscribe annotation");

}else{

METHOD_CACHE.put(subscriberClass, subscriberMethods);

returnsubscriberMethods;

}

}

}

private static finalMap, List>METHOD_CACHE=newConcurrentHashMap<>();

eventTypesCache緩存事件類型與(事件類型父類和接口)的關系

private static finalMap, List>>eventTypesCache=newHashMap<>();

/** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */

private staticList> lookupAllEventTypes(Class eventClass) {

synchronized(eventTypesCache) {

List> eventTypes =eventTypesCache.get(eventClass);

if(eventTypes ==null) {

eventTypes =newArrayList<>();

Class clazz = eventClass;

while(clazz !=null) {

eventTypes.add(clazz);

addInterfaces(eventTypes, clazz.getInterfaces());

clazz = clazz.getSuperclass();

}

eventTypesCache.put(eventClass, eventTypes);

}

returneventTypes;

}

}

通過反射查找訂閱方法時,將所有中間對象封裝成FindState.全局維護一個FindState的對象池。使用完對象之后通過recycle()擦除痕跡。

classSubscriberMethodFinder {

privateList findUsingInfo(Class subscriberClass) {

FindState findState = prepareFindState();

findState.initForSubscriber(subscriberClass);

while(findState.clazz!=null) {

findState.subscriberInfo= getSubscriberInfo(findState);

if(findState.subscriberInfo!=null) {

SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();

for(SubscriberMethod subscriberMethod : array) {

if(findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {

findState.subscriberMethods.add(subscriberMethod);

}

}

}else{

findUsingReflectionInSingleClass(findState);

}

findState.moveToSuperclass();

}

returngetMethodsAndRelease(findState);

}

private static finalFindState[]FIND_STATE_POOL=newFindState[POOL_SIZE];

privateList getMethodsAndRelease(FindState findState) {

List subscriberMethods =newArrayList<>(findState.subscriberMethods);

findState.recycle();

synchronized(FIND_STATE_POOL) {

for(inti =0; i

if(FIND_STATE_POOL[i] ==null) {

FIND_STATE_POOL[i] = findState;

break;

}

}

}

returnsubscriberMethods;

}

privateFindState prepareFindState() {

synchronized(FIND_STATE_POOL) {

for(inti = 0; i

FindState state =FIND_STATE_POOL[i];

if(state !=null) {

FIND_STATE_POOL[i] =null;

returnstate;

}

}

}

return newFindState();

}

static classFindState{

final List subscriberMethods = new ArrayList<>();

finalMapanyMethodByEventType=newHashMap<>();

finalMapsubscriberClassByMethodKey=newHashMap<>();

finalStringBuildermethodKeyBuilder=newStringBuilder(128);

ClasssubscriberClass;

Classclazz;

booleanskipSuperClasses;

SubscriberInfosubscriberInfo;

voidinitForSubscriber(Class subscriberClass) {

this.subscriberClass=clazz= subscriberClass;

skipSuperClasses=false;

subscriberInfo=null;

}

void recycle() {

subscriberMethods.clear();

anyMethodByEventType.clear();

subscriberClassByMethodKey.clear();

methodKeyBuilder.setLength(0);

subscriberClass = null;

clazz = null;

skipSuperClasses = false;

subscriberInfo = null;

}

}

回調方法隊列PendPostQueue中的元素PendingPost本身就是對象池。構造器為私有,從而只能通過obtainPendingPost從對象池獲取對象,對象池中有保存的對象則獲取對象(ArrayList尾部獲取),沒有就通過new創(chuàng)建。releasePendingPost則將使用后的對象歸還給對象池,歸還的時候要將對象的使用痕跡擦除,同時要限制對象池大小為10000,防止對象池無限增大。

final classPendingPost {

private final static ListpendingPostPool= new ArrayList();

Objectevent;

Subscriptionsubscription;

PendingPostnext;

privatePendingPost(Object event, Subscription subscription) {

this.event= event;

this.subscription= subscription;

}

staticPendingPost obtainPendingPost(Subscription subscription, Object event) {

synchronized(pendingPostPool) {

intsize =pendingPostPool.size();

if(size >0) {

PendingPost pendingPost =pendingPostPool.remove(size -1);

pendingPost.event= event;

pendingPost.subscription= subscription;

pendingPost.next=null;

returnpendingPost;

}

}

return newPendingPost(event, subscription);

}

static void releasePendingPost(PendingPost pendingPost) {

pendingPost.event = null;

pendingPost.subscription = null;

pendingPost.next = null;

synchronized (pendingPostPool) {

// Don't let the pool grow indefinitely

if (pendingPostPool.size() < 10000) {

pendingPostPool.add(pendingPost);

}

}

}

}

總結一句話:通過編譯時注解解決反射問題,緩存與對象池解決內存開銷問題。

EventBus是通過可配置的門面來維護事件,訂閱者與回調方法之間的關系。然后按照指定線程模型調度執(zhí)行。的使用簡潔,線程安全,高性能的消息總線。最重要的是,它帶給大家一些編程上面的思考。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

  • 原文鏈接:http://blog.csdn.net/u012810020/article/details/7005...
    tinyjoy閱讀 562評論 1 5
  • 先吐槽一下博客園的MarkDown編輯器,推出的時候還很高興博客園支持MarkDown了,試用了下發(fā)現(xiàn)支持不完善就...
    Ten_Minutes閱讀 572評論 0 2
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,785評論 18 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,719評論 18 399
  • 前言RxJava和Retrofit也火了一段時間了,不過最近一直在學習ReactNative和Node相關的姿勢,...
    AFinalStone閱讀 558評論 0 0