动手来写一个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,可以取消订阅呢? 我也不卖关子了,总结一下自己所得:

  1. EventBus register是记录当前类中被@Subscribe注解修饰的方法,获取该方法中的参数、参数类型、线程调度、方法优先级统一存入静态容器;
  2. EventBus unRegister是将当前类中被@Subscribe 修饰的方法从静态容器中除去;
  3. 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)

  1. 我们首先需要定义一个自己的注解,来标明我们需要获取哪些方法,那么就定义一个@HxSubscribe吧,我们实现的是最简单版,没有线程调度模型、也没有调用优先级,所以@HxSubscribe只是个空标识,用来记录我们需要定义的方法.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface HxSubscribe 


  1. 我们已经定义了我们自己的@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,然后查看每个MethodHxSubscribe注解,有则说明使我们需要的方法,没有的话我们就直接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;
    

了解了CopyOnWriteArrayListHxSubscription之后,我们再来将订阅方法包装分类:

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)

动手造轮子:实现简单的 EventQueue

EventBus手写实现事件通信框架 ( 订阅类-订阅方法缓存集合 | 事件类型-订阅者集合 | 订阅对象-事件类型集合 )

EventBus 事件总线 原理

EventBus事件通信框架 ( 发送事件 | 根据事件类型获取订阅者 | 调用订阅方法 )

EventBus事件通信框架 ( 订阅方法注册 | 注册 事件类型 - 订阅类 + 订阅方法 到指定集合 | 取消注册 数据准备 )