Android EventBus

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

 

Netkiller Android 手札

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

Mr. Neo Chan, 陳景峯(BG7NYT)



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

<netkiller@msn.com>php

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

版權 © 2018 Neo Chanjava

 

版權聲明python

轉載請與做者聯繫,轉載時請務必標明文章原始出處和做者信息及本聲明。android

http://www.netkiller.cn
http://netkiller.github.io
http://netkiller.sourceforge.net
微信訂閱號 netkiller-ebook (微信掃描二維碼)
QQ:13721218 請註明「讀者」
QQ羣:128659835 請註明「讀者」

 

2018-10git

個人系列文檔github

編程語言spring

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();
    }

}
相關文章
相關標籤/搜索