1. 程式人生 > >事件總線模式——實例講解

事件總線模式——實例講解

clas etc cti justify private pty 繼承 substring 消息傳遞

上一篇博客我們講解了事件總線模式的概念及其實現原理 ,我們在實際運用中,大多數是在Android開發中運用到事件總線模式,它實現了事件訂閱者和事件發布者的解耦,讓我們更加容易在actvity等組件間傳遞信息。這篇文章通過這個簡單的實例,給大家說明EventBus實現的原理,一起來打造一個簡單的事件總線框架。如果你明白了這個框架的設計原理,那麽EventBus也就相差不大,兩者比起來只是後者更加完善和高效。

由於是模仿EventBus寫得,所以我也把這個“框架”稱為EventBus,先來看一下我們怎麽使用這個框架。

現在我們實現的一個效果是,有兩個acivity如圖MainActivity:

當點擊"click"按鈕的時候,就會跳轉到secondActivity,而secondActivity裏面有兩個按鈕,點擊以後,可以改變MainActivity裏面TextView的文字。

技術分享圖片技術分享圖片

這其實就是一個消息傳遞的過程,假設我們用EventBus,下面我們來看一下,代碼是怎麽寫的。

public class MainActivity extends FragmentActivity {  
  
    Button btn;  
    TextView text;  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        
super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); EventBus.getInstance().register(this); btn = (Button) findViewById(R.id.btn); text = (TextView) findViewById(R.id.text); btn.setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View v) { startActivity(new Intent(MainActivity.this,SecondActivity.class)); } }); } public void onEvent(Info i){ Log.i("cky", i.msg); } public void onEventMain(Info i){ text.setText(i.msg); } public void onEventMain(Info2 i){ text.setText(text.getText()+i.msg); } }

可以看到,我們在onCreate()裏面調用了EventBus.register(this),來註冊事件然後又兩者函數,一個是onEvent(),這個方法裏面的代碼,會在一個子線程中執行一個是onEventMain(),這個方法裏面的代碼,會在UI線程執行。在這裏,我們改變了TextView中的文字。使用過EventBus的朋友,現在看來使用方式是不是跟EventBus很像(我就是想實現EventBus的效果啊)。EventBus對事件在不同線程中處理,有四種方式,但是我們這裏只寫了簡單的兩者,主線程和非主線程,這樣代碼更加簡單,而且原理是一樣的,會寫兩個,就會寫多個。然後SecondActivity裏面,大家一定知道是怎麽回事,肯定是在點擊裏面調用了post()方法。

public class SecondActivity extends Activity {  
  
    Button btn2;  
    Button btn3;  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_second);  
        btn2 = (Button) findViewById(R.id.btn2);  
        btn2.setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                EventBus.getInstance().post(new Info("信息1"));  
            }  
        });  
        btn3 = (Button) findViewById(R.id.btn3);  
        btn3.setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                EventBus.getInstance().post(new Info2("信息2"));  
            }  
        });  
    }  
}  

就是這麽簡單,使用方法跟EventBus如出一轍。

大家可能會好奇到底怎麽樣用簡單的代碼,實現這樣的效果,下面我帶大家看我的代碼。!

先看EventBus

public class EventBus {  
    HashMap<Class<?>,ArrayList<Subscription>> subscriptionsByEventType =  
            new HashMap<Class<?>,ArrayList<Subscription>>();  
    MainThreadHandler mainThreadHandler = new MainThreadHandler(this,Looper.getMainLooper());  
    AsyThreadHandler asyThreadHandler = new AsyThreadHandler(this);  
  
    private final static EventBus instance = new EventBus();  
    public static EventBus getInstance(){  
           return instance;  
    }  
    private EventBus(){};  
}  

有幾個地方:

1,將構造函數設為私有,使用了getInstance()來給外界提供EventBus實例,其實就是一個單例模式

2,有幾個復雜的屬性,首先是subscriptionsByEventType,這是一個map,key代表某個bean類。

使用過EventBus都知道,我們在傳遞信息的時候,要傳遞一個實體類,例如我上面的例子,就是Info這個類。

value值是一個ArrayList<Subscription>,Subscription的意義就是一個訂閱,是一個我創建的實體類。這個類的具體含義留到後面講。

剩下兩個mainThreadHandler和asyThreadHandler,顧名思義就是分別用來處理主線程和子線程的,都是我創建的類。

看到這裏可能大家有點糊塗,出現了三個類,別急,我一定會解釋清楚。現在先讓我們來看非常重要的register()方法。

public void register(Object subscriber){  
        Class<?> clazz = subscriber.getClass();//獲取訂閱者的類型  
        Method[] methods = clazz.getMethods();//獲取該類的所有方法  
        for(Method m:methods){//遍歷方法  
            String name = m.getName();//獲取方法名  
            if(name.startsWith("onEvent")){//判斷方法名是否以"onEvent"開頭             </span>  
                Class<?>[] params = m.getParameterTypes();//獲取該方法的參數類型  
                ArrayList<Subscription> arr;  
                if(subscriptionsByEventType.containsKey(params[0])){  
                    arr = subscriptionsByEventType.get(params[0]);  
                }else{  
                    arr = new ArrayList<Subscription>();  
                }  
                int len = name.substring("onEvent".length()).length();//截取方法除"onEvent"部分    
                Subscription sub;  
                if(len==0){//如果剩余長度為0,說明是子線程中執行  
                    sub =  new Subscription(subscriber,new SubscriberMethod(m,params[0],0));  
                }else{//否則,在主線程執行  
                    sub =  new Subscription(subscriber,new SubscriberMethod(m,params[0],1));  
                }  
                arr.add(sub);  
                subscriptionsByEventType.put(params[0],arr);  
            }  
        }  
    } 

從上面的註釋我們可以看到,我們先獲取了訂閱者(例子中是MainActivity)的方法,找到onEvent開頭的方法,獲得它們的參數類型

然後判斷subscriptionsByEventType是否有以這些參數類型為key的數據,如果沒有,新建一個ArrayList<Subscription>。

然後我們先看Subscription

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

它代表一個訂閱,擁有subsriber,也就是訂閱者

還有一個SubscriberMethod,這是訂閱方法類

public class SubscriberMethod {  
    Method m;  
    int type;  
    public SubscriberMethod(Method m, Class<?> param, int type) {  
        this.m = m;  
        this.type = type;  
    }  
}  

它有一個Method m屬性,就是我們註冊的方法,另外一個type就是標記,0代表m在子線程中執行,其余代表在主線程中執行。

所以總的來說register()通過遍歷訂閱者,找到訂閱方法(onEvent(),onEventMain()),將方法包裝成SubscriberMethod類,再將SubscriberMethod和訂閱者一起,

包裝成Subscription類。

而subscriptionsByEventType根據參數類型為key,所謂參數類型,在例子中就是Info.class和info2.class

為什麽我們要用參數類型為鍵呢?我們來想,以後我們在post的時候,是這樣調用post的:

EventBus.getINstance().post(new Info("信息"));  

我們要傳進去一個參數,而EventBus的任務就是,調用所有註冊了這個參數類型的訂閱方法。

所有顯然,我們要根據參數類型,找到這些方法,這是key是參數類型的原因了。

Ok,register()方法下來,最重要的一件事就是我們得到了subscriptionsByEventType

接下來我們就可以看post方法了

public void post(Object event){  
        Class<?> clazz = event.getClass();  
        ArrayList<Subscription> arr = subscriptionsByEventType.get(clazz);  
        for(Subscription sub:arr){//遍歷訂閱  
            if(sub.SubscriberMethod.type==0){  
                asyThreadHandler.post(sub,event);  
            }else{  
                mainThreadHandler.post(sub,event);  
            }  
        }  
    }  

post()方法裏面,如同我們上面所說,獲取了參數類型,然後在subscriptionsByEventType中查詢所有改類型對應的訂閱Subscription

對於Subscription,它有我們訂閱類的所有信息。

首先根據type判斷是在主線程還是子線程執行,然後調用一開始講到的兩個類的實例就好了。

我們先看asyThreadHandler,這個會在子線程執行

public class AsyThreadHandler {  
    private EventBus eventBus;  
    AsyThreadHandler(EventBus eventBus) {  
        this.eventBus = eventBus;  
    }  
  
    public void post(final Subscription sub, final Object event){  
        new Thread(){  
            @Override  
            public void run() {  
                eventBus.invoke(sub,event,sub.SubscriberMethod.m);  
            }  
        }.start();  
    }  
}  

代碼很簡單,其實它的post方法,就是啟用了一個線程,然後在線程裏面,調用了EventBus的invoke()方法。

我們看這個方法

public void invoke(final Subscription sub, final Object event,Method m){  
        try {  
            m.invoke(sub.subscriber,event);  
        } catch (IllegalAccessException e) {  
            e.printStackTrace();  
        } catch (InvocationTargetException e) {  
            e.printStackTrace();  
        }  
    }  

其實只有一句話,就是調用了反射去執行方法。m是訂閱方法,sub.subscriber就是訂閱者,event就是post()方法傳入的實體

這樣我們就在子線程中調用了這個方法了,相當於MainActivity主動調用這個方法。

那麽在主線程中呢,大同小異

public class MainThreadHandler extends Handler{  
    private final EventBus eventBus;  
  
    MainThreadHandler(EventBus eventBus, Looper looper) {  
        super(looper);  
        this.eventBus = eventBus;  
    }  
  
    @Override  
    public void handleMessage(Message msg) {  
        EventBus.getInstance().invoke(sub,event,sub.SubscriberMethod.m);  
    }  
  
    Subscription sub;  
    Object event;  
    public void post(Subscription sub, Object event){  
        this.sub = sub;  
        this.event = event;  
        sendEmptyMessage(0);  
    }  
}  

可以看到繼承了handler,表明在主線程中調用。

post方法傳遞了一個空message,在handlerMessage()方法裏面,又是調用了EventBus的invoke()方法。

殊途同歸,最後都是調用EventBus的invoke()方法,不過一個在子線程中調用,一個在主線程中調用。

就這樣,我們輕而易舉得實現了EventBus的基本功能哦!

是不是覺得代碼很簡單,其實上面的代碼,已經將EventBus框架的思路將清楚了,大家明白這思路,再看EventBus就容易多了。

事件總線模式——實例講解