动手来写一个EventBus吧~~~~
Posted microhex
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动手来写一个EventBus吧~~~~相关的知识,希望对你有一定的参考价值。
最近在项目拍错过程中,使用的Eventbus出现了一个问题,然后翻了一下源码,感觉理解得差不多了,然后我动手实现了一个,当然是最简单的,为此,我希望通过本篇文章能将Eventbus的原理说清楚,应该不是很难。
我实现的效果如下:
首先在首页注册我的eventbus事件:
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hx_event_bus)
HxEventBus.getDefault().register(this)
@SuppressWarnings("unused")
@HxSubscribe
fun onEventData(data: MyEventData)
id_tv_content.text = "姓名为$data.name , 年龄为$data.age"
override fun onDestroy()
super.onDestroy()
HxEventBus.getDefault().unRegister(this)
然后在下一个页面,发送事件代码:
fun sendData(view : View)
val data = MyEventData()
data.name = "tom"
data.age = 18
HxEventBus.getDefault().post(data)
大致代码和结果大家也看清楚了,不看官方EventBus源码的情况下,我们思考一下这种方式是怎么实现的,为什么我register了事件,在项目的任意位置发送事件都可以收到呢?如果这个页面退出了,为什么通过unRegister,可以取消订阅呢? 我也不卖关子了,总结一下自己所得:
- EventBus register是记录当前类中被@Subscribe注解修饰的方法,获取该方法中的参数、参数类型、线程调度、方法优先级统一存入静态容器;
- EventBus unRegister是将当前类中被@Subscribe 修饰的方法从静态容器中除去;
- EventBus 是发射事件 首先是在静态容器中找到和当前事件类型一致的关联方法, 然后通过 反射机制 达到执行方法的目的。
当然官方的EventBus源码中还有很多细节值得我们学习,大家可以翻翻源码。针对上面三条总结,今天我们就来实现一下自己的EventBus。
我大致的方案如下图,比较简单:)
HxEventBus.getDefault()
这个比较简单,获取一个单例模式,使用双null检查创建:
private static volatile HxEventBus instance ;
public static HxEventBus getDefault()
if(null == instance)
synchronized (HxEventBus.class)
if(null == instance)
instance = new HxEventBus();
return instance;
public void register(Object subscriber)
- 我们首先需要定义一个自己的注解,来标明我们需要获取哪些方法,那么就定义一个
@HxSubscribe
吧,我们实现的是最简单版,没有线程调度模型、也没有调用优先级,所以@HxSubscribe
只是个空标识,用来记录我们需要定义的方法.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface HxSubscribe
- 我们已经定义了我们自己的
@HxSubscribe
,当使用到某个方法上时,如下面:
@HxSubscribe
fun onEventData(data: MyEventData)
id_tv_content.text = "姓名为$data.name , 年龄为$data.age"
那么我们还需要定义一个方法,用来记录我们需要的方法 onEventData
, 方法中的参数类型 MyEventData
,那么就定义为:
public class HxSubscriberMethod
/**
* 订阅的实际方法
*/
public Method method;
/**
* 订阅方法中实际参数中所指定的class类型
*/
public Class<?> eventClass;
public HxSubscriberMethod(Method method, Class<?> eventClass)
this.method = method;
this.eventClass = eventClass;
有了注解和自定义的HxSubscriberMethod
,我们再来实现一下register():
public void register(Object subscriber)
if(null == subscriber) return;
List<HxSubscriberMethod> subscriberMethodList = methodFinder.getAllHxSubscriberMethod(subscriber);
synchronized (this)
for(HxSubscriberMethod subscriberMethod : subscriberMethodList)
subscribe(subscriber,subscriberMethod);
我们通过methodFinder
类来寻找register的Object类中被@HxSubscribe 修饰的类,这个应该很简单,不过我也贴一下代码:
List<HxSubscriberMethod> subscriberMethodList = new ArrayList<>();
Method[] methods = null ;
try
methods = object.getClass().getDeclaredMethods();
catch (SecurityException e)
e.printStackTrace();
if(null == methods || methods.length == 0) return subscriberMethodList;
for(Method method : methods)
//we just want the public method
Class<?>[] parameterTypes = method.getParameterTypes();
if(parameterTypes.length == 1) //只获取只有一个参数的
HxSubscribe hxSubscribe = method.getAnnotation(HxSubscribe.class);
if(null != hxSubscribe)
Class<?> eventType = parameterTypes[0];
subscriberMethodList.add(new HxSubscriberMethod(method,eventType));
return subscriberMethodList;
上面的代码中,我们首先通过反射获取到所有的Method
,然后查看每个Method
的HxSubscribe
注解,有则说明使我们需要的方法,没有的话我们就直接contiune
。这里因为实现得比较简单,没有向Object的super class
查找,当然这个Eventbus是实现了的,就是子类注册事件,父类中的方法也是被记录的。
所有被@HxSubsribe修饰的方法都已经被找到的,那么需要我们对每个方法正式注册一下了:
for(HxSubscriberMethod subscriberMethod : subscriberMethodList)
subscribe(subscriber,subscriberMethod);
我们知道,EventBus是基于Data Class传递的,只有@Subcribe
方法中的DataClass
类型与EventBus
发射的DataClass
类型一致,注册的事件才会被接受,并被执行。那我们可以想象一下,如果注册了很多DataClass
一致的方法,当Event发送一个DataClass事件时,需要所有的注册方法都被执行,那么最快的方案是什么呢? 就是将这些DataClass一致的方法到到容器中,此时只需要从容器中拿到对应的方法集合即可。此时,容器登场:
/**
* 通过订阅类型 将订阅者分类
*/
private Map<Class<?>,CopyOnWriteArrayList<HxSubscription>> subscriptionByEventTypeMap = new HashMap<>();
此时我们需要解释一下,CopyOnWriteArrayList
是啥?CopyOnWriteArrayList
是一种读写分离的List,是线程安全的,在遍历过程中,如果同时也删除数据,普通的ArrrayList会直接报ConcurrentModificationException
,但是CopyOnWriteArrayList
不会,它可以算得上比较高级的List了。另外,我们又多了一个HxSubscription
,这个比较简单,就是封装了当前注册的对象,与HxSubscriberMethod
,大致如下:
public class HxSubscription
/**
* 订阅的对象
*/
public Object subscriber;
/**
* 订阅的方法
*/
public HxSubscriberMethod subscriberMethod;
/**
* 是否活跃
*/
public volatile boolean active = true;
public HxSubscription(Object subscriber, HxSubscriberMethod subscriberMethod)
this.subscriber = subscriber;
this.subscriberMethod = subscriberMethod;
了解了CopyOnWriteArrayList
和HxSubscription
之后,我们再来将订阅方法包装分类:
private void subscribe(Object subscriber, HxSubscriberMethod subscriberMethod)
//获取DataClass的Class类型,通过Class类型存储
Class<?> eventType = subscriberMethod.eventClass;
//包装订阅类
HxSubscription hxSubscription = new HxSubscription(subscriber,subscriberMethod);
//直接定义缓存
CopyOnWriteArrayList<HxSubscription> subscriptions = subscriptionByEventTypeMap.get(eventType);
if(null == subscriptions)
subscriptions = new CopyOnWriteArrayList<>();
subscriptionByEventTypeMap.put(eventType, subscriptions);
else
//如果已经被注册过,那么直接报错
if(subscriptions.contains(hxSubscription))
throw new RuntimeException("object:" + subscriber + " has been registered");
//添加hxSubscription类型
subscriptions.add(hxSubscription);
到了这里,我们的思路估计差不多了,其实也没做多少事情,只是找到了我们的HxSubscriberMethod
,并将它们根据方法参数的Class类型分类存储。
现在有个问题,我们是否该记录一下当前订阅类subscriber
中多少Type
类型呢?为什么需要记录呢?因为这样我们在unRegister
时会更容易目标Class,remove
就容易了。那么也定义一个容器吧:
/**
* 订阅类中class 与 订阅方法中集合 方便直接unregister
*/
private Map<Object, List<Class<?>>> subscriberByMap = new HashMap<>();
subscriberByMap的key为当前Object,Value为记录的Classtype:
List<Class<?>> subscribedEvent = subscriberByMap.get(subscriber);
if(null == subscribedEvent)
subscribedEvent = new ArrayList<>();
subscriberByMap.put(subscriber, subscribedEvent);
subscribedEvent.add(eventType);
public void post(Object object)
我们首先来聊一聊发送发射事件,由于没有考虑线程调度,都是默认在主线程中作用,那么代码非常简单:
public void post(Object object)
Class<?> eventType = object.getClass();
CopyOnWriteArrayList<HxSubscription> hxSubscriptions = subscriptionByEventTypeMap.get(eventType);
if(null != hxSubscriptions)
for(HxSubscription subscription : hxSubscriptions)
//如果已经不是active状态了 则需要直接跳过
if(!subscription.active) continue;
try
subscription.subscriberMethod.method.invoke(subscription.subscriber,object);
catch (IllegalAccessException e)
e.printStackTrace();
catch (InvocationTargetException e)
e.printStackTrace();
找到我们的Object类对应的ClassType,从缓存中获取到所有的HxSubscription
,别忘了HxSubscription
封装了订阅对象subscription.Object
和订阅方法subscription.subscriberMethod
,那么此时调用subscription.subscriberMethod
invoke方法,使用反射将代码执行,从而完成我们需要的结果!
所以这里最重要的就是 Method.invoke()
方法了,而EventBus最核心的思想也就是使用了Method.invoke
然后达到了各种匪夷所思的目的。
public void unRegister(Object subscriber)
对于反注册,我想代码应该比较简单,大概如下:
/**
反注册
*/
public void unRegister(Object subscriber)
// 找到当前ClassType类型
List<Class<?>> subscribedEvent = subscriberByMap.get(subscriber);
if(null != subscribedEvent)
for(Class<?> clazz : subscribedEvent)
unRegisterSubscriberByType(clazz, subscriber);
subscriberByMap.remove(subscriber);
else
Log.w("","subscriber:" + subscriber + " has not been registered.");
/**
* 通过class名找到注册subscriber
* @param clazz
* @param subscriber
*/
private void unRegisterSubscriberByType(Class<?> clazz, Object subscriber)
CopyOnWriteArrayList<HxSubscription> hxSubscriptions = subscriptionByEventTypeMap.get(clazz);
if(hxSubscriptions != null )
int length = hxSubscriptions.size();
for (int i = 0; i < length; i++)
HxSubscription hxSubscription = hxSubscriptions.get(i);
if(hxSubscription.subscriber == subscriber)
hxSubscription.active = false;
hxSubscriptions.remove(i);
i--;
length--;
思路大概如此,通读了一下EventBus
的源码,然后通过自己的理解,写下这个简单实现版,也算是对EventBus的一种感悟吧。
源码为:
https://github.com/Microhx/studyCodes/tree/master/hxeventbus
以上是关于动手来写一个EventBus吧~~~~的主要内容,如果未能解决你的问题,请参考以下文章
EventBus手写实现事件通信框架 ( 订阅类-订阅方法缓存集合 | 事件类型-订阅者集合 | 订阅对象-事件类型集合 )
EventBus事件通信框架 ( 发送事件 | 根据事件类型获取订阅者 | 调用订阅方法 )
EventBus事件通信框架 ( 订阅方法注册 | 注册 事件类型 - 订阅类 + 订阅方法 到指定集合 | 取消注册 数据准备 )