最全面的EventBus 3.1的使用教程及官方推荐的结合订阅者索引processor显著提升性能和实际项目中的使用注意事项

版权声明:转载必须注明本文转自郭子轩的博客 https://blog.csdn.net/gpf1320253667/article/details/84102691

需求场景

无论是现在的项目还是以前的项目中,都会遇见线程之间通信,组件之间通信的需求,我们知道这些需求都可以使用EventBus来处理,为了对比体现出EventBus使用的方便简洁,我们先来回顾下在EventBus出现以前我们是怎么处理线程间通信和组件间通信的。
1,线程间通信 + Handler消息传递机制
一个Android应用程序被创建的时候都会创建一个UI主线程,但是有时候我们会有一些比较耗时的操作,比如请求网络,为了防止阻塞UI线程,导成应用程序ANR,我们会将耗时的操作放到子线程中进行处理,而处理完之后又需要操作UI,但是Android不允许子线程操作UI并且规定更新UI的操作必须在UI线程中执行,此时就涉及到了子线程和UI线程之间的通信,在EventBus出现之前我们的处理的方式都是:Handler消息传递机制,具体的参见 http://www.cnblogs.com/whoislcj/p/5590615.html
2,组件之间通信 + Intent / 广播
点赞数据的同步:在详情页对该作品进行点赞,需要同时更新列表页展示时该作品的点赞数量,EventBus出来以前我们可以使用广播来完成,具体参见 http://www.cnblogs.com/whoislcj/p/5593056.html
3,代替Intent传递复杂数据
另外阿里安卓开发文档中明确指定:对于数据量比较大的Activity之间的数据通信,建议避免使用Intent+Parcelable(序列化接口)的方式,可以考虑使用EventBus代替,以免造成TransactionTooLargeException错误使应用崩溃。
我们知道如果想要在两个activity之间传递对象,那么这个对象必须序列化,android中序列化一个对象有两种方式,一种是实现Serializable接口,这个非常简单,只需要声明一下就可以了。但是android中还有一种特有的序列化方法,那就是实现Parcelable接口,使用这种方式来序列化的效率要高于实现Serializable接口,但是这种序列化方式实现起来非常麻烦,此处不详细讲解,读者自行去百度。

此外当利用Intent向Activity传递数据的时候,传递数据时有一个缓冲区,而这个缓冲区最大只有1MB,所以getIntent().getSerializableExtra()等方法携带的数据不宜过大,否则容易报错TransactionTooLargeException导致程序崩溃。
鉴于以上的原因使用EventBus来代替Intent传递复杂数据就显得尤为有必要了,至于怎么样传递,见本文。

EventBus简介

EventBus由greenrobot组织贡献(该组织还贡献了greenDAO),是一种用于Android的事件发布(publish)—订阅(subscribe)总线,主要用来代替Intent,Handler,Broadcast在Fragment,Activity,Service,线程之间传递消息,简化各组件之间的通信,优缺点如下:

优点:简化组件之间的通信方式,实现解耦让业务代码更加简洁,可以动态设置事件处理线程以及优先级,EventBus 3.0中添加了新的性能订阅者索引来显著提升效率,具体使用见文末。
缺点:每个事件都必须自定义一个事件类,造成事件类太多,无形中加大了维护成本。
GitHub地址:https://github.com/greenrobot/EventBus

EventBus 3.0使用

1,添加依赖

compile 'org.greenrobot:eventbus:3.1.1'

2,定义一个事件类型

public class DataSynEvent {
    private int count;

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

其实这个类就是一个Bean类,里面定义用来传输的数据的类型。

3,注册/解除注册
当我们需要在Activity或者Fragment里订阅事件时,我们需要注册EventBus。我们一般选择在Activity的onCreate()方法里去注册EventBus,在onDestory()方法里,去解除注册。

@Override
protected void onCreate(Bundle savedInstanceState) {           
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);
     EventBus.getDefault().register(this)// 注册
}
@Override
protected void onDestroy() {
    super.onDestroy();
    if(EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().unregister(this);  // 解除注册
        }
}

4,发布事件

EventBus.getDefault().post(new DataSynEvent());

5,订阅事件处理

@Subscribe(threadMode = ThreadMode.MAIN) //在ui线程执行
// 3.0之后事件处理的方法名可以随意取,不过需要加上注解@subscribe(),并且指定线程模型,默认是POSTING。
public void onDataSynEvent(DataSynEvent event) {
     Log.e(TAG, "event---->" + event.getCount());
}

至此,经过以上5个步骤我们就可以简单的使用EventBus了。
EventBus的注册和反注册以及事件处理的方法我们一般单独写在一个activity中,而事件的发送我们一般会写在另外一个activity中,而负责事件发送的activity中我们不必去写注册与反注册的逻辑代码。

EventBus的线程模式

EventBus支持订阅者方法在不同于发布事件所在线程的线程中被调用。你可以使用线程模式来指定调用订阅者方法的线程。EventBus总共支持5种线程模式:
1,ThreadMode.POSTING 订阅者方法将在发布事件所在的线程中被调用。这是 默认的线程模式。事件的传递是同步的,一旦发布事件,所有该模式的订阅者方法都将被调用。这种线程模式意味着最少的性能开销,因为它避免了线程的切换。因此,对于不要求是主线程并且耗时很短的简单任务推荐使用该模式。使用该模式的订阅者方法应该快速返回,以避免阻塞发布事件的线程,这可能是主线程。
2,ThreadMode.MAIN 订阅者方法将在主线程(UI线程)中被调用。因此,可以在该模式的订阅者方法中直接更新UI界面。如果发布事件的线程是主线程,那么该模式的订阅者方法将被直接调用。使用该模式的订阅者方法必须快速返回,以避免阻塞主线程。
3,ThreadMode.MAIN_ORDERED 订阅者方法将在主线程(UI线程)中被调用。因此,可以在该模式的订阅者方法中直接更新UI界面。事件将先进入队列然后才发送给订阅者,所以发布事件的调用将立即返回。这使得事件的处理保持严格的串行顺序。使用该模式的订阅者方法必须快速返回,以避免阻塞主线程。
4,ThreadMode.BACKGROUND 订阅者方法将在后台线程中被调用。如果发布事件的线程不是主线程,那么订阅者方法将直接在该线程中被调用。如果发布事件的线程是主线程,那么将使用一个单独的后台线程,该线程将按顺序发送所有的事件。使用该模式的订阅者方法应该快速返回,以避免阻塞后台线程。
5,ThreadMode.ASYNC 订阅者方法将在一个单独的线程中被调用。因此,发布事件的调用将立即返回。如果订阅者方法的执行需要一些时间,例如网络访问,那么就应该使用该模式。避免触发大量的长时间运行的订阅者方法,以限制并发线程的数量。EventBus使用了一个线程池来有效地重用已经完成调用订阅者方法的线程。

黏性事件

普通的事件我们通过post发送给EventBus,发送过后之后当前已经订阅过的方法可以收到。但是如果有些事件需要所有订阅了该事件的方法都能执行呢?例如一个Activity,要求它管理的所有Fragment都能执行某一个事件,但是当前我只初始化了3个Fragment,如果这时候通过post发送了事件,那么当前的3个Fragment当然能收到。但是这个时候又初始化了2个Fragment,那么我必须重新发送事件,这两个Fragment才能执行到订阅方法。
粘性事件就是为了解决这个问题,通过 postSticky 发送粘性事件,这个事件不会只被消费一次就消失,而是一直存在系统中,知道被 removeStickyEvent 删除掉。那么只要订阅了该粘性事件的所有方法,只要被register 的时候,就会被检测到,并且执行。订阅的方法需要添加 sticky = true 属性。

订阅和发布一个粘性事件的示例代码如下所示:

// 订阅粘性事件
@Subscribe(sticky = true)
public void onMessageEvent(MessageEvent event) {
    ...
}

// 发布粘性事件
EventBus.getDefault().postSticky(new MessageEvent("Hello EventBus!"));

发布一个粘性事件之后,EventBus将一直缓存该粘性事件。如果想要移除粘性事件,那么可以使用如下方法:

// 移除指定的粘性事件
removeStickyEvent(Object event);

// 移除指定类型的粘性事件
removeStickyEvent(Class<T> eventType);

// 移除所有的粘性事件
removeAllStickyEvents();

如下:

 @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void onMessageEvent(MessageEvent event) {
        Log.i(TAG, "message is " + event.getMessage());
        // 更新界面
        mTvMessage.setText(event.getMessage());
        // 移除粘性事件
        EventBus.getDefault().removeStickyEvent(event);
    }

上面当接收到MessageEvent粘性事件时,订阅者方法将打印日志消息,并更新界面上的TextView,最后移除该粘性事件。

设置订阅事件的优先级和取消事件传递

EventBus支持在定义订阅者方法时指定事件传递的优先级。默认情况下,订阅者方法的事件传递优先级为0。数值越大,优先级越高。在相同的线程模式下,更高优先级的订阅者方法将优先接收到事件。注意:优先级只有在相同的线程模式下才有效。

@Subscribe(threadMode = ThreadMode.MAIN,priority = 100) //在ui线程执行 优先级100
    public void onDataSynEvent(DataSynEvent event) {
        Log.e(TAG, "event---->" + event.getCount());
    }

你可以在高优先级的订阅者方法接收到事件之后取消事件的传递。此时,低优先级的订阅者方法将不会接收到该事件。
注意: 订阅者方法只有在线程模式为ThreadMode.POSTING时,才可以取消一个事件的传递。
取消事件传递的示例代码如下所示:

 @Subscribe(threadMode = ThreadMode.POSTING, priority = 1)
public void onMessageEvent(MessageEvent event) {
    ...
    // 取消事件传递
    EventBus.getDefault().cancelEventDelivery(event);
}

EventBus是如何代替Handler完成线程间消息传递的?

在子线程发送的消息,在接受的地方切换到主线程,只需要增加一个参数即可,比Handler使用方便太多了,具体如下:

new Thread(new Runnable() {
                    @Override
                    public void run() {
                        EventBus.getDefault().post(666);
                    }
                }).start();
MainActivity里面接收事件,只需要指定线程模式即可,即threadMode = ThreadMode.MAIN
@Subscribe(threadMode = ThreadMode.MAIN)
public void getEventBus(Integer num) {
    if (num != null) {
         Toast.makeText(this, "num" + num, Toast.LENGTH_SHORT).show();
    }
}

EventBus是如何代替Intent传递复杂数据的?

文章最开始我们提到,阿里巴巴安卓开发文档中明确表示对于数据量比较大的Activity之间的数据通信,建议避免使用Intent+Parcelable(序列化接口)的方式,可以考虑使用EventBus代替,以免造成TransactionTooLargeException错误使应用崩溃。具体原因在上面我们也分析过了。
使用EventBus代替Intent传递复杂数据目前有两种方式(推荐使用方式2也是阿里安卓开发推荐的方式):
1,调用startActivity启动界面时先不用调用EventBus的post去传参。而是在当前界面的onStop中调用post去传递你想传的复杂参数。为什么是onStop里面呢?因为这个方法是在下一个界面成功启动后调用的。这样你就绕过序列化了,如下:

@Override
    protected void onStop() {
        super.onStop();
        EventBus.getDefault().post(new ReplyDetailEvent());
    }

但这样可能会存在内存泄漏,那样可能要对导致内存泄漏的对象再做处理,就又变的麻烦了,所以不推荐使用该方式。
2,通过EventBus的黏性事件处理(阿里安卓开发推荐)
传递数据:

/**
     * @desc 点击发送数据过去第二个界面
     * @author lzPeng
     * @time 2018/4/27  20:15
     */
    public void btnSend(View view) {
        EventBus.getDefault().postSticky(new ObjEvent(etName.getText().toString(), etAge.getText().toString()));
        startActivity(new Intent(OneActivity.this, TwoActivity.class));
    }

接收数据时:


//用来赋值传递过来的对象
ObjEvent mObjEvent;

/**
     * @desc 接收第一个界面传递过来的数据
     * @author lzPeng
     * @time 2018/4/27  20:15
     */
   @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_two);
    //注册绑定事件即可接收相关数据
    EventBus.getDefault().register(this);
    //处理相关数据
    if (null != mObjEvent) {
        ((TextView) findViewById(R.id.tv_data)).setText("name:" + mObjEvent.getName() + "\n\n" + "age:" + mObjEvent.getAge());
    }
}


    @Subscribe(threadMode = ThreadMode.POSTING, sticky = true)
    public void onDataEvent(ObjEvent objEvent) {
        if (null != objEvent) {
            //赋值
            this.mObjEvent = objEvent;
        }
    }

@Override
protected void onDestroy() {
    //移除全部粘性事件
    EventBus.getDefault().removeAllStickyEvents();
    //解绑事件
    EventBus.getDefault().unregister(this);
    super.onDestroy();
}

使用该方式在定义bean对象ObjEvent时不用进行序列化仍然可以进行传递。

EventBus 3.0 官方推荐新特性:结合订阅者索引processor使用,显著提升性能

EventBus提供了一个EventBusAnnotationProcessor注解处理器来在编译期通过读取@Subscribe()注解并解析,处理其中所包含的信息,然后生成java类来保存所有订阅者关于订阅的信息,这样就比在运行时使用反射来获得这些订阅者的信息速度要快。

默认情况下,EventBus在查找订阅者方法时采用的是反射。订阅者索引是EventBus 3的一个新特性。它可以加速订阅者的注册,是一个可选的优化。订阅者索引的原理是:使用EventBus的注解处理器在应用构建期间创建订阅者索引类,该类包含了订阅者和订阅者方法的相关信息。EventBus官方推荐在Android中使用订阅者索引以获得最佳的性能。

要开启订阅者索引的生成,你需要在构建脚本中使用annotationProcessor属性将EventBus的注解处理器添加到应用的构建中,还要设置一个eventBusIndex参数来指定要生成的订阅者索引的完全限定类名。

1,首先,修改模块下的build.gradle构建脚本:

android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [eventBusIndex: 'com.github.cyc.eventbus.subscriberindexdemo.MyEventBusIndex']
            }
        }
    }
    ...
}

dependencies {
    ...
    compile 'org.greenrobot:eventbus:3.1.1'
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}

2,然后build一下工程,EventBus注解处理器将自动为你生成一个订阅者索引类。我们在\build\generated\source\apt\PakageName\下看到通过注解分析生成的索引类,这样我们便可以在初始化EventBus时应用我们生成的索引了,生成的索引类代码如下:

package com.github.cyc.eventbus.subscriberindexdemo;

import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;
import org.greenrobot.eventbus.meta.SubscriberMethodInfo;
import org.greenrobot.eventbus.meta.SubscriberInfo;
import org.greenrobot.eventbus.meta.SubscriberInfoIndex;

import org.greenrobot.eventbus.ThreadMode;

import java.util.HashMap;
import java.util.Map;

/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onMessageEvent", MessageEvent.class, ThreadMode.MAIN),
        }));

    }

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

3,最后在应用自定义的Application类的onCreate()方法中将订阅者索引类添加到EventBus默认的单例中,示例代码如下所示:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        // 配置EventBus
        EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
    }
}

经过以上操作我们再通过之前的方式EventBus.getDefault()来获取EventBus对象的时候,虽然两种情况下获取对象的方式一样,但此时我们获得的对象本身已经具备了订阅者索引新特性了,相当于优化加强版。
之后再按照上面讲的正常使用EventBus,就会发现效率有显著提升。
例如添加前后注册效率对比:
两种情况下分别进行注册:EventBus.getDefault().register(this);
添加之前:注册过程使用了9毫秒
在这里插入图片描述
添加之后:注册过程只用了2毫秒
在这里插入图片描述

实际项目中使用提示

1,注册一般写在onCreate或者onStart中,尽量不要写在onResume,可能出现多次注册。
2,取消注册一定写在onDestory,写在onStop可能会引发异常。
3,一般建议在BaseActivity中进行注册和取消注册,此外如果actiivty注册了eventbus,而没写一个方法被@Subscribe注解就会报异常,所以一般在BaseActivity中添加一个带@Subscribe注解的方法默认接收事件,可能该事件永远也不会触发,因为我们可能永远不会发送该参数指定的BaseActivity类型的事件,所以该默认的事件处理方法存在的目的只是为了解决前面所说的异常而存在。

public class BaseActivity extends AppCompatActivity {

    @Override
    protected void onStart() {
        super.onStart();
        // 绑定
        EventBus.getDefault().register(this);
    }

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

    /**
     * 默认绑定一个事件,防止源码里面去找方法的时候找不到报错。
     * @param activity
     */
    @Subscribe
    public void onEvent(BaseActivity activity){
        
    }
}

EventBus插件的使用

项目使用EventBus ,里面各种事件乱窜,这个插件对于项目中事件横飞的情况,还是非常好用的。
在AndroidStudio Setting——–>Plugins:
在这里插入图片描述
点击post事件小图标,会直接列出所有接收的地方:
在这里插入图片描述
点击接收事件的图标,会直接列出所有发送事件的地方:
在这里插入图片描述

代码混淆

-keepattributes *Annotation*
-keepclassmembers class ** {
    @org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }

# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    <init>(java.lang.Throwable);
}

猜你喜欢

转载自blog.csdn.net/gpf1320253667/article/details/84102691