《NetkillerAndroid手札》之EventBus使用詳解

netkiller發表於2018-11-08

本文節選自電子書《Netkiller Android 手札》

Netkiller Android 手札

http://www.netkiller.cn/android/index.html

Mr. Neo Chan, 陳景峰(BG7NYT)

中國廣東省深圳市望海路半島城邦三期
518067
+86 13113668890

<netkiller@msn.com>

$Id: book.xml 606 2013-05-29 09:52:58Z netkiller $

版權 © 2018 Neo Chan

版權宣告

轉載請與作者聯絡,轉載時請務必標明文章原始出處和作者資訊及本宣告。

by-nc-sa.png
http://www.netkiller.cn
http://netkiller.github.io
http://netkiller.sourceforge.net
weixin.jpg
微信訂閱號 netkiller-ebook (微信掃描二維碼)
QQ:13721218 請註明“讀者”
QQ群:128659835 請註明“讀者”

2018-10

我的系列文件

程式語言

Netkiller Architect 手札 Netkiller Developer 手札 Netkiller Java 手札 Netkiller Spring 手札 Netkiller PHP 手札 Netkiller Python 手札
Netkiller Testing 手札 Netkiller Cryptography 手札 Netkiller Perl 手札 Netkiller Docbook 手札 Netkiller Project 手札 Netkiller Database 手札

第 47 章 EventBus

目錄

47.1. 新增 EventBus 依賴到專案Gradle檔案

47.2. 快速開始一個演示例子

47.2.1. 建立 MessageEvent 類

47.2.2. Layout

47.2.3. Activity

47.3. Sticky Events

47.3.1. MainActivity

47.3.2. StickyActivity

47.3.3. MessageEvent

47.3.4. 刪除粘性事件

47.4. 執行緒模型

47.5. 配置 EventBus 

47.6. 事件優先順序

47.7. 捕獲異常事件

http://greenrobot.org/eventbus

在EventBus中主要有以下三個成員:

Event:事件,可以自定義為任意物件,類似Message類的作用;
Publisher:事件釋出者,可以在任意執行緒、任意位置釋出Event,已釋出的Evnet則由EventBus進行分發;
Subscriber:事件訂閱者,接收並處理事件,需要通過register(this)進行註冊,而在類銷燬時要使用unregister(this)方法解註冊。每個Subscriber可以定義一個或多個事件處理方法,其方法名可以自定義,但需要新增@Subscribe的註解,並指明ThreadMode(不寫預設為Posting)。

47.1. 新增 EventBus 依賴到專案Gradle檔案

Gradle:

implementation `org.greenrobot:eventbus:3.1.1`

完整的例子

apply plugin: `com.android.application`

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "cn.netkiller.eventbus"
        minSdkVersion 26
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile(`proguard-android.txt`), `proguard-rules.pro`
        }
    }
}

dependencies {
    implementation fileTree(dir: `libs`, include: [`*.jar`])
    implementation `com.android.support:appcompat-v7:28.0.0`
    implementation `com.android.support.constraint:constraint-layout:1.1.3`
    testImplementation `junit:junit:4.12`
    androidTestImplementation `com.android.support.test:runner:1.0.2`
    androidTestImplementation `com.android.support.test.espresso:espresso-core:3.0.2`
    implementation `org.greenrobot:eventbus:3.1.1`
}

47.2. 快速開始一個演示例子

操作 EventBus 只需四個步驟

1. 註冊事件

EventBus.getDefault().register( this );

2. 取消註冊

EventBus.getDefault().unregister( this );

3. 訂閱事件

	@Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }

4. 傳送資料

EventBus.getDefault().post(new MessageEvent("Helloworld"));

47.2.1. 建立 MessageEvent 類

package cn.netkiller.eventbus.pojo;

public class MessageEvent {
    public final String message;

    public MessageEvent(String message) {
        this.message = message;
    }
}

47.2.2. Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

</android.support.constraint.ConstraintLayout>

47.2.3. Activity

package cn.netkiller.eventbus;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import cn.netkiller.eventbus.pojo.MessageEvent;

public class MainActivity extends AppCompatActivity {

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

        EventBus.getDefault().register(this);

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        //取消註冊 , 防止Activity記憶體洩漏
        EventBus.getDefault().unregister(this);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }
}

47.3. Sticky Events

Sticky Events 粘性事件可以理解為Message做了持久化,直到Message被消費為止。無需註冊即可傳送Message。

下面的例子:在MainActivity傳送事件,在StickyActivity裡註冊並且接收事件

A. MainActivity 傳送事件:

EventBus.getDefault().postSticky(new MessageEvent("http://www.netkiller.cn"));

B. StickyActivity 接收事件 

1. 註冊

EventBus.getDefault().register( this );

2. 事件接收

	@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }

3. 取消註冊

EventBus.getDefault().unregister( this ) ;

47.3.1. MainActivity

Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

</android.support.constraint.ConstraintLayout>

MainActivity

package cn.netkiller.eventbus;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import cn.netkiller.eventbus.pojo.MessageEvent;

public class MainActivity extends AppCompatActivity {

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

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
                startActivity(new Intent(MainActivity.this, StickyActivity.class));
            }
        });

    }

}

47.3.2. StickyActivity

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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=".StickyActivity">

</android.support.constraint.ConstraintLayout>

StickyActivity

package cn.netkiller.eventbus;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import cn.netkiller.eventbus.pojo.MessageEvent;

public class StickyActivity extends AppCompatActivity {

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

        EventBus.getDefault().register(this);
    }

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

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }

}

47.3.3. MessageEvent

package cn.netkiller.eventbus.pojo;

public class MessageEvent {
    public final String message;

    public MessageEvent(String message) {
        this.message = message;
    }
}

47.3.4. 刪除粘性事件

MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);

// Better check that an event was actually posted before
if(stickyEvent != null) {
	// "Consume" the sticky event
	EventBus.getDefault().removeStickyEvent(stickyEvent);
	// Now do something with it
}

47.4. 執行緒模型

EventBus 有五種執行緒模型(ThreadMode) 

Posting:直接在事件釋出者所線上程執行事件處理方法;
Main:直接在主執行緒中執行事件處理方法(即UI執行緒),如果釋出事件的執行緒也是主執行緒,那麼事件處理方法會直接被呼叫,並且未避免ANR,該方法應避免進行耗時操作;
MainOrdered:也是直接在主執行緒中執行事件處理方法,但與Main方式不同的是,不論釋出者所線上程是不是主執行緒,釋出的事件都會進入佇列按事件序列順序依次執行;
BACKGROUND:事件處理方法將在後臺執行緒中被呼叫。如果釋出事件的執行緒不是主執行緒,那麼事件處理方法將直接在該執行緒中被呼叫。如果釋出事件的執行緒是主執行緒,那麼將使用一個單獨的後臺執行緒,該執行緒將按順序傳送所有的事件。
Async:不管釋出者的執行緒是不是主執行緒,都會開啟一個新的執行緒來執行事件處理方法。如果事件處理方法的執行需要一些時間,例如網路訪問,那麼就應該使用該模式。為避免觸發大量的長時間執行的事件處理方法,EventBus使用了一個執行緒池來有效地重用已經完成呼叫訂閱者方法的執行緒以限制併發執行緒的數量。  後面會通過程式碼展示五種ThreadMode的工作方式。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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=".ThreadModeActivity">

    <Button
        android:id="@+id/buttonSend"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:text="Send"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/buttonThread"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:text="Send Thread"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/buttonSend" />
</android.support.constraint.ConstraintLayout>
package cn.netkiller.eventbus;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

public class ThreadModeActivity extends AppCompatActivity {

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

        EventBus.getDefault().register(this);

        findViewById(R.id.buttonSend).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("EventBus Thread : ", Thread.currentThread().getName());
                EventBus.getDefault().post("http://www.netkiller.cn");
            }
        });

        findViewById(R.id.buttonThread).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Log.d("EventBus Thread : ", Thread.currentThread().getName());
                        EventBus.getDefault().post("http://www.netkiller.cn");

                    }
                }).start();

            }
        });

    }

    @Subscribe(threadMode = ThreadMode.POSTING)
    public void onMessageEventPostThread(String event) {
        Log.d("EventBus PostThread", "Message: " + event + "  thread: " + Thread.currentThread().getName());
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEventMainThread(String event) {
        Log.d("EventBus MainThread", "Message: " + event + "  thread: " + Thread.currentThread().getName());
    }

    @Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
    public void onEventMainOrdered(String event) {
        Log.d("EventBus MainOrdered", "Message: " + event + " thread:" + Thread.currentThread().getName());
    }

    @Subscribe(threadMode = ThreadMode.BACKGROUND)
    public void onMessageEventBackgroundThread(String event) {
        Log.d("EventBus BackgroundThread", "Message: " + event + "  thread: " + Thread.currentThread().getName());
    }

    @Subscribe(threadMode = ThreadMode.ASYNC)
    public void onMessageEventAsync(String event) {
        Log.d("EventBus Async", "Message: " + event + "  thread: " + Thread.currentThread().getName());
    }


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

在 main 執行緒中釋出訊息

D/EventBus Thread :: main
D/EventBus MainThread: Message: http://www.netkiller.cn  thread: main
D/EventBus PostThread: Message: http://www.netkiller.cn  thread: main
D/EventBus Async: Message: http://www.netkiller.cn  thread: pool-1-thread-1
D/EventBus BackgroundThread: Message: http://www.netkiller.cn  thread: pool-1-thread-2
D/EventBus MainOrdered: Message: http://www.netkiller.cn thread:main

線上程中釋出訊息

D/EventBus Thread :: Thread-2
D/EventBus BackgroundThread: Message: http://www.netkiller.cn  thread: Thread-2
D/EventBus PostThread: Message: http://www.netkiller.cn  thread: Thread-2
D/EventBus Async: Message: http://www.netkiller.cn  thread: pool-1-thread-2
D/EventBus MainOrdered: Message: http://www.netkiller.cn thread:main
D/EventBus MainThread: Message: http://www.netkiller.cn  thread: main

47.5. 配置 EventBus 

上面章節中的例子EventBus例項中採用預設方式

EventBus.getDefault().register(this);

這種方式的獲取到的EventBus的都是預設屬性,有時候並不能滿足我們的要求,這時候我們可以通過EventBusBuilder來個性化配置EventBus的屬性。

// 建立預設的EventBus物件,相當於EventBus.getDefault()。

EventBus installDefaultEventBus():
// 新增由EventBus“註釋前處理器生成的索引
EventBuilder addIndex(SubscriberInfoIndex index):
// 預設情況下,EventBus認為事件類有層次結構(訂戶超類將被通知)
EventBuilder eventInheritance(boolean eventInheritance):
// 定義一個執行緒池用於處理後臺執行緒和非同步執行緒分發事件
EventBuilder executorService(java.util.concurrent.ExecutorService executorService):
// 設定忽略訂閱索引,即使事件已被設定索引,預設為false
EventBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex):
// 列印沒有訂閱訊息,預設為true
EventBuilder logNoSubscriberMessages(boolean logNoSubscriberMessages):
// 列印訂閱異常,預設true
EventBuilder logSubscriberExceptions(boolean logSubscriberExceptions):
// 設定傳送的的事件在沒有訂閱者的情況時,EventBus是否保持靜默,預設true
EventBuilder sendNoSubscriberEvent(boolean sendNoSubscriberEvent):
// 傳送分發事件的異常,預設true
EventBuilder sendSubscriberExceptionEvent(boolean sendSubscriberExceptionEvent):
// 在3.0以前,接收處理事件的方法名以onEvent開頭,方法名稱驗證避免不是以此開頭,啟用嚴格的方法驗證(預設:false)
EventBuilder strictMethodVerification(java.lang.Class<?> clazz)
// 如果onEvent***方法出現異常,是否將此異常分發給訂閱者(預設:false)
EventBuilder throwSubscriberException(boolean throwSubscriberException)

我的例項參考

EventBus eventBus = EventBus.builder().eventInheritance(true)
    .ignoreGeneratedIndex(false)
    .logNoSubscriberMessages(true)
    .logSubscriberExceptions(false)
    .sendNoSubscriberEvent(true)
    .sendSubscriberExceptionEvent(true)
    .throwSubscriberException(false)
    .strictMethodVerification(true)
    .build();
eventBus.register(this);

47.6. 事件優先順序

priority 數值越大優先順序又高

// MainActivity
	@Subscribe(priority = 2)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }

	// SecondActivity
	@Subscribe(priority = 1)
    public void onMessageSecondEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }

時間攔截,MainActivity 收到資訊後呼叫 EventBus.getDefault().cancelEventDelivery(event); 之後所有訂閱將收不到資訊。

// MainActivity
	@Subscribe(priority = 2)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
        EventBus.getDefault().cancelEventDelivery(event);
    }

	// SecondActivity
	@Subscribe(priority = 1)
    public void onMessageSecondEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }

47.7. 捕獲異常事件

在 init() 中加入你的業務邏輯,根據需要,在特定的情況下使用 throw new Exception(“異常資訊”); 丟擲異常。異常會被 hrowableFailureEvent(ThrowableFailureEvent event) 捕獲到。

package cn.netkiller.eventbus;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.greenrobot.eventbus.util.AsyncExecutor;
import org.greenrobot.eventbus.util.ThrowableFailureEvent;

import cn.netkiller.eventbus.pojo.MessageEvent;

public class MainActivity extends AppCompatActivity {

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

        EventBus.getDefault().register(this);

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                AsyncExecutor.create().execute(
                        new AsyncExecutor.RunnableEx() {
                            @Override
                            public void run() throws Exception {
                                init();
                                EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
                            }
                        }
                );
            }
        });
    }

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

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }

    public void init() throws Exception {
        // ...
        throw new Exception("實際傳送異常");
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void hrowableFailureEvent(ThrowableFailureEvent event) {
        Log.d("EventBus", "hrowableFailureEvent: " + event.getThrowable().getMessage());
        Toast.makeText(this, event.getThrowable().getMessage(), Toast.LENGTH_SHORT).show();
    }

}


相關文章