從零開始“擼”出自己的EventBus(一)

轉(zhuǎn)載文章請(qǐng)標(biāo)明出處http://www.reibang.com/p/476a3a6dc789
其他同系列文章

  1. 從零開始“擼”出自己的EventBus(二)
  2. 從零開始“擼”出自己的EventBus(三)

當(dāng)初學(xué)Android的時(shí)候,無(wú)意間發(fā)現(xiàn)了EventBus這個(gè)庫(kù)寞冯,就像發(fā)現(xiàn)了一片新大陸一樣。居然可以直接在Android的組件之間互相傳遞數(shù)據(jù),為啥作者就這么牛逼呢?為啥能弄出這么叼的東西呻征?隨著知識(shí)的不斷累計(jì),發(fā)現(xiàn)其實(shí)當(dāng)初覺得非常神秘,遙不可及的東西悠菜,其實(shí)原理相當(dāng)?shù)暮?jiǎn)單。對(duì)败富,簡(jiǎn)單到你也可以寫出來(lái)的悔醋。下面,我就來(lái)教教大家如何從零開始“擼”出自己的EventBus兽叮。當(dāng)然篙顺,你得具備一定得Java基礎(chǔ)以及Android基礎(chǔ),不然你也不會(huì)看到這篇文章~

原理

先來(lái)講講原理啊~網(wǎng)上得說(shuō)法幾乎都是講EventBus是基于觀察者模式的充择。嗯,那么究竟誰(shuí)是觀察者匪蟀,誰(shuí)是被觀察者呢椎麦?EventBus這個(gè)東東本身是觀察者還是被觀察者?按照我的理解材彪,EventBus它既不是觀察者观挎,也不是被觀察者,它不過(guò)是連接觀察者和被觀察者之間的橋梁段化。那么觀察者是誰(shuí)嘁捷?觀察者就是在你調(diào)用EventBus.getDefault().register(this)中的'this',它可能是個(gè)Activity、Fragment显熏,也可能是個(gè)Service啥的(其實(shí)更準(zhǔn)確的說(shuō)應(yīng)該是其中你onEvent開頭或標(biāo)記了Subscribe注解的方法)雄嚣。那么被觀察者自然就是所有可以向EventBus傳遞消息對(duì)象。這里盜用一張EventBus官網(wǎng)的圖。

EvenBus原理圖

夢(mèng)想起航

好的缓升,了解了原理我們現(xiàn)在開始來(lái)“擼”代碼鼓鲁。從第一步開始,首先建個(gè)工程骇吭,起名就叫MiniBus吧,畢竟我們沒(méi)有人家強(qiáng)大燥狰。等我們強(qiáng)大起來(lái)就改名叫SuperBus斜筐。哈哈哈哈哈哈龙致。。奴艾。。

<br />首先新建個(gè)類就叫MiniBus,和工程名相同像啼。首先明確其需要基于單例模式,畢竟不希望使用者可以new出來(lái)好幾個(gè)MiniBus潭苞,那樣的話這些觀察者就沒(méi)法管理了忽冻。單例這種東西對(duì)于身經(jīng)百戰(zhàn)的我們來(lái)說(shuō)是So easy。在這里此疹,我就隨意選了一種單例的寫法。

public class MiniBus {
    private MiniBus() {
    }

    private static class BusHolder {
        private static MiniBus instance = new MiniBus();
    }

    public static MiniBus getInstance() {
        return BusHolder.instance;
    }
}

然后呢湖笨?我們需要暴露出我們的方法蹦骑。包括觀察者注冊(cè)的方法register()、觀察者取消注冊(cè)的方法unregister()眠菇、被觀察者發(fā)送事件的方法post()。嗯笑窜,暫時(shí)就這三個(gè)方法吧登疗。

//觀察者注冊(cè)
public void register(Object subscriber){}

//觀察者取消注冊(cè)
public void unregister(Object subscriber){}

//被觀察者發(fā)送消息,在此暫時(shí)不考慮粘性事件等復(fù)雜情況
public void post(Object event){}

至此匾寝,MiniBus大致的骨架已經(jīng)搭建好了,接下來(lái)就要依照這些骨架填充好其血肉啦(為啥感覺這句話這么血腥艳悔?)。<br />

沖浪~

首先來(lái)看看register()方法該做一些什么工作抡锈。

初級(jí)方案

最剛開始我想的就是直接把注冊(cè)的觀察者直接存到一個(gè)List集合里面乔外,然后當(dāng)有post請(qǐng)求事件發(fā)出的時(shí)候遍歷這些觀察者拿到對(duì)應(yīng)的該執(zhí)行的方法,然后通過(guò)反射拿到這些方法去執(zhí)行撇簿。當(dāng)然,這種方法是可行的四瘫,但是存在一個(gè)問(wèn)題欲逃。那就是每次有事件發(fā)出都要去遍歷一遍觀察者的所有方法,效率很低洗做。

好一點(diǎn)的方案

改進(jìn)的方案就是以犧牲空間換取時(shí)間的方法彰居。在注冊(cè)的時(shí)候直接遍歷出該對(duì)象的有效方法,然后存儲(chǔ)為和subscriber對(duì)應(yīng)的Map類型的數(shù)據(jù),這樣的話陈惰,每次post事件發(fā)出是僅僅只需要遍歷有效的方法,效率上要比初級(jí)方案遍歷每次都遍歷所有的方法要高很多。

更好的方案

第二種方案雖然提升了不小的效率画髓,但是仍然會(huì)在每次請(qǐng)求的時(shí)候遍歷所有的有效方法平委。通過(guò)仔細(xì)思考,在第二種方案上做一絲絲改動(dòng),就是不以subscriber為Map的key值匾鸥,而是以event的類型作為Map的key值碉纳,這樣的話就不用每次post請(qǐng)求可以直接通過(guò)key值取出對(duì)應(yīng)的方法,都遍歷一遍那么多的方法啦~<br />
雖然第三種方案相對(duì)最好奴愉,但是這里我為了簡(jiǎn)單起見铁孵,選擇了折中的第二種方案。下面我們就來(lái)徒手?jǐn)]代碼蜕劝。

public class MiniBus {
    
    private MiniBus() {
    }

    private static class BusHolder {
        private static MiniBus instance = new MiniBus();
        //存儲(chǔ)觀察者以及其有效的觀察者方法
        private static Map<Object, List<Method>> subMethods = new HashMap<>();
    }

    public static MiniBus getInstance() {
        return BusHolder.instance;
    }

    public void register(Object subscriber) {

        Class<?> subClass = subscriber.getClass();
        Method[] methods = subClass.getDeclaredMethods();
        List<Method> usefulMethods = new ArrayList<>();
        //遍歷觀察者所有的方法岖沛,拿到所有有效的方法
        for (Method method : methods) {
            if (checkIsUsefulMethod(method)) {
                usefulMethods.add(method);
            }
        }

        if (usefulMethods.size() > 0) {
            BusHolder.subMethods.put(subscriber, usefulMethods);
        }
    }
    
    /**
     * 判斷一個(gè)方法是否是有效的觀察者方法,
     * 在此先采用EventBus3.0版本以前的方式烫止,
     * 也就是以onEvent開頭的方法馆蠕,返回值為void
     * 并且只有一個(gè)參數(shù),即視為有效方法
     */
    private boolean checkIsUsefulMethod(Method method) {
        String methodName = method.getName();
        Class<?> returnType = method.getReturnType();
        Class<?>[] paramsClass = method.getParameterTypes();
        if (methodName.startsWith("onEvent")
                && returnType.equals(Void.TYPE)
                && paramsClass.length == 1) {
            return true;
        }
        return false;
    }
}

接下來(lái)看看unregister()方法的實(shí)現(xiàn)互躬。那就簡(jiǎn)單了吼渡,直接移除該key值以及對(duì)應(yīng)的數(shù)據(jù)就好了。

public void unregister(Object subscriber) {
    BusHolder.subMethods.remove(subscriber);
}

post()方法的實(shí)現(xiàn)剛才已經(jīng)講過(guò)了寺酪,就是遍歷所有的觀察者方法,然后比對(duì)事件類型得滤,若事件類型相同即為對(duì)應(yīng)的觀察者方法盒犹,即對(duì)其發(fā)送post事件消息眨业。

public void post(final Object event) {
        Class<?> eventClass = event.getClass();
        Set<Object> keys = BusHolder.subMethods.keySet();
        for (final Object key : keys) {
            List<Method> methods = BusHolder.subMethods.get(key);
            for (final Method method : methods) {
                if (method.getParameterTypes()[0].equals(eventClass)) {
                    //打開對(duì)私有方法的調(diào)用權(quán)限
                    method.setAccessible(true);
                    try {
                        method.invoke(key, event);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

至此沮协,一個(gè)簡(jiǎn)單的EventBus原型已完成慷暂。雖然它還存在不少問(wèn)題,但是EventBus最核心的功能已經(jīng)實(shí)現(xiàn)呜呐。讓我們建兩個(gè)Activity試試我們自己徒手?jǐn)]出來(lái)的EventBus。<br />

著陸

第一個(gè)Activity測(cè)試代碼洋机。

public class MainActivity extends AppCompatActivity {
    private boolean first = true;
    private int count = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MiniBus.getInstance().register(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        if(first){
            startActivity(new Intent(this, SecondActivity.class));
            first = false;
        }
    }

    //觀察方法
    private void onEventMain(String str) {
        Toast.makeText(getApplicationContext(), str, Toast.LENGTH_SHORT).show();
        ((TextView) findViewById(R.id.tv))
                .setText("你點(diǎn)了" + (++count) + "下");
    }
}

第二個(gè)Activity測(cè)試代碼洋魂。

public class SecondActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        findViewById(R.id.bt).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new Thread(){
                    @Override
                    public void run() {
                        MiniBus.getInstance().post("我?guī)?);
                    }
                }.start();
            }
        });
    }
}

在第一個(gè)Activity里面定義了一個(gè)OnEventMain()的觀察方法副砍,向MiniBus注冊(cè)了觀察者,并在onResume()的時(shí)候直接跳轉(zhuǎn)到第二個(gè)Activity豁翎。第二個(gè)Activity設(shè)置了一個(gè)點(diǎn)擊事件,點(diǎn)擊的時(shí)候通知第一個(gè)Activity邦尊。熟悉EventBus的朋友應(yīng)該很熟悉优烧,就和EventBus2.x版本的用法幾乎一樣。然后運(yùn)行項(xiàng)目畦娄,發(fā)現(xiàn)點(diǎn)擊按鈕第一個(gè)Activity能成功接收并處理消息熙卡。Oh,yes!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市滑燃,隨后出現(xiàn)的幾起案子喂柒,更是在濱河造成了極大的恐慌,老刑警劉巖灾杰,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件艳吠,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡昭娩,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門呛梆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)磕诊,“玉大人,你說(shuō)我怎么就攤上這事滞磺±嘲” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵阅茶,是天一觀的道長(zhǎng)炮障。 經(jīng)常有香客問(wèn)我,道長(zhǎng)胁赢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任谅摄,我火速辦了婚禮系馆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闽寡。我一直安慰自己,他們只是感情好爷狈,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布涎永。 她就那樣靜靜地躺著,像睡著了一般羡微。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上博投,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天启涯,我揣著相機(jī)與錄音,去河邊找鬼黎做。 笑死松忍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鸣峭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼爬骤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼莫换!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起坷剧,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤喊暖,失蹤者是張志新(化名)和其女友劉穎陵叽,沒(méi)想到半個(gè)月后丛版,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡硼婿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了刊殉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逸月,死狀恐怖遍膜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情恩尾,我是刑警寧澤挽懦,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站冀偶,受9級(jí)特大地震影響渔嚷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜形病,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一窒朋、第九天 我趴在偏房一處隱蔽的房頂上張望搀罢。 院中可真熱鬧侥猩,春花似錦、人聲如沸唧取。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)淡诗。三九已至,卻和暖如春韩容,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背群凶。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工请梢, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人毅弧。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓够坐,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親咆霜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

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