组件通信框架: EventBus使用详解和手写简易EventBus框架
EventBus使用详解和手写简易EventBus框架
EventBus使用详解
前言
如今Android项目越来越庞大,参与人数越来越多,采用组件化开发已经是很有必要的了。组件化开发的优缺点就不多做介绍了,而EventBus大家或多或少也使用过。EventBus是一种用于Android的发布/订阅事件总线。它有很多优点:简化应用组件间的通信;解耦事件的发布者和接收者;避免复杂和容易出错的依赖和生命周期的问题等等。我们所看重的就是高度解耦和使用方便。
基础知识
EventBus采用了发布者/订阅订阅者模式
订阅者:通过EventBus来订阅事件,即发布者发布事件的时候,如果该订阅者的订阅事件处理方法就会被调用
发布者:通过EventBus把事件POST出去。
使用方法
- 添加依赖
- 基础用法
- 粘性事件
- 线程模式
- 事件优先级
- 订阅者索引
1.添加依赖
在模块的build.gradle添加依赖EventBus依赖
dependencies {
...
implementation 'org.greenrobot:eventbus:3.1.1'
}
如果开启了混淆的话,请添加EventBus的混淆规则。ProGuard工具混淆了方法名,并可能会移除未被调用的方法。而订阅者的事件处理方法是没有被直接调用的,如果开启的话,就必须保留这些方法,则在模块的混淆配置里面proguard-rules.pro混淆规则文件添加如下规则:
# EventBus
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
2.基础用法
主要有三个步骤:
-
定义事件
事件可以是任意的java对象。例如:
public class EvenBusMessageVo {
private String mMessage;
public EvenBusMessageVo(String mMessage) {
this.mMessage = mMessage;
}
public String getmMessage() {
return mMessage;
}
public void setmMessage(String mMessage) {
this.mMessage = mMessage;
}
}
-
定义事件处理方法(订阅者方法)
订阅者需要定义事件处理方法(订阅者方法),在发布对应类型数据(即你post的对象类型),该方法会被调用。EventBus 3使用 @Subscribe注解来表明该方法就是订阅者方法。方法名可以是任意,参数类型为订阅事件的类型。例如:
@Subscribe(threadMode = ThreadMode.MAIN)
public void sub(EvenBusMessageVo message) {
Log.d(TAG, "发送过来数据是:" + message.getmMessage());
}
订阅者还需要在总线上进行注册(EventBus),在不需要的时候需要注销。只有注册了的订阅者,才会收到事件。一般我们是在 Activity 或 Fragment的生命周期的 onCreate() 和 onDestroy() 方法来进行注册和注销。例如:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity===>>";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 注册订阅者
EventBus.getDefault().register(this);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void sub(EvenBusMessageVo message) {
Log.d(TAG, "发送过来数据是:" + message.getmMessage());
}
public void toTwoActivity(View view) {
Intent intent = new Intent(this, TwoActivity.class);
startActivity(intent);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 注销订阅者
EventBus.getDefault().unregister(this);
}
}
-
发送事件
在需要的时候发布事件,所有订阅了该类型事件的订阅者 都会收到该事件。例如:
public class TwoActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
}
/**
* 点击发布事件
* @param view
*/
public void sendMain(View view) {
EventBus.getDefault().post(new EvenBusMessageVo("发布一个事件"));
}
}
当点击了发布事件的按钮时候,TwoActivity会发布一个EvenBusMessageVo事件。此时可以看到一条log打印:
2019-06-12 15:23:10.646 5436-5436/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: 发送过来数据是:发布一个事件
3.粘性事件
如果先发布事件,然后才有订阅者订阅了该事件,除非再次发布,否则订阅者永远收不到该事件。此时可以使用粘性事件,发布一个粘性事件后,EventBus将在内存中缓存该粘性事件,当有订阅者订阅订阅该类型的粘性事件,订阅会收到该事件。
例如:
订阅粘性事件
public class TwoActivity extends AppCompatActivity {
private static final String TAG = "TwoActivity===>>";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
// 注册订阅者
EventBus.getDefault().register(this);
}
@Subscribe(threadMode = ThreadMode.MAIN ,sticky = true)
public void sub(EvenBusMessageVo message) {
Log.d(TAG, "发送过来数据是:" + message.getmMessage());
// 如果不清除该粘性事件,则在当次启动应用过程中,每次打开TwoActivity都会收到该事件
EventBus.getDefault().removeStickyEvent(message);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 注销订阅者
EventBus.getDefault().unregister(this);
}
}
发布粘性事件
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity===>>";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 发送粘性事件
* @param view
*/
public void send(View view){
EventBus.getDefault().postSticky(new EvenBusMessageVo("发布一个粘性事件"));
}
/**
* 跳转到TwoActivity
* @param view
*/
public void toTwoActivity(View view) {
Intent intent = new Intent(this, TwoActivity.class);
startActivity(intent);
}
}
运行,先点击发布按钮,然后再点击跳转按钮,跳转到TwoActivity后,确实收到了该事件。
日志输出结果:
2019-06-12 16:02:17.429 5663-5663/com.kbs.client.dn_20190605_eventbus D/TwoActivity===>>: 发送过来数据是:发布一个粘性事件
4.线程模式
EventBus支持订阅者方法在不同的线程中被调用。可以使用指定线程模式来指定订阅者方法运行在哪个线程,EventBus中支持五种线程模式:
-
ThreadMode.POSTING订阅者方法将在发布者所在的线程中被调用,即发布者是主线程,则订阅者方法就是在主线程中被调用。这个是 默认的线程模式。优势:事件的传递是同步的,一旦发布事件,那么所有的该模式下的订阅者都将被调用。这种线程模式避免了线程的切换,有最少的性能开销,因此不要求是必须是主线程并且耗时很多的简单任务推荐使用该模式。使用该模式的订阅者方法不能做耗时操作,因为很有可能是在主线程,会导致主线程的阻塞,出现ANR错误。
-
ThreadMode.MAIN订阅者方法将强制在主线程(UI线程)中被调用,因此可以在该模式下的订阅者方法可以直接更新UI。如果发布者是主线程,那么该模式的订阅方法将会被直接调用,反之会切换到主线程后,再调用该模式的订阅者方法。使用该模式的订阅者方法不能做耗时操作,会导致主线程的阻塞,出现ANR错误。
-
ThreadMode.MAIN_ORDERED订阅者方法将强制在主线程(UI线程)中被调用。因此可以在该模式下的订阅者方法可以直接更新UI。如果发布者是主线程,否则会先进行线程的切换,然后该模式的订阅方法将先进入队列后才发送给订阅者。使得事件的处理保持严格的串行顺序。使用该模式的订阅者方法不能做耗时操作,会导致主线程的阻塞,出现ANR错误。
-
ThreadMode.BACKGROUND订阅者方法将强制在后台线程(子线程)中被调用。如果发布者不是主线程,则该模式下订阅者方法会被直接调用,否则会进行线程的切换,再调用订阅者方法。由于该模式下订阅者方法是运行在后台线程(子线程)中,所以不能直接做UI更新操作。
-
ThreadMode.ASYNC订阅者方法是在独立的线程中运行,既不在发布事件的线程中,也不在主线程中。 一般可以使用这来进行网络和一些耗时操作。EventBus有专门的线程池对子线程进行管理,但仍然要避免同一时间开启太多的ASYNC模式线程。
订阅者方法:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity===>>";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 注册订阅者
EventBus.getDefault().register(this);
}
@Subscribe(threadMode = ThreadMode.POSTING)
public void sub(EvenBusMessageVo message) {
Log.d(TAG, "ThreadMode.POSTING,发送过来数据是:" + message.getmMessage() + ",线程名:" + Thread.currentThread().getName());
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void sub1(EvenBusMessageVo message) {
Log.d(TAG, "ThreadMode.MAIN,发送过来数据是:" + message.getmMessage() + ",线程名:" + Thread.currentThread().getName());
}
@Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
public void sub2(EvenBusMessageVo message) {
Log.d(TAG, "ThreadMode.MAIN_ORDERED,发送过来数据是:" + message.getmMessage() + ",线程名:" + Thread.currentThread().getName());
}
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void sub4(EvenBusMessageVo message) {
Log.d(TAG, "ThreadMode.BACKGROUND,发送过来数据是:" + message.getmMessage() + ",线程名:" + Thread.currentThread().getName());
}
@Subscribe(threadMode = ThreadMode.ASYNC)
public void sub5(EvenBusMessageVo message) {
Log.d(TAG, "ThreadMode.ASYNC,发送过来数据是:" + message.getmMessage() + ",线程名:" + Thread.currentThread().getName());
}
public void toTwoActivity(View view) {
Intent intent = new Intent(this, TwoActivity.class);
startActivity(intent);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 注销订阅者
EventBus.getDefault().unregister(this);
}
}
发布者方法:
public class TwoActivity extends AppCompatActivity {
private static final String TAG = "TwoActivity===>>";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
}
/**
* 主线程发布事件
*
* @param view
*/
public void sendMain(View view) {
EventBus.getDefault().post(new EvenBusMessageVo("主线程发布一个事件"));
Log.d(TAG, "发布的线程:" + Thread.currentThread().getName());
}
/**
* 子线程发布事件
*
* @param view
*/
public void sendMain2(View view) {
new Thread(new Runnable() {
@Override
public void run() {
EventBus.getDefault().post(new EvenBusMessageVo("子线程发布一个事件"));
Log.d(TAG, "发布的线程:" + Thread.currentThread().getName());
}
}).start();
}
}
点击主线程发布事件,结果如下:
2019-06-12 17:32:09.451 8753-8753/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.POSTING,发送过来数据是:主线程发布一个事件,线程名:main
2019-06-12 17:32:09.451 8753-8753/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.MAIN,发送过来数据是:主线程发布一个事件,线程名:main
2019-06-12 17:32:09.451 8753-8753/com.kbs.client.dn_20190605_eventbus D/TwoActivity===>>: 发布的线程:main
2019-06-12 17:32:09.460 8753-8952/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.BACKGROUND,发送过来数据是:主线程发布一个事件,线程名:pool-1-thread-4
2019-06-12 17:32:09.461 8753-8953/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.ASYNC,发送过来数据是:主线程发布一个事件,线程名:pool-1-thread-5
2019-06-12 17:32:09.467 8753-8753/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.MAIN_ORDERED,发送过来数据是:主线程发布一个事件,线程名:main
点击子线程发布事件,结果如下:
2019-06-12 17:31:09.116 8753-8943/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.POSTING,发送过来数据是:子线程发布一个事件,线程名:Thread-4
2019-06-12 17:31:09.116 8753-8943/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.BACKGROUND,发送过来数据是:子线程发布一个事件,线程名:Thread-4
2019-06-12 17:31:09.116 8753-8943/com.kbs.client.dn_20190605_eventbus D/TwoActivity===>>: 发布的线程:Thread-4
2019-06-12 17:31:09.120 8753-8944/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.ASYNC,发送过来数据是:子线程发布一个事件,线程名:pool-1-thread-3
2019-06-12 17:31:09.130 8753-8753/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.MAIN,发送过来数据是:子线程发布一个事件,线程名:main
2019-06-12 17:31:09.130 8753-8753/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.MAIN_ORDERED,发送过来数据是:子线程发布一个事件,线程名:main
由此可以推断以上的结论。
5.事件优先级
EventBus支持在定义订阅者方法的时候指定事件传递的优先级。通常情况下,订阅者方法的事件传递优先级为0,数值越大,则表示优先级越高,在相同的线程模式下,优先级越高的更快接收到该事件。
PS:事件优先级只有在相同的线程模式下才有效。
那么如何在订阅者方法下进行指定优先级。例如:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity===>>";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 注册订阅者
EventBus.getDefault().register(this);
}
@Subscribe(threadMode = ThreadMode.MAIN, priority = 1)
public void sub(EvenBusMessageVo message) {
Log.d(TAG, "priority = 1,发送过来数据是:" + message.getmMessage());
}
@Subscribe(threadMode = ThreadMode.MAIN, priority = 2)
public void sub2(EvenBusMessageVo message) {
Log.d(TAG, "priority = 2,发送过来数据是:" + message.getmMessage());
}
@Subscribe(threadMode = ThreadMode.MAIN, priority = 3)
public void sub3(EvenBusMessageVo message) {
Log.d(TAG, "priority = 3,发送过来数据是:" + message.getmMessage());
}
public void toTwoActivity(View view) {
Intent intent = new Intent(this, TwoActivity.class);
startActivity(intent);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 注销订阅者
EventBus.getDefault().unregister(this);
}
}
在TwoActivity中点击多次发布事件按钮,日志打印如下:
2019-06-12 16:19:25.932 5790-5790/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: priority = 3,发送过来数据是:发布一个事件
2019-06-12 16:19:25.933 5790-5790/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: priority = 2,发送过来数据是:发布一个事件
2019-06-12 16:19:25.933 5790-5790/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: priority = 1,发送过来数据是:发布一个事件
通过日志可以知道,优先级越高的越早接收到事件。
6.订阅者索引
默认情况下,EventBus在查找订阅方法的时采用的是反射。订阅者索引是3.0后才有的新特性。它可以加速订阅者的注册,是一个可优化的选项。原理是:使用EventBus的注解处理器在应用构建期间创建订阅者索引类,此类包含了订阅者和订阅者方法的相关信息,官方推荐在Android中使用订阅者索引类以获得最佳的性能。以前没怎么用过,此了解来源于网络。
要开启订阅者索引的生成,你需要在构建脚本中使用 annotationProcessor 属性将EventBus的注解处理器添加到应用的构建中,还要设置一个 eventBusIndex 参数来指定要生成的订阅者索引的完全限定类名。
第一步:修改下模块下面的 build.gradle 构建脚本
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [eventBusIndex: 'com.kbs.client.dn_20190605_eventbus.MyEventBusIndex']
}
}
}
第二步:重新rebuild一下,EventBus注解处理器会生产一个订阅者索引类,里面保存了所有订阅者和订阅者方法信息,如下所示:
/** 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("sub", EvenBusMessageVo.class),
new SubscriberMethodInfo("sub1", EvenBusMessageVo.class, ThreadMode.MAIN),
new SubscriberMethodInfo("sub2", EvenBusMessageVo.class, ThreadMode.MAIN_ORDERED),
new SubscriberMethodInfo("sub4", EvenBusMessageVo.class, ThreadMode.BACKGROUND),
new SubscriberMethodInfo("sub5", EvenBusMessageVo.class, ThreadMode.ASYNC),
}));
}
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;
}
}
第三步:
使用订阅者索引时,需要先进行初始化操作,在应用启动的时候进行初始化,例如:
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
// 配置EventBus
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
}
}
在AndroidManifest.xm
文件中的application
标签里面添加android:name =".App",就能进行初始化操作了。
运行结果:
2019-06-12 18:33:04.107 8753-8753/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.MAIN,发送过来数据是:主线程发布一个事件,线程名:main
手写简易版EventBus
我们需要手写这样的一个框架的话,就必须要清楚,该框架的原理,这里我不会单去将原理,我们直接通过步骤代码来明白原理。其实原理其实很简单,就是获取订阅者和订阅者方法,通过辨别订阅者方法参数类型来确定调用的什么方法,然后通过反射来调用订阅者方法,那样就实现了,具体里面的一些粘性事件,优先级,订阅者索引,只是一些额外的功能和优化点而已,在这我就没有写了,跨进程也暂时没有实现,那些需要慢慢的去添加,去优化框架,这我只是通过最简单的实现来剖析原理。主要通过以下部分来手写实现的,有什么不足的希望大家多多指点。
- 定义注解
- 对订阅者方法的封装
- 注册订阅者
- 发布事件
- 执行订阅者方法
- 线程模式处理
1.定义注解
在你的工程项目中,先新建一个lib,它是属于java Library的。命名为buslibrary
新建注解,我们也取名为Subscribe
/**
* author : android 老鲜肉
* document : 声明注解
*/
@Target(ElementType.METHOD) // 注解的作用域(表示放在哪个上面的)
@Retention(RetentionPolicy.RUNTIME) // 声明这个注解的生命周期 SOURCE:源码期 RUNTIME:运行期 CLASS:编译期
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING; // 默认线程为POSION
}
注解里面就一个方法,threadMode()
,来获取线程模式,在这我们还需要一个线程模式的枚举类,
那我们就是创建这个枚举类ThreadMode
,里面的值和我上面介绍的功能一样,当然你也可以自己任意命名的
/**
* author : android 老鲜肉
* document : 声明线程模式枚举
*/
public enum ThreadMode {
POSTING, // 发布者在什么线程,订阅者方法就运行在什么线程
MAIN, // 强制切换到主线程
BACKGROUND, // 强制切换到子线程
}
2.对订阅者方法的封装
首先我们要分析下,订阅者方法包含哪些内容。
@Subscribe(threadMode = ThreadMode.MAIN, priority = 3)
public void sub3(EvenBusMessageVo message) {
Log.d(TAG, "priority = 3,发送过来数据是:" + message.getmMessage());
}
我截取了之前我们实现过的订阅者方法,优先级咱们就先不加了,当然这个可以自行添加其他的标识属性。可以发现订阅者方法包含了ThreadMode
、EvenBusMessageVo(订阅者方法参数对象类型)
、方法本身
。那我们就把订阅者方法封装成一个对象。
/**
* author : android 老鲜肉
* document : 声明线程模式枚举
*/
public class MethodManager {
// 方法接受的参数对象的类型
private Class<?> type;
// 线程模式
private ThreadMode threadMode;
// 方法本身
private Method method;
public MethodManager(Class<?> type, ThreadMode threadMode, Method method) {
this.type = type;
this.threadMode = threadMode;
this.method = method;
}
public Class<?> getType() {
return type;
}
public ThreadMode getThreadMode() {
return threadMode;
}
public Method getMethod() {
return method;
}
}
3.注册订阅者
EventBus中注册订阅者方式:EventBus.getDefault().register(this);
,那我们也新建一个名字也为EventBus
的类,可以看的出来它是单例的,代码如下:
/**
* author : android 老鲜肉
* document : 声明线程模式枚举
*/
public class EventBus {
private static EventBus instance;
// 存储所有订阅者的map
// map的键就是注册的时候传进去的对象
private Map<Object, List<MethodManager>> map;
/**
* 初始化单例EventBus
*
* @return EventBus.this
*/
public static EventBus getDefault() {
if (instance == null)
synchronized (EventBus.class) {
if (instance == null) instance = new EventBus();
}
return instance;
}
/**
* 私有化构造方法
*/
private EventBus() {
map = new HashMap<>();
}
}
注册订阅者方法定义:
/**
* 传进来的组件 找到这个组件中的所有订阅方法
*
* @param object
*/
public void register(Object object) {
// 获取这个组件所有的订阅者方法集合
List<MethodManager> methodManagers = map.get(object);
if (methodManagers == null) {
// 通过组件对象找到这个组件所有标识了Subscribe注解的方法即为订阅方法
methodManagers = findAnnotationMethod(object);
map.put(object, methodManagers);
}
}
/**
* 通过传进来的组件来找它所有符合条件的订阅方法
*
* @param object 组件对象
* @return 这个组件所有的订阅者方法集合
*/
private List<MethodManager> findAnnotationMethod(Object object) {
List<MethodManager> methodList = new ArrayList<>();
// 获取到组件的类对象
Class<?> aClass = object.getClass();
// 通过类对象获取到所有方法的集合
Method[] declaredMethods = aClass.getDeclaredMethods();
// 循环方法集合进行判别那个是订阅者方法
for (Method declaredMethod : declaredMethods) {
// 返回该方法的注解在此方法的指定注解类型
Subscribe annotation = declaredMethod.getAnnotation(Subscribe.class);
if (annotation == null) { // 如果注解为空就表示不是订阅者方法
continue;
}
// 添加严格的校验
// 获取方法返回值的类型
Type genericReturnType = declaredMethod.getGenericReturnType();
if (!genericReturnType.toString().equals("void")) {
throw new RuntimeException("方法返回值不是void");
}
// 获取到方法所有接收参数类型数组
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
if (parameterTypes.length != 1) {
throw new RuntimeException("方法必须有且只有一个参数");
}
// 生成订阅者方法封装对象
MethodManager methodManager = new MethodManager(parameterTypes[0], annotation.threadMode(), declaredMethod);
methodList.add(methodManager);
}
return methodList;
}
4.发布事件
发布事件我们也在EventBus
类中,定义了一个post
方法。
/**
* 发布事件
* @param setter 事件发布的对象类型
*/
public void post(final Object setter) {
Set<Object> objects = map.keySet();
// 循环存储所有订阅者的map
for (final Object object : objects) {
// 获取到订阅者所有的订阅者方法的集合
List<MethodManager> methodManagers = map.get(object);
if (methodManagers != null && methodManagers.size() > 0) {
for (final MethodManager methodManager : methodManagers) {
// 判断发布者发布事件的发布的对象类型和订阅者方法的定义的类型是否一致
if (methodManager.getType().isAssignableFrom(setter.getClass())) {
// 通过反射来执行订阅者方法
invoke(methodManager, object, setter);
}
}
}
}
}
5.执行订阅者方法
执行订阅者方法是通过反射来执行的。
/**
* 通过反射执行订阅者方法
* @param methodManager 订阅者方法封装对象
* @param object 订阅者对象(组价)
* @param setter 订阅者方法定义的参数对象
*/
private void invoke(MethodManager methodManager, Object object, Object setter) {
Method method = methodManager.getMethod();
try {
// 通过反射执行方法
method.invoke(object, setter);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
至此就已经可以实现组件通信功能了,但是无法做到订阅者方法在指定的线程中执行。
6.线程模式处理
在MethodManager
对象中是有线程模式这个属性的,也就是说在invoke()
之前进行线程判断和切换就可以了。
在post
方法中对应位置添加
if (methodManager.getType().isAssignableFrom(setter.getClass())) {
// 线程切换操作
switch (methodManager.getThreadMode()) { // 判断线程模式
case POSTING: // 默认 直接invoke,不进行线程切换
invoke(methodManager, object, setter);
break;
case BACKGROUND:
// 判断当前线程是否是主线程
if (Looper.getMainLooper() == Looper.myLooper()) {
// 切换到子线程操作 executorService 是线程池对象
executorService.execute(new Runnable() {
@Override
public void run() {
invoke(methodManager, object, setter);
}
});
} else { // 当前是子线程
invoke(methodManager, object, setter);
}
break;
case MAIN:
// 判断当前线程是否是主线程
if (Looper.getMainLooper() == Looper.myLooper()) {
invoke(methodManager, object, setter);
} else { // 当前是子线程 handler 是用来切换到主线程的
handler.post(new Runnable() {
@Override
public void run() {
invoke(methodManager, object, setter);
}
});
}
break;
}
在EventBus的构造方法里面生成线程池对象executorService
/**
* 私有化构造方法
*/
private EventBus() {
map = new HashMap<>();
executorService = Executors.newCachedThreadPool();
}
构建成员变量 Handler ,来进行切换到主线程的操作
private Handler handler = new Handler();
最后还需要进行注销订阅者
/**
* 注销订阅方法
* @param object
*/
public void unregister(Object object) {
if (object != null && map.get(object) != null && map.get(object).size() > 0) {
map.remove(object);
}
}
这样简易版EventBus就已经写完了,比较简单,接下来运行一下
订阅者代码:
import com.kbs.client.buslibrary.EventBus;
import com.kbs.client.buslibrary.Subscribe;
import com.kbs.client.buslibrary.ThreadMode;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity===>>";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 注册订阅者
EventBus.getDefault().register(this);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void sub(EvenBusMessageVo message) {
Log.d(TAG, "ThreadMode.MAIN,发送过来数据是:" + message.getmMessage() + ",线程名:" + Thread.currentThread().getName());
}
@Subscribe(threadMode = ThreadMode.POSTING)
public void sub2(EvenBusMessageVo message) {
Log.d(TAG, "ThreadMode.POSTING,发送过来数据是:" + message.getmMessage() + ",线程名:" + Thread.currentThread().getName());
}
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void sub3(EvenBusMessageVo message) {
Log.d(TAG, "ThreadMode.BACKGROUND,发送过来数据是:" + message.getmMessage() + ",线程名:" + Thread.currentThread().getName());
}
public void toTwoActivity(View view) {
Intent intent = new Intent(this, TwoActivity.class);
startActivity(intent);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 注销订阅者
EventBus.getDefault().unregister(this);
}
}
发布事件代码:
import com.kbs.client.buslibrary.EventBus;
public class TwoActivity extends AppCompatActivity {
private static final String TAG = "TwoActivity===>>";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
}
/**
* 主线程发布事件
* @param view
*/
public void sendMain(View view) {
Log.d(TAG, "发布的线程:" + Thread.currentThread().getName());
EventBus.getDefault().post(new EvenBusMessageVo("主线程发布一个事件"));
}
/**
* 子线程发布事件
*
* @param view
*/
public void sendMain2(View view) {
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "发布的线程:" + Thread.currentThread().getName());
EventBus.getDefault().post(new EvenBusMessageVo("子线程发布一个事件"));
}
}).start();
}
}
运行结果:
2019-06-13 13:33:59.162 2210-2210/com.kbs.client.dn_20190605_eventbus D/TwoActivity===>>: 发布的线程:main
2019-06-13 13:33:59.163 2210-2210/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.MAIN,发送过来数据是:主线程发布一个事件,线程名:main
2019-06-13 13:33:59.163 2210-2210/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.POSTING,发送过来数据是:主线程发布一个事件,线程名:main
2019-06-13 13:33:59.167 2210-2817/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.BACKGROUND,发送过来数据是:主线程发布一个事件,线程名:pool-1-thread-1
2019-06-13 13:34:06.978 2210-2818/com.kbs.client.dn_20190605_eventbus D/TwoActivity===>>: 发布的线程:Thread-4
2019-06-13 13:34:06.985 2210-2818/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.POSTING,发送过来数据是:子线程发布一个事件,线程名:Thread-4
2019-06-13 13:34:06.988 2210-2818/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.BACKGROUND,发送过来数据是:子线程发布一个事件,线程名:Thread-4
2019-06-13 13:34:07.001 2210-2210/com.kbs.client.dn_20190605_eventbus D/MainActivity===>>: ThreadMode.MAIN,发送过来数据是:子线程发布一个事件,线程名:main
运行结果可见,我们的功能确实是实现了。至于其他的功能优化之类的,可以自行添加,个人能力有限,希望大家多多指教。
好了,到此就结束了。谢谢大家。
上一篇: spring4新特性之web开发增强
下一篇: mysql中如何使用正则表达式查询