Android之旅Android 与 设计模式

EventBus3.x使用篇

2019-08-04  本文已影响4人  h2coder

EventBus是一个基于观察者模式的发布、订阅的框架。

添加依赖

implementation 'org.greenrobot:eventbus:3.1.1'

添加混淆

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

核心方法

粘性事件方法

注册、解注册、订阅事件、发布事件

@Override
public void onCreate() {
    super.onCreate();
    EventBus.getDefault().register(this);
}
 
@Override
public void onDestroy() {
    super.onDestroy();
    EventBus.getDefault().unregister(this);
}
//定义登录事件
public class LoginEvent {
    private String uid;
    private String token;

    public LoginEvent(String uid, String token) {
        this.uid = uid;
        this.token = token;
    }

    //省略get、set方法
}

//订阅登录事件
@Subscribe()
public void onLogin(LoginEvent event) {
    String currentThreadName = Thread.currentThread().getName();
    System.out.println("当前线程名:" + currentThreadName);
    System.out.println("<收到了登录事件> uid:" + event.getUid() + ",token:" + event.getToken());
}
//创建事件
LoginEvent event = new LoginEvent("10086", "xxxxxxx1234");
//发送事件
EventBus.getDefault().post(event);

订阅的多种线程模式

上面我们使用的@Subscribe()订阅事件,接收事件方法会在发送方的线程中调用,例如发送方在主线程,那么接收方就在主线程中接收到事件。如果发送方在子线程中发送事件,接收方就在子线程中接收。

如果我们在接口请求中发送事件(例如在子线程中),事件接收方法改变UI,那么肯定会收到不能在主线程外改变UI的异常。这时候我们就需要使用到线程模式的指定。

线程指定,我们只需要在接收方的@Subscribe()注解中,添加一个threadMode线程模式。线程模式有5种:

  1. POSTING,默认指定线程模式就为POSTING,接收方法在发送方的线程中执行。
public class UpdateUnreadCountEvent {
    private int unreadCount;

    public UpdateUnreadCountEvent(int unreadCount) {
        this.unreadCount = unreadCount;
    }
    //...
}

/**
 * 子线程中发送事件,则在子线程中执行,主线程发送,则在主线程中执行。
 */
@Subscribe(threadMode = ThreadMode.POSTING)
public void onUpdateUnreadCount(UpdateUnreadCountEvent event) {
    String currentThreadName = Thread.currentThread().getName();
    System.out.println("当前线程名:" + currentThreadName);
    System.out.println("<收到了更新用户未读数事件> unreadCount:" + event.getUnreadCount());
}
  1. MAIN,指定接收方法在主线程中调用,如果当前就处于主线程会直接执行,不会再使用Handler添加到任务队列中。
public class LoginEvent {
    private String uid;
    private String token;

    public LoginEvent(String uid, String token) {
        this.uid = uid;
        this.token = token;
    }
    //...
}

/**
 * 必定在主线程中执行
 */
@Subscribe(threadMode = ThreadMode.MAIN)
public void onLogin(LoginEvent event) {
    String currentThreadName = Thread.currentThread().getName();
    System.out.println("当前线程名:" + currentThreadName);
    System.out.println("<收到了登录事件> uid:" + event.getUid() + ",token:" + event.getToken());
}
  1. MAIN_ORDERED,接收方法也是在主线程中执行,但是和MAIN模式有区别,就算当前就在主线程中,也会使用Handler添加到任务队列中,再执行。
public class ExitAppEvent {
}

/**
 * 每次发送都使用Handler去post一个任务到队列中,再执行
 */
@Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
public void onExitApp(ExitAppEvent event) {
    String currentThreadName = Thread.currentThread().getName();
    System.out.println("当前线程名:" + currentThreadName);
    System.out.println("<收到了退出App事件>");
}
  1. BACKGROUND,指定接收方法在线程池的子线程中执行,如果当前就处于子线程中,则直接执行,而不会再新建一个线程去执行。
public class ObtainUserInfoEvent {
    private String uid;

    public ObtainUserInfoEvent(String uid) {
        this.uid = uid;
    }
    //...
    
    /**
     * 子线程执行,如果已经在子线程了,直接执行
     */
    @Subscribe(threadMode = ThreadMode.BACKGROUND)
    public void onObtainUserInfo(ObtainUserInfoEvent event) {
        String currentThreadName = Thread.currentThread().getName();
        System.out.println("当前线程名:" + currentThreadName);
        System.out.println("<收到了更新用户信息事件> uid:" + event.getUid());
    }
}
  1. ASYNC,同样是线程池的子线程中执行,但和BACKGROUND模式有区别,如果当前就处于子线程中,也会新建一个线程去执行。
public class LogoutEvent {
    private String uid;

    public LogoutEvent(String uid) {
        this.uid = uid;
    }
    //...
    
    /**
     * 子线程执行,不管当前是不是已经在子线程,都新开一个线程去执行
     */
    @Subscribe(threadMode = ThreadMode.ASYNC)
    public void onLogout(LogoutEvent event) {
        String currentThreadName = Thread.currentThread().getName();
        System.out.println("当前线程名:" + currentThreadName);
        System.out.println("<收到了登出事件> uid:" + event.getUid());
    }
}

发送粘性事件和订阅粘性事件

如果事件发送比订阅早,但需要接收方订阅时依旧能接收到事件的最后一个,那么就需要使用到粘性事件,注意:粘性事件会一直存在于内存中,下一次再订阅依旧会受到事件接收的调用,如果不想则需要移除粘性事件。

  1. 事件的注册和解注册和普通事件订阅是一致的,所以就不再贴出了。
  2. 粘性事件的接收,需要将@Subscribe()注解添加一个sticky属性,指定为true,该属性默认为false。
public class LoginEvent {
    private String uid;
    private String token;

    public LoginEvent(String uid, String token) {
        this.uid = uid;
        this.token = token;
    }
    //...
}

@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onLogin(MessageEvent event) {
    //UI操作...
}
  1. 发送粘性事件,post(event)方法改为postSticky(event)即可。
LoginEvent event = new LoginEvent("10086", "xxxxxxx1234");
EventBus.getDefault().postSticky(event);
  1. 获取粘性事件,一般用于不订阅,直接获取事件使用事件数据。或者调用removeStickyEvent移除事件(一般是收到事件后,马上移除)。
LoginEvent event = EventBus.getDefault().getStickyEvent(LoginEvent.class);
String uid = event.getUid();
//...其他处理
//或者取消事件
EventBus.getDefault().removeStickyEvent(event);
  1. 移除粘性事件,分为事件对象移除或使用事件类型移除。
//使用事件对象移除
EventBus.getDefault().removeStickyEvent(event);
//使用事件类型移除
EventBus.getDefault().removeStickyEvent(LoginEvent.class);

事件接收优先级和拦截事件传播

  1. 事件的传播,还可以指定优先级,优先级越高,越早执行。

  2. 并且如果事件是在POSTING线程模式中的,可以打断事件的传播。

//指定优先级,默认优先级为0,比onEventB早接收
@Subscribe(priority = 1, threadMode = ThreadMode.POSTING);
public void onEventA(MessageEvent event) {
    System.out.println("A:收到登录事件");
    //打断事件传播,仅限于POSTING线程模式
    EventBus.getDefault().cancelEventDelivery(event);
}

@Subscribe(threadMode = ThreadMode. POSTING);
public void onEventB(MessageEvent event) {
    System.out.println("B:收到登录事件");
}

EventBus配置和替换默认实例

上面我们获取EventBus实例都是使用EventBus.getDefault()方法获得,调用该方法获取的都是默认配置生成的EventBus,那么如果我们想自己配置需要怎么做的呢?EventBus给我们提供了EventBus.builder类进行配置,最后使用installDefaultEventBus()来替换默认实例。

EventBus eventBus = EventBus.builder()
    .logSubscriberExceptions(false)//默认为ture,是否记录,调用接收者响应事件的方法出现异常时的异常日志
    .logNoSubscriberMessages(false) //默认为ture,是否记录,发送者发布事件时没有接收者的日志 
    .sendNoSubscriberEvent(false) //默认为ture,当发送者发布事件时没有接收者时,是否将事件转化为 post一个NoSubscriberEvent事件
    .sendSubscriberExceptionEvent(false) // 默认为ture,调用接收者响应事件的方法出现异常时,是否 post一个SubscriberExceptionEvent事件
    .throwSubscriberException(BuildConfig.DEBUG) //默认为 false,调用接收者响应事件的方法出现异常时是否抛出EventBusException
    .eventInheritance(false) // 默认为ture,若发送者发布的事件是接收者的订阅事件的子类,是否将事件传递给接收者处理
    .ignoreGeneratedIndex(true) //默认为false,是否忽略Index索引
    .strictMethodVerification(true) //默认为false,是否严格认证 @Subscribe注解标注的接收者响应事件方法,如果 方法是0个或多于1个参数,或者是 非 public, 抽象的,静态的,会抛出EventBusException
    .installDefaultEventBus();//将生成的配置实例设置到Default单例,后续getDefault()获取到的都是该配置的EventBus实例

添加索引替代反射

默认EventBus调用接收者的接收方法是使用反射调用,查找订阅方法也是使用反射,对于性能有一定影响,Android上使用,我们使用提供的annotationProcessor,编译时查找接收者的接收方法和接收者信息,生成索引,那么运行时则直接使用索引而避免了反射,效率更高。

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [ eventBusIndex : 'me.wally.eventbus.sample.AppEventBusIndex' ]
            }
        }
    }
}

dependencies {
    implementation 'org.greenrobot:eventbus:3.1.1'
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}
EventBus.builder()
.addIndex(new AppEventBusIndex())
//其他类库的索引
.addIndex(new LibEventBusIndex())
.installDefaultEventBus();
EventBus eventBus = EventBus.getDefault();
//...后续使用

EventBus的优缺点

优点:

  1. EventBus能代替广播为我们提供单进程的组件间通信,比广播,如果涉及多进程通信,还是使用广播方便。

  2. Activity内多Fragment,避免通过Activity作为中介,查找Fragment耦合Activity。使用EventBus解耦。

缺点:

  1. 组件化项目,如果多个Module之间通信,需要将Event类下沉到Base库,容易造成Event类膨胀。

  2. 容易出现滥用,导致调用流程太松散,不好追踪调用流程。

总结

EventBus不应该滥用,否则出现调用流程不清晰的问题,适当区域使用可以解耦。

例如详情页面点赞,外层列表页需要更新点赞状态和点赞数量等,如果使用startActivityForResult()跳转结束时返回点赞状态和点赞数量,多个界面都需要同步这些数据时,会产生耦合Activity。

如果还需要使用数据库记录数据,将数据库代码写在Activity,另外一个界面也需要时,容易代码臃肿。

此时使用EventBus能很简单的同步状态和数据,数据库操作也能单独一个类接收事件消息进行操作即可。

上一篇下一篇

猜你喜欢

热点阅读