自己寫一個EventBus

大名鼎鼎的EventBus很多人一定都用過,這個框架通過利用注解+反射,很好的實現(xiàn)了事件訂閱者與發(fā)布者的解耦。今天我們就手動實現(xiàn)一個簡易版本的EventBus。

先寫我們的EventBus類,模仿真正的EventBus類,我們這個類里面包含的也是標(biāo)準(zhǔn)的幾個方法:

1.void register(Object subscriber)
2.void unregister(Object subscriber)
3.void post(Object eventType)

EventBus里的方法只有這幾個(因為我們是簡易版本的嘛)。當(dāng)然我們的EventBus是一個單例模式類,我用的是雙重檢測null的那種:

private static EventBus defaultInstance;

public static EventBus getDefault(){
        if(defaultInstance==null){
            synchronized (EventBus.class){
                if(defaultInstance==null) defaultInstance=new EventBus();
            }
        }
        return defaultInstance;
    }

接下來是一個很重要的成員變量:

private HashMap<Class<?>,ArrayList<Subscription>> subscriptionsByEventType=new HashMap<>();

解釋一下:HashMap的key為Class對象,其實就是訂閱者中各種onEvent*函數(shù)的參數(shù)的Class對象,用過EventBus的都知道我們可以利用不同的參數(shù)對象實現(xiàn)不同事件的訂閱。而HashMap的value是一個ArrayList,其中就是存儲針對不同的參數(shù)對象的訂閱。利用這個HashMap,可以輕易地找到所有訂閱某一參數(shù)類型事件的所有訂閱者。訂閱信息就包含在類Subscription中。

我們分析一下Subscription類中應(yīng)該包含哪些信息:

1.事件的訂閱者,即subscriber。
2.訂閱的事件,即method。
3.事件應(yīng)該發(fā)生的線程,即ThreadMode。

綜上,我們的類Subscription如下:

public class Subscription {
    public Method method;
    public Object subscriber;
    public ThreadMode mode;

    public Subscription(Method method, Object subscriber, ThreadMode mode) {
        this.method = method;
        this.subscriber = subscriber;
        this.mode = mode;
    }
}

其中ThreadMode就是一個簡單的枚舉,作為演示我只設(shè)置了兩個值:

public enum ThreadMode {
    MainThread,PostThread
}

即post事件所在的線程以及主線程main。

EventBus的成員變量暫時就這些,還有一個稍后再講,接下來就是成員方法的實現(xiàn),我們一個一個來,首先是register方法。先看一下register的全部代碼:

public void register(Object subscriber){
        //根據(jù)反射機(jī)制,查找subscriber中所有已onEvent開頭的method
        Class<?> clazz=subscriber.getClass();
        Method[] methods=clazz.getMethods();
        for(Method method:methods){
            String name=method.getName();
            if(name.startsWith("onEvent")){
                Class<?> param=method.getParameterTypes()[0];
                ArrayList<Subscription> subscriptions=subscriptionsByEventType.get(param);
                if(subscriptions==null){
                    subscriptions=new ArrayList<>();
                    subscriptionsByEventType.put(param,subscriptions);
                }
                //根據(jù)函數(shù)名字決定線程
                if(name.substring("onEvent".length()).length()==0){
                    //onEvent 默認(rèn)為postThread
                    subscriptions.add(new Subscription(method,subscriber, ThreadMode.PostThread));
                }else{
                    //onEventMainThread
                    subscriptions.add(new Subscription(method,subscriber, ThreadMode.MainThread));
                }
            }
        }
    }

首先利用反射獲取訂閱者所在類中所有以"onEvent"開頭的方法,獲取方法的參數(shù),根據(jù)參數(shù)類型去subscriptionsByEventType查找對應(yīng)的ArrayList<Subscription>,如果找不到,說明當(dāng)前還沒有訂閱者訂閱該類型的事件,就新建一個ArrayList<Subscription>,并將該參數(shù)類型與新建的ArrayList插入到HashMap中。

接下來就是構(gòu)建Subscription,這里默認(rèn)onEvent()為發(fā)生在PostThread,而onEventMainThread()發(fā)生在主線程main中。這樣,register方法就結(jié)束,是不是很簡單呢?

unregister:

public void unregister(Object subscriber){
        Class<?> clazz=subscriber.getClass();
        Method[] methods=clazz.getMethods();
        for(Method method:methods){
            String name=method.getName();
            if(name.startsWith("onEvent")){
                Class<?> param=method.getParameterTypes()[0];
                ArrayList<Subscription> subscriptions=subscriptionsByEventType.get(param);
                if(subscriptions!=null){
                    for(Subscription subscription:subscriptions){
                        if(subscription.subscriber==subscriber) subscriptions.remove(subscription);
                    }
                }
            }
        }
    }

懂了register怎么寫的寫unregister就簡單多了,同樣利用反射找到onEvent*方法,獲取參數(shù)類型,然后將該訂閱者的所有訂閱事件Sunscription從HashMap中去除即可。

最后是post方法:

public void post(Object eventType){
        Class<?> clazz=eventType.getClass();
        ArrayList<Subscription> subscriptions=subscriptionsByEventType.get(clazz);
        if(subscriptions==null) Log.d(TAG,"EventBus: no subscriber has subscribed to this event");
        else{
            for(Subscription subscription:subscriptions){
                switch (subscription.mode){
                    case MainThread:
                        mainThreadHandler.post(subscription,eventType);
                        break;
                    case PostThread:
                        try {
                            subscription.method.invoke(subscription.subscriber,eventType);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                        break;
                    default:
                        break;
                }
            }
        }
    }

post中要做的事情是根據(jù)參數(shù)類型,從HashMap中取出所有訂閱該事件的Subscription,然后依次執(zhí)行每一個訂閱。根據(jù)訂閱事件發(fā)生的線程,這里分為兩種情況。PostThread很簡單,直接在post函數(shù)里執(zhí)行即可,具體方法為利用Method.Invoke(Object obj,Object...args);

subscription.method.invoke(subscription.subscriber,eventType);

接下來是要執(zhí)行應(yīng)該發(fā)生在MainThread中的訂閱事件,提到主線程,我們就聯(lián)想到了Handler,我們可以創(chuàng)建一個與主線程關(guān)聯(lián)的Handler,然后將事件的處理交給Handler。

MainThreadHandler:

public class MainThreadHandler extends Handler {
    private Subscription subscription;
    private Object eventType;

    public MainThreadHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        try {
            subscription.method.invoke(subscription.subscriber,eventType);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    public void post(Subscription subscription,Object eventType){
        this.subscription=subscription;
        this.eventType=eventType;
        sendMessage(Message.obtain());
    }
}

然后在建立EventBus的時候建立MainThreadHandler:

private EventBus(){
        mainThreadHandler=new MainThreadHandler(Looper.getMainLooper());
    };

至此,我們的EventBus全部完成,是不是很簡單呢?接下來就是測試。

我在MainActivity的界面中放了兩個 Button,分別對應(yīng)發(fā)生在MainThread以及子線程中。

寫了兩個訂閱函數(shù),訂閱事件類型都是ToastEvent:

public class ToastEvent {
}

MainActivity.java:

public class MainActivity extends AppCompatActivity {
    Button bt;
    Button btAsync;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        EventBus.getDefault().register(this);
        bt= (Button) findViewById(R.id.bt);
        btAsync= (Button) findViewById(R.id.btAsync);
        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                EventBus.getDefault().post(new ToastEvent());
            }
        });
        btAsync.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        EventBus.getDefault().post(new ToastEvent());
                    }
                }).start();
            }
        });
    }

    public void onEvent(ToastEvent event){
        Log.d("YJW","onEvent: "+ Thread.currentThread().getName());
    }

    public void onEventMainThread(ToastEvent event){
        Toast.makeText(this, "Haha on MainThread", Toast.LENGTH_SHORT).show();
        Log.d("YJW","onEventMainThread: "+ Thread.currentThread().getName());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }
}

點擊兩個Button都出現(xiàn)Toast,并打印相應(yīng)日志,上兩條日志為上面的Button,下面兩條日志對應(yīng)下面的Button,測試功能正常。

至此,大功告成。特別說明:真正的EventBus原理大致如此,不過更復(fù)雜,做了很多優(yōu)化與特殊處理,還利用了注解。但作為演示,此demo足夠了。

全部源代碼點此

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • EventBus 是 Android 開發(fā)者們都很熟悉的一個庫,它可以代替Intent、Handler 或者 Br...
    彼岸sakura閱讀 980評論 0 7
  • 先吐槽一下博客園的MarkDown編輯器,推出的時候還很高興博客園支持MarkDown了,試用了下發(fā)現(xiàn)支持不完善就...
    Ten_Minutes閱讀 573評論 0 2
  • 項目到了一定階段會出現(xiàn)一種甜蜜的負(fù)擔(dān):業(yè)務(wù)的不斷發(fā)展與人員的流動性越來越大,代碼維護(hù)與測試回歸流程越來越繁瑣。這個...
    fdacc6a1e764閱讀 3,209評論 0 6
  • 原文鏈接:http://blog.csdn.net/u012810020/article/details/7005...
    tinyjoy閱讀 563評論 1 5
  • 春歸日暮亭頭,雨初收。邀友推杯換盞五更留。聚終散,是離亂。滿風(fēng)樓,千語萬言難說一個愁。
    長安舊人閱讀 296評論 3 12