EventBus原理解析筆記以及案例實戰(結合demo)

筆記概述

  • EventBus簡介

  • EventBus方法介紹

  • EventBus實際運用

EventBus簡介

  • 開源項目地址:https://github.com/greenrobot/EventBus

  • EventBus主頁:http://greenrobot.org/eventBus/

  • github項目地址中關于EventBus的簡介:
    Event bus for Android and Java that simplifies communication between Activities, Fragments, Threads, Services, etc. Less code, better quality.
    即,
    Event 簡化了活動、碎片、進程、服務等之間的通訊方式;
    使APP項目用更少的代碼量實現更好的質量;

  • 關于EventBus的優勢

  • 簡化組件間的通訊方式
    • 解耦合事件發送者和接收者
    • 使活動、碎片和后臺的線程實現更高的執行效率
    • 防止復雜的有錯誤(傾向)的依賴以及生命周期的問題
  • 讓你的代碼簡潔
  • 運行快
  • 庫小
  • EventBus主頁簡潔:
    EventBus is an open-source library for Android and Java using the publisher/subscriber pattern for loose coupling. EventBus enables central communication to decoupled classes with just a few lines of code – simplifying the code, removing dependencies, and speeding up app development.
    即,
    EventBus是一個開源庫,
    使用發布/訂閱機制來對代碼進行解耦。
    簡化項目的集中通訊(僅僅通過幾行代碼就可以解耦各個類),
    移除了一些不必要的依賴,加速移動應用的開發。

關于大項目,如果還是用Java/Android原生的調用,
兩個Activity之間的通訊,
還用startActivity()/startActivityForResult()這類通信方式的話,
代碼會非常的冗余
例如Activity和Fragment之間的通訊就需要不斷地調用相關的函數
使用EventBus可以解除這些耦合;
否則如果代碼耦合性非常大的話,
會大大增加后期維護的難度!



EventBus架構

  • Publisher
    調用post()方法,
    把Event發送到EventBus;

  • EventBus(類似于快遞中心)
    分發Publisher發布的Event
    給對應的Subscriber(訂閱者);

  • Subscriber接收Event;

EventBus概述

  • EventBus是一個Android端優化的
    publish/subscribe消息總線;

  • 簡化了應用程序內各組件間、組件與后臺線程間的通訊;

  • 舉例一個EventBus可簡化代碼的場景:
    請求網絡時候,等網絡返回時通過Handler或Broadcast通知UI;
    兩個Fragment之間需要通過Listener通訊;
    以上都可以用EventBus來代替;

  • EventBus作為一個消息總線,有三個主要的元素:

    • Event:事件
      Event可以是任意對象,
      用來描述傳遞的數據事件類型
      一般Event是由開發者按照需求自己定義的,
      里面封裝要傳遞的事件類型和數據

    • Subscriber:事件訂閱者,接收待定的事件
      在Event中,使用約定制定事件訂閱者簡化使用

      在3.0之前,EventBus還沒使用注解的方式,
      消息處理的方法也僅限于:
      onEventonEventMainThreadonEventBackgroundThreadonEventSync
      分別代表四種線程模型

      在3.0之后消息處理的方法可以隨便取名
      但是需要
      添加一個注解@Subscribe
      并且要指定線程的模型

    • Publisher:事件發布者,用于通知Subscriber有事件發生
      可以在任意線程、任意位置發送事件,
      直接調用EventBuspost(Object)方法即可;

    • 調用EventBus.getDefault()方法,實例化EventBus對象



EventBus線程模型

ThreadMode

ThreadMode指定了會調用的函數,
只能有以下四種(因為每個訂閱事件都是和一個線程模型相關的):
PostThread、 BackgroundThread、 MainThread、 Async

PostThread: 在相同的進程中做EventBus通信

  • 事件的處理事件的發送相同的進程
    所以事件的處理時間不應太長,
    不然會影響事件的發送線程
    而這個線程可能是UI線程

  • 對應的函數名是onEvent
    一般在UI線程使用,
    如果堵塞時間較長則會影響其他線程的刷新
    引起界面的卡頓

    打個比方說你在UI線程中卡了兩秒等下UI就不動,不刷新了

相關地舉一個案例

  • 這里有兩個Activity:

    按下Activity1中的Button,
    會跳轉到Activity2;

    按下Activity2中的button,
    會通過EventBus去通知Activity1;

    Activity1會通過OnEvent接收,
    如果接收到Activity2發送過來消息,
    然后觸發Toast;

接下來新建一個項目,根據官方GitHub添加依賴,

implementation 'org.greenrobot:eventbus:3.1.1'

下面是主布局(Activity1):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/bt_toAc2"
        android:text="跳轉到Activity2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>
  • 然后根據需求定義Event(類似于Module類),
    這里沒什么特別需求,
    定義一個簡單的Event就可以了:
public class MyEvent {

    public String msg;

    public MyEvent() {}

    public MyEvent(String msg) {
        this.msg = msg;
    }
}
  • 接下來,
    要在我們這個“Activity1”里面注冊 OnEvent ,

    這個OnEvent 是在跟 發送事件的線程 同一個線程里面 接收事件的,

    我們這里雖然是分開兩個Activity,
    但是Activity本身就都是在主線程里面的;
    所以這里 事件的發送(在Activity2), 事件的接收(在Activity1)
    都在同一個線程中;


    即,以上所說的PostThread線程類型中,
    事件的發送 跟 事件的接收 是在同一個線程里面的;


    下面注冊一個onEvent(),
    如果接收到Activity2發送過來消息,觸發Toast;
    /**
     * 注冊onEvent(),
     * 注意寫上注解!
     */
    @Subscribe
    public void onEvent(MyEvent event) {
        popOutToast("接收到Event:" + event.msg);
    }
    /**
     * 封裝彈出短時Toast提示
     * @param text 企圖彈出的文本內容
     *
     */
    private void popOutToast(String text) {
        Toast.makeText(MainActivity.this,text,Toast.LENGTH_SHORT).show();
    }
  • 使用EventBus的接收方法的活動,需要在onCreate中注冊
        EventBus.getDefault().register(this);
  • 反注冊
    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }
  • 整個Activity1的java代碼:
public class MainActivity extends AppCompatActivity {

    private Button mButton;

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

        //使用EventBus的接收方法的活動,需要注冊
        EventBus.getDefault().register(this);

        mButton = findViewById(R.id.bt_toAc2);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }

    /**
     * 注冊onEvent(),
     * 注意寫上注解!
     */
    @Subscribe
    public void onEvent(MyEvent event) {
        popOutToast("接收到Event:" + event.msg);
    }

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

    /**
     * 封裝彈出短時Toast提示
     * @param text 企圖彈出的文本內容
     *
     */
    private void popOutToast(String text) {
        Toast.makeText(MainActivity.this,text,Toast.LENGTH_SHORT).show();
    }
}

創建第二個活動SecondActivity,布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/bt_sendMsg"
        android:text="發送消息"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

java:

public class SecondActivity extends AppCompatActivity {

    private Button mButton;

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

        mButton = findViewById(R.id.bt_sendMsg);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().post(new MyEvent("洞妖洞妖我是棟二!!!"));
            }
        });
    }
}

運行效果圖:

Activity1:

跳到Activity2,點擊按鈕,彈出Toast:

補充

  • 以上這種場景,
    如果不用EventBus,
    我們可能需要用到Handler + Runnable的方式,
    但是如果有十個Activity向Activity1發消息,
    我們就需要寫十個Handler了,
    這樣子相當繁瑣;
    而使用EventBus,
    這里只要稍微用代碼注冊一下就可以了,
    明顯方便很多,
    一個方post、一個onEvent,也很輕松地解耦了;
  • 另外一個需要注意的地方就是,
    EventBus.getDefault().register(this);系列的注冊與反注冊代碼,
    onEvent()系列的接收函數是緊密綁定的;
    用時缺一不可,不用時存一不可,同生同滅;

    也就是說一個活動注冊onEvent()系列的接收函數了,
    則必須用EventBus.getDefault().register(this);去注冊,
    不然會報錯;

    而一個活動它沒有寫onEvent()系列的接收函數,
    卻用EventBus.getDefault().register(this);去注冊了,
    同樣也會報錯!
  • onEvent()處理時間比較長,會導致線程堵塞;
    如以下再onEvent()中掛起線程3秒,模擬3秒處理時間:
    @Subscribe
    public void onEvent(MyEvent event) {

        //消耗時間模擬
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        popOutToast("接收到Event:" + event.msg);
    }

接著運行項目的話,
會發現我們在Activity2中點擊發送消息的按鈕之后,
要等到3秒鐘,主線程才會刷新UI(彈出Toast),
這樣子在實際運用中用戶體驗很差;

MainThread

  • 其機制同onEvent()其實是差不多的,
    發送接收都是在同一個線程 主線程 / UI線程中進行;

使用:基于PostThread的代碼,
加多一行(threadMode = ThreadMode.MAIN)即可:

    //MainThread
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEvent(MyEvent event) {

        //消耗時間模擬
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        popOutToast("接收到Event:" + event.msg);
    }

BackgroundThread

  • 事件的處理會在一個后臺線程中執行,
    對應的函數名是onEventBackgroundThread

  • 雖然名字是BackgroundThread,
    事件的處理是在后臺線程,
    但事件的處理時間還是不宜太長;

  • 如果發送事件的線程是在后臺線程,
    會直接執行事件;

  • 如果當前線程是UI線程,
    事件會被加到一個隊列中,
    由一個線程依次處理這些事件,

    如果某個事件處理時間太長,
    會阻塞隊列中 排在后面的事件的派發或處理;

圖解

對于PostThread和MainThread

一次執行
  • 當只有一個線程的時候,
    post(發送)和onEvent()是在同一個線程中去跑的,
    一個線程里面的話,

    宏觀上來說,其代碼便是一次執行的:

對于BackgroundThread

一一對應
  • 這里有兩個線程,UI主線程和后臺線程,
    這個后臺線程 專門用來
    處理onEventBackgroundThread方法及其對應的事件的;

    也就是說,
    比如現在主線程里面有一個post
    它會對應執行到后臺的一個onEventBackgroundThread()
順序執行,前者執行,后者等待阻塞
  • 一個前臺線程的post
    會對應執行到一個后臺線程的onEventBackgroundThread()
    來了第二個post,
    就對應第二個onEventBackgroundThread()

  • 后臺線程中的onEventBackgroundThread()
    是按照post順序依次執行的;
    如果前面一個post對應的onEventBackgroundThread();沒有執行完,
    這時候又post了一下,
    那么對應的后面的這個onEventBackgroundThread()會等待前面一個onEventBackgroundThread()執行完,它才執行;

  • 來個例子,修改MainActivity代碼:
    //BackgroundThread
    @Subscribe(threadMode = ThreadMode.BACKGROUND)
    public void onEvent(MyEvent event) {

        Log.d(TAG, "onEvent Start!!!!!!!! ");
        //消耗時間模擬
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Log.d(TAG, "onEvent End!!!!!!!! ");

//        popOutToast("接收到Event:" + event.msg);
    }
  • 接著運行代碼,
    老規矩,Activity1跳轉到Activity2,
    點擊“發送信息”按鈕,
    連續點擊兩次(根據以上SecondActivity中寫的按鈕點擊事件,
    這里可以理解成連續post兩次,一前一后),
    觀察logcat:

  • 我們可以觀察到兩對Start和End是順序執行的;

  • 執行時候沒有交叉,先第一對,后第二對;
    這里也便驗證了以上理論——
    即,
    一一對應,一個post對應一個event,
    event順序執行,
    前post者對應的event執行中,
    則后post者對應的event等待阻塞;

  • 其實把代碼改成MainThread的,
    再運行,連續點擊三次,
    同樣是能體現一一對應,順序執行,前者執行,后者等待阻塞的特性:

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEvent(MyEvent event) {

        Log.d(TAG, "onEvent Start!!!!!!!! ");
        //消耗時間模擬
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Log.d(TAG, "onEvent End!!!!!!!! ");

//        popOutToast("接收到Event:" + event.msg);
    }
  • 但區別在于,
    Main是執行在主線程,
    而Background是執行在后臺線程,
    而且我們前面說過,
    在主線程中執行占用資源多、占用時間長的任務是不合適的,
    既不規范,也影響體驗;
  • PostThread/MainThread缺點:
    執行在主線程,
    事件的個數,事件的耗時,
    都需要做比較嚴格的限制

  • BackgroundThread缺點:
    運行在后臺線程,不占用主線程資源,
    PostThread/MainThread好那么一點,
    但是還是沒有解決——
    多個(>= 2 個)事件時,
    一次處理一個,依次處理,
    前者執行,后者等待阻塞
    的問題,
    不適合事件中有耗時較長的任務

Async adj.異步的;

sync n.同時,同步;
  • 事件處理會在單獨的線程中執行,
    主要用于在后臺線程中執行耗時操作,
    每個事件會開啟一個線程
    (程序初始化時,已經幫我們創建好一個線程池,
    每次POST一下框架都會去取一個線程來執行),
    但最好限制線程的數目
    (線程過多,CPU使用大,設備耗電快);

    每次POST一下框架都會去取一個線程來執行
    線程池中線程之間互相不干擾,可以同時運行

更改代碼:

    //AsyncThread
    @Subscribe(threadMode = ThreadMode.ASYNC)
    public void onEvent(MyEvent event) {

        Log.d(TAG, "onEvent Start!!!!!!!! ");
        //消耗時間模擬
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Log.d(TAG, "onEvent End!!!!!!!! ");
    }

執行效果:

這次我們可以看到,
事件的處理就沒有——
一次處理一個,依次處理,前者執行中,后者等待阻塞的特性了,
因為各個post的事件,
都有各自獨立的線程去處理,
所以事件的處理運行是同時的、異步的;

小結

  • PostThread:發送和接收在同一個線程;
  • MainThread:發送和接收同在主線程;
    (應用范圍上被PostThread包括)
  • BackgroundThread
  • PostThread/MainThread缺點:
    一般執行在主線程,
    事件的個數,事件的耗時,
    都需要做比較嚴格的限制

  • BackgroundThread缺點:
    運行在后臺線程,不占用主線程資源,
    PostThread/MainThread好那么一點,
    但是還是沒有解決——
    多個(>= 2 個)事件時,
    一次處理一個,依次處理,
    前者執行,后者等待阻塞
    的問題,
    不適合事件中有耗時較長的任務

以上三種線程都是不適合跑耗時操作的;

Async adj.異步的;
核心:異步,同時,高效

  • 事件處理會在單獨的線程中執行,
    主要用于在后臺線程中執行耗時操作,
    每個事件會開啟一個線程
    (程序初始化時,已經幫我們創建好一個線程池,
    每次POST一下框架都會去取一個線程來執行),
    但最好限制線程的數目
    (線程過多,CPU使用大,設備耗電快);

    每次POST一下框架都會去取一個線程來執行
    線程池中線程之間互相不干擾,可以同時運行

補充:

  • 優先級(priority)就是字面上的意義,
    值越高,優先級越高;

  • sticy即粘性發送,
    發送方法EventBus.getDefault().postSticky(new MyEvent());
    注銷粘性event的方法EventBus.getDefault().removeStickyEvent(new MyEvent());
    發送(postSticky)的時候,
    項目中有多少Fragment、Activity等載體,
    事件MyEvent就發送多少份;
    粘性其意義在于,
    無論項目中載體類中
    是否使用EventBus.getDefault().register(this);對EventBus注冊過,
    都會對其發送事件,

    若載體注冊了,則接收處理該粘性事件;
    若載體未注冊,則該粘性事件會緩存起來,
    一旦載體注冊,馬上接收處理事件

    但由于這種粘性發送在項目比較大的時候
    需要占用一定量的緩存資源,
    所以一般使用較少;

  • 另外一個需要注意的地方就是,
    EventBus.getDefault().register(this);系列的注冊與反注冊代碼,
    onEvent()系列的接收函數是緊密綁定的;
    用時缺一不可,不用時存一不可,同生同滅;

    也就是說一個活動注冊onEvent()系列的接收函數了,
    則必須用EventBus.getDefault().register(this);去注冊,
    不然會報錯;

    而一個活動它沒有寫onEvent()系列的接收函數,
    卻用EventBus.getDefault().register(this);去注冊了,
    同樣也會報錯!

使用技巧

  • 事件只需要傳遞一個狀態 / 指令,無需傳遞數據時,
    event自定義類內容可以為空;


    比如一個只需要傳遞“清空位置信息列表”這個指令的事件,
    可以這么定義:

    就是定義一個Event類,但是內容為空;

    無需傳遞數據,
    僅僅event類的類名已經具備傳遞的事件、指令意義;

  • 一個Fragment或者Activity需要接收處理多個Event時候,
    通過建立多個注解方法,并以不同的event 形參
    區分處理,接收到不同的event時,該做的對應的邏輯





參考資料:慕課網

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 本篇文章主要講解關于EventBus的使用,下篇文章會根據EventBus的使用具體講解其中的原理。雖然現在RxJ...
    Ruheng閱讀 6,945評論 8 42
  • 先吐槽一下博客園的MarkDown編輯器,推出的時候還很高興博客園支持MarkDown了,試用了下發現支持不完善就...
    Ten_Minutes閱讀 582評論 0 2
  • 我每周會寫一篇源代碼分析的文章,以后也可能會有其他主題.如果你喜歡我寫的文章的話,歡迎關注我的新浪微博@達達達達s...
    SkyKai閱讀 25,010評論 23 184
  • EventBus是一個Android開源庫,其使用發布/訂閱模式,以提供代碼間的松耦合。EventBus使用中央通...
    壯少Bryant閱讀 669評論 0 4
  • 文章基于EventBus 3.0講解。首先對于EventBus的使用上,大多數人還是比較熟悉的。如果你還每次煩于使...
    Hohohong閱讀 2,334評論 0 6