EventBus的基本使用

EventBus

简介

EventBus是用于android和Java的发布/订阅事件总线。

EventBus的基本使用_第1张图片

图片源自GitHub greenrobot/EventBus:适用于 Android 和 Java 的事件总线,可简化 Activity、Fragments、Threads、Services 等之间的通信。更少的代码,更好的质量。 (github.com)

附加:EventBus Documentation - EventBus官方文档

EventBus使用了发布者/订阅之模式:

发布者通过EventBus发布事件,订阅者通过EventBus订阅事件。当发布者发布事件时,订阅该事件的事件处理方法将被调用。

EventBus的优点

  • 解耦和简化Activity,Fragment等组件以及后台线程之间的通信,分离事件发送方和接收方。

  • 避免复杂且容易出错的依赖关系和生命周期问题。

  • 使用代码简洁,高效。

  • EventBus的包的体积小。

  • 等。

EventBus的三要素

  • Event:事件。事件可以是任意类的对象,是通信的内容

  • Subscriber:事件订阅者。在接收事件的组件创建时订阅注册,同时需要在组件被销毁前解除注册。在该组件中编写收到事件后的回调函数。回调函数的形参是事件对象,回调函数名称可自定义,但必须添加注解@Subscribe,并指定线程模式。(默认为POSTING)。

  • Publisher:事件发布者,可以在任意线程模式、任意位置发布事件。直接调用EventBus.post(Object)。根据post函数的参数类型,会自动调用订阅相应类型的回调函数

EventBus的五种线程模式

在事件订阅者使用@Subscribe注解标记回调函数时,需要指定线程模式,未指定默认为POSTING。

  • ThreadMode.POSTING:默认线程模式。订阅者方法将会在发布事件的同一线程中被调用。事件的传递是同步的,一旦发布事件,所有该模式的订阅者方法都将被调用。这种线程模式以为着最少的性能开销,因为他避免了线程切换。因此对于耗时短并且不需要主线程的简单任务,推荐使用这种模式。使用这种模式的事件处理应该快速返回,因为有可能事件是在主线程中发布的。

  • ThreadMode.MAIN:订阅者方法将会在主线程中调用。如果发布事件的线程也是主线程,那么和指定为ThreadMode.POSTING一样,不需要切换线程运行。使用这种模式的事件处理必须快速返回,否则会阻塞主线程。

  • ThreadMode.MIAN_ORDERED:订阅者方法会在主线程中被调用。发布者发布的事件会进入队列,依照先后顺序依次发送给订阅者。所以叫Ordered

    例如:如果在MAIN线程模式中的事件处理(称为第一事件)中去发布另外一个事件(称为第二事件),第二事件的订阅者的线程模式也是MAIN,那么第二事件会在第一事件处理结束前就开始处理。但是如果第二事件的订阅者的线程模式是MAIN_ORDERED,那么在第一事件结束的稍后的事件就会开始处理第二事件。

    同样,使用此模式的事件处理必须快速返回,以免阻塞主线程。

  • ThreadMode.BACKGROUND:订阅者方法将在后台线程中被调用。如果发布事件的线程不是主线程,则将直接在发布线程中调用事件处理方法;如果是主线程,那么EventBus会创建一个单独的后台线程,该线程将按照顺序传递所有事件。使用此这种模式的事件处理应该尽量快速返回,避免阻塞后台线程。使用这种线程模式不会在主线程中处理事件

  • ThreadMode.ASYNC:订阅者方法将会在一个单独的线程中被调用。这个线程始终独立于发布线程和主线程。如果事件处理方法的执行可能需要一些时间,例如网络访问,则应该使用此模式。

EventBus的基本使用

正常开发(单Module开发)使用

  1. 在app的build.gradle文件中添加依赖:

    implementation ‘org.greenrobot:eventbus:3.3.1’

  2. 构建一个作为事件数据的类:

    public class MessageEvent {
        //传递的数据(事件)类
    ​
        private String message;
    ​
        public MessageEvent(String message) {
            this.message = message;
        }
    ​
        public String getMessage() {
            return message;
        }
    }

  3. 在需要接收事件的Activity中,注册订阅和取消注册,并使用@Subcribe注解定义事件处理方法。

    public class MainActivity extends AppCompatActivity {
    ​
        TextView tv;
        Button btn;
        
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //注册订阅
            EventBus.getDefault().register(this);
    ​
            ...
        }
        
        @Subscribe
        public void messageReceived(MessageEvent event) {
            tv.setText(event.getMessage());
        }
    ​
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //取消注册订阅
            EventBus.getDefault().unregister(this);
        }
    }

  4. 要发送事件的Activity中使用post对象

    public class MainActivity extends AppCompatActivity {
        
        TextView tv;
        Button btn;
        
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ...
    ​
            tv = (TextView) findViewById(R.id.ctvv);
            btn = (Button) findViewById(R.id.btn);
    ​
            btn.setOnClickListener(view -> {
                //设置点击事件,点击发送事件
                EventBus.getDefault().post(new MessageEvent("发送了message1"));
            });
        }
    ​
        ...
    }

    这里为了Demo方便,我就把发送事件的过程也放在同一个Activity中进行了。

结果可以正常运行和使用。

EventBus的使用,发布者发布的事件,多个订阅者接收到。那些订阅者能收到,取决于订阅者方法的参数。

这里发布者发布的对象是MessageEvent的对象,所以之后形参是MessageEvent的订阅者方法能接收到并调用。

组件化开发使用

在组件化中使用EventBus最大的问题就是,跨组件的话我们定义的MessageEvent是使用不了的,所以要把事件类MessageEvent定义在基础组件中。基础组件被其他所有业务组件和功能组件所依赖。MessageEvent就能被所有组件使用。

EventBus粘性事件(sticky event)

普通事件在发布者post前订阅者需要在所在组件提前注册好,当post发出后,订阅者所在组件开始调用订阅者方法回调。

粘性事件允许发布者post的时间在接收者的组件中持久存在,直至接收者注册了粘性事件才调用好回调函数。

粘性件所处理的问题就是:发布者先发送了事件,但订阅者此时还未订阅,一段时间后订阅者订阅了该事件,仍然能后调用事件处理方法。

使用:

接收

接收粘性事件的订阅者方法必须在注解中加入sticky = true

@Subscribe(sticky = true)
public void messageReceived(String message) {
    Log.d("moduleEventBus", "messageReceived: " + message + this);
}

发布

发布者发布粘性事件时要使用postSticky()代替post()。

EventBus.getDefault().postSticky("发送了message" + num++);

手动获取和移除粘性事件

发布者发布了粘性事件之后,EventBus会将粘性事件保留在内存中,一直等待订阅者接收。如果想要主动移除粘性事件,那么使用以下方法手动获取并移除粘性事件

//手动获取粘性事件 参数是粘性事件的类型
String stickyEvent = EventBus.getDefault().getStickyEvent(String.class);
if (stickyEvent != null) {
    //移除粘性事件
    EventBus.getDefault().removeStickyEvent(stickyEvent);
}

按照EventBus的官方文档的说法。EventBus会将某一类型的最后一个粘性事件保留在内存中。所以通过类型的映射,获取到的只是一个事件。

移除粘性事件还有其他两个方法:

EventBus的基本使用_第2张图片

订阅和取消订阅

订阅注册和取消注册步骤不变,不过使用粘性事件就不需要一定把注册写在onCreate(),取消注册写在onDestory()。

因为EventBus不能重复注册,注册之前最好判断以下是否已经注册:

if (EventBus.getDefault().isRegistered(this)) {
    EventBus.getDefault().register(this);
}

EventBus的事件优先级和取消事件传递

EventBus支持在定义订阅者方法是指定事件传递的优先级。在@Subcribe注解中添加priority属性指定优先级。

EventBus的基本使用_第3张图片

默认情况下。订阅者方法的优先级为0。数值越大,优先级越高。相同的线程模式下,优先级更高的订阅者方法会优先接收到事件开始处理。

注意:优先级不会影响具有不同ThreadMode的订阅者方法之间的交付顺序

我们可以在高优先级的订阅者方法中使用cancelEventDelivery(对象事件)取消事件的传递。任何进一步的活动交付都会被取消,后续订阅者方法也不会接收到事件。

    @Subscribe(threadMode = ThreadMode.POSTING,priority = 10)
    public void messageReceived(String message) {
        ...
        EventBus.getDefault().cancelEventDelivery(message);
    }

注意:订阅者方法只有在线程模式为ThreadMode.POSTING时,才可以取消一个事件的传递。其实官方文档中说的时“取消仅限于在发布线程(ThreadMode.PostThread)中运行的事件处理方法。”,我想真的细分的话ThreadMode.POSTING模式下一定是和发布线程是同一线程,其他模式也可能和发布线程在同一线程,未必不能在其他线程模式下取消事件传递。总之使用的时候需要注意。

测试:

public class MainActivity extends AppCompatActivity {
​
    int num = 1 
​
    TextView tv;
    Button btn;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        EventBus.getDefault().register(this);
​
        Toast.makeText(this, "This is Main Module.", Toast.LENGTH_SHORT).show();
​
        tv = (TextView) findViewById(R.id.ctvv);
        btn = (Button) findViewById(R.id.btn);
​
        btn.setOnClickListener(view -> {
            //设置点击事件,点击发送事件
            EventBus.getDefault().post("发送了message" + num++);
        });
    }
​
    @Subscribe(priority = 10)
    public void messageReceived1(String message) {
        tv.setText(message);
        Log.d("messagePriority", "messageReceived1: " + message);
    }
​
    @Subscribe(priority = 7)
    public void messageReceived2(String message) {
        tv.setText(message);
        Log.d("messagePriority", "messageReceived2: " + message);
    }
    
    @Subscribe(priority = 5)
    public void messageReceived3(String message) {
        tv.setText(message);
        Log.d("messagePriority", "messageReceived3: " + message);
    }
    
    @Subscribe(priority = 3)
    public void messageReceived4(String message) {
        tv.setText(message);
        Log.d("messagePriority", "messageReceived4: " + message);
        EventBus.getDefault().cancelEventDelivery(message);
    }
    
    @Subscribe(priority = 1)
    public void messageReceived5(String message) {
        tv.setText(message);
        Log.d("messagePriority", "messageReceived5: " + message);
    }
​
​
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().unregister(this);
        }
    }
}

可以看到:为了省事在MainActivity中同时订阅和发布事件。设置了五个不同优先级的订阅方法,接收同一个事件。在第三个订阅方法(优先级为3)中取消了事件的传递。

EventBus的基本使用_第4张图片

可以看到只有优先级大于等于3的订阅方法接收到了事件。

执行取消事件的代码需要放在线程模式为ThreadMode.POSTING中,但后续的优先级更低的订阅方法无论是什么线程模式都不能接收到事件

EventBus的订阅者索引

订阅者索引是从EventBus3.0开始加入的。

默认情况下,Eventbus在查找订阅者方法时采用的是反射。订阅者索引可以冰面在运行时使用反射对订阅者方法进行成本高昂的查找,而是使用EventBus的注解处理器查找。从而加速订阅者的注册,是一个可选的优化。

订阅者索引的原理是:使用EventBus的注解处理器在编译期间创建订阅者索引类,该类包含了订阅者和订阅方法的相关信息。

索引的要求

  • @Subscribe方法及其类必须是公共的。

  • 事件类必须是公共的

  • @Subscribe不能在匿名类中使用

配置使用

在需要使用订阅者索引的module中配置build.gradle文件:

java:

android {
    ...
​
    defaultConfig {
        ...
​
        javaCompileOptions {
            annotationProcessorOptions {
                //这里'com.example.myapp'是所在的包名,'MyEventBusIndex'是类名,都可以自己指定,同步后可以在build->generated中找到,是EventBus自己生成的。
                arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
                //如果只有一个argument的话可以这么写
                //但是如果还有别的argument的话应该写成 arguments += [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
            }
        }
    }
    ...
}
​
dependencies {
    ...
    implementation 'org.greenrobot:eventbus:3.3.1'
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.3.1'
​
    ...
}

Kotilin:

apply plugin: 'kotlin-kapt' // ensure kapt plugin is applied
 
dependencies {
    def eventbus_version = '3.2.0'
    implementation "org.greenrobot:eventbus:$eventbus_version"
    kapt "org.greenrobot:eventbus-annotation-processor:$eventbus_version"
}
 
kapt {
    arguments {
        arg('eventBusIndex', 'com.example.myapp.MyEventBusIndex')
    }
}

在自定义的Application中使用

public class App extends Application {
​
    private Context mContext;
    @Override
    public void onCreate() {
        super.onCreate();
        //配置EventBus
        EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
        //使用EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();能将带有索引的EventBus设置为EventBus.getDefault()返回的实例
        //使用EventBus.builder().addIndex(new MyEventBusIndex()).build();能将索引类的实例传递给EventBus,也就是说会返回一个带索引的EventBus。
        //不太清楚有什么区别,使用第一个就行了吧。
        ...
    }
}

接下来的就是正常使用了。

如果在其他的module中也要使用订阅者索引,配置相同,且要为每个module调用

EventBus eventBus = EventBus.builder()
    .addIndex(new MyEventBusAppIndex()) //这个是自身的库
    .addIndex(new MyEventBusLibIndex()).build(); //这个是别的module的库
//或者
EventBus eventBus = EventBus.builder()
    .addIndex(new MyEventBusAppIndex()) //这个是自身的库
    .addIndex(new MyEventBusLibIndex()).installDefaultEventBus(); //这个是别的module的库

使用订阅者索引时会查找订阅者方法,之后自动生成一个类:

public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map, SubscriberInfo> SUBSCRIBER_INDEX;
​
    static {
        SUBSCRIBER_INDEX = new HashMap, SubscriberInfo>();
​
        putIndex(new SimpleSubscriberInfo(com.example.mudule.main.MainActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("messageReceived1", String.class, ThreadMode.POSTING, 10, false),
            new SubscriberMethodInfo("messageReceived2", String.class, ThreadMode.POSTING, 7, false),
            new SubscriberMethodInfo("messageReceived3", String.class, ThreadMode.POSTING, 5, false),
            new SubscriberMethodInfo("messageReceived4", String.class, ThreadMode.POSTING, 3, false),
            new SubscriberMethodInfo("messageReceived5", String.class, ThreadMode.POSTING, 1, false),
        }));
​
    }
​
    private static void putIndex(SubscriberInfo info) {
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }
​
    @Override
    public SubscriberInfo getSubscriberInfo(Class subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }
}

所以应该先在代码中定义好要使用的订阅者方法,之后在build.gradle文件中进行配置

你可能感兴趣的:(android,学习,java)