手写简单的EventBus

EventBus是一种用于Android的事件发布-订阅总线框架,由GreenRobot开发,Gihub地址是:EventBus。它简化了应用程序内各个组件之间进行通信的复杂度,尤其是碎片之间进行通信的问题,可以避免由于使用广播通信而带来的诸多不便。
关于事件-发布框架后来还出现了类似的Rxbus、LiveDataBus。这里就不做介绍。
EventBus出现也有些年头了,目前的最新版本是3.2.0。下面简单介绍下它的基本用法。

依赖:

Via Gradle:

implementation 'org.greenrobot:eventbus:3.2.0'

Via Maven:


    org.greenrobot
    eventbus
    3.2.0

混淆

-keepattributes *Annotation*
-keepclassmembers class * {
    @org.greenrobot.eventbus.Subscribe ;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
 
# And if you use AsyncExecutor:
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    (java.lang.Throwable);
}

事件类型定义

public static class MessageEvent { /* Additional fields if needed */ }

事件接收方法定义

@Subscribe(threadMode = ThreadMode.MAIN)  
public void onMessageEvent(MessageEvent event) {/* Do something */};

注册和反注册

 @Override
 public void onStart() {
     super.onStart();
     EventBus.getDefault().register(this);
 }

 @Override
 public void onStop() {
     super.onStop();
     EventBus.getDefault().unregister(this);
 }

发布事件

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

总结下它的使用方式就是:订阅者需要在特定的生命周期注册和反注册,并且定义一个事件接收方法,当发布者post特定事件之后,订阅者就会在事件接收出做相应的操作。

原理图
图片.png

关于EventBus具体的原理还是需要看源码分析的,网上也有很多例子,这里暂时不做太多解释。
下面开始今天的重点:

手写一个简单的EventBus

基于它的原理:注解+反射,我们可以写个简单的事件通知框架。
首先分析他的基本构成:
1. 有一个自定义的注解,注解包含一个订阅者线程定义
2. 明确方法:register()、unRegister()、post()

根据步骤我们创建一个自定义注解:
/**
 * @author : wangzw
 * @date : 21-1-7下午4:09
 * @desc :
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TmacSubscribe {
    TmacSubscribeThreadMode THREAD_MODE() default  TmacSubscribeThreadMode.MAIN;
}

该注解中包含了一个线程定义的枚举类:TmacSubscribeThreadMode

/**
 * @author : wangzw
 * @date : 21-1-7下午4:10
 * @desc :
 */
public enum TmacSubscribeThreadMode {

    /**
     * Subscriber will be called directly in the same thread, which is posting the event. This is the default. Event delivery
     * implies the least overhead because it avoids thread switching completely. Thus this is the recommended mode for
     * simple tasks that are known to complete in a very short time without requiring the main thread. Event handlers
     * using this mode must return quickly to avoid blocking the posting thread, which may be the main thread.
     */
    POSTING,

    /**
     * On Android, subscriber will be called in Android's main thread (UI thread). If the posting thread is
     * the main thread, subscriber methods will be called directly, blocking the posting thread. Otherwise the event
     * is queued for delivery (non-blocking). Subscribers using this mode must return quickly to avoid blocking the main thread.
     * If not on Android, behaves the same as {@link #POSTING}.
     */
    MAIN,

    /**
     * On Android, subscriber will be called in Android's main thread (UI thread). Different from {@link #MAIN},
     * the event will always be queued for delivery. This ensures that the post call is non-blocking.
     */
    MAIN_ORDERED,

    /**
     * On Android, subscriber will be called in a background thread. If posting thread is not the main thread, subscriber methods
     * will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single
     * background thread, that will deliver all its events sequentially. Subscribers using this mode should try to
     * return quickly to avoid blocking the background thread. If not on Android, always uses a background thread.
     */
    BACKGROUND,

    /**
     * Subscriber will be called in a separate thread. This is always independent from the posting thread and the
     * main thread. Posting events never wait for subscriber methods using this mode. Subscriber methods should
     * use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number
     * of long running asynchronous subscriber methods at the same time to limit the number of concurrent threads. EventBus
     * uses a thread pool to efficiently reuse threads from completed asynchronous subscriber notifications.
     */
    ASYNC
}

该类其实就是EventBus源码中的ThreadMode类,用于指定订阅者的执行线程

第二步也是重点步骤

首先我们定义单例工具类TmacEventBus

  public static TmacEventBus getDefault() {
        if (instance == null) {
            synchronized (TmacEventBus.class) {
                if (instance == null) {
                    instance = new TmacEventBus();
                }
            }
        }
        return instance;
    }

接着定义一个register注册类

 /**
     * 注册
     *
     * @param subscriber 注册者 通常是fragment activity
     */
    public void register(Object subscriber) {
        //未注册则创建添加到缓存,反之不添加防止多次创建造成消耗
        List tmacSubscribeMethods = cacheMap.get(subscriber);
        if (tmacSubscribeMethods == null) {
            tmacSubscribeMethods = getSubScribeMethods(subscriber);
            cacheMap.put(subscriber, tmacSubscribeMethods);
        }

    }

注册类我们传入的是一个订阅者。举个例子:一个Activity如果是订阅者,那么他一定需要定义带有自定义注解@TmacSubscribe的方法。根据这个思路我们需要遍历传入的订阅者的所有方法筛选出订阅方法。这里我们需要用到一个Map集合去缓存已经存在的订阅者。

private Map> cacheMap;

第一个参数表示订阅者,第二个参数表示带有@TmacSubscribe的订阅方法的一些属性的对象。
举个Activity中订阅方法的例子:

 @TmacSubscribe(THREAD_MODE = TmacSubscribeThreadMode.MAIN)
    public void receive(CommonEvent event) {
        if (event != null) {
           /* Additional fields if needed */
        }
    }

可以看到,该方法可以分解成:方法:Method、参数类型:Class 以及执行线程类型
TmacSubscribeThreadMode
所以我们可以定义出TmacSubscribeMethod:

public class TmacSubscribeMethod {
    private Method method;

    private Class eventType;

    private TmacSubscribeThreadMode tmacThreadMode;


    public TmacSubscribeMethod(Method method, Class eventType, TmacSubscribeThreadMode tmacThreadMode) {
        this.method = method;
        this.eventType = eventType;
        this.tmacThreadMode = tmacThreadMode;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public Class getEventType() {
        return eventType;
    }

    public void setEventType(Class eventType) {
        this.eventType = eventType;
    }

    public TmacSubscribeThreadMode getTmacThreadMode() {
        return tmacThreadMode;
    }

    public void setTmacThreadMode(TmacSubscribeThreadMode tmacThreadMode) {
        this.tmacThreadMode = tmacThreadMode;
    }
}

好了,我们回到上文说到的register方法,遍历订阅者的TmacSubscribeMethod,得到相应的信息然后通过Method.invoke方法去执行订阅方法。

    public void register(Object subscriber) {
        //未注册则创建添加到缓存,反之不添加防止多次创建造成消耗
        List tmacSubscribeMethods = cacheMap.get(subscriber);
        if (tmacSubscribeMethods == null) {
            tmacSubscribeMethods = getSubScribeMethods(subscriber);
            cacheMap.put(subscriber, tmacSubscribeMethods);
        }

    }

所有的核心就在getSubScribeMethods 方法

 private List getSubScribeMethods(Object subscriber) {
        //获取到订阅者类
        Class aClass = subscriber.getClass();
        List list = new ArrayList<>();
        while (aClass != null) {
            //如果是系统的类直接忽略
            String name = aClass.getName();
            if (name.startsWith("java.") ||
                    name.startsWith("javax.") ||
                    name.startsWith("android.") ||
                    name.startsWith("androidx.")) {
                break;
            }

            //获取到所有方法
            Method[] declaredMethods = aClass.getDeclaredMethods();
            if (declaredMethods != null) {
                for (Method declaredMethod : declaredMethods) {
                    int modifiers = declaredMethod.getModifiers();
                    if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                        //获取每个方法上面的特定注解
                        TmacSubscribe annotation = declaredMethod.getAnnotation(TmacSubscribe.class);
                        if (annotation == null) {
                            continue;
                        }
                        //参数列表
                        Class[] parameterTypes = declaredMethod.getParameterTypes();
                        if (parameterTypes.length != 1) {
                            throw new TmacEventBusException("@Subscribe method " + declaredMethod +
                                    "must have exactly 1 parameter but has " + parameterTypes.length);
                        }

                        if (annotation != null) {
                            //获取threadMode
                            TmacSubscribeThreadMode tmacSubscribeThreadMode = annotation.THREAD_MODE();
                            list.add(new TmacSubscribeMethod(declaredMethod, parameterTypes[0], tmacSubscribeThreadMode));

                        }
                    }else if(declaredMethod.isAnnotationPresent(TmacSubscribe.class)){
                        String methodName = declaredMethod.getDeclaringClass().getName() + "." + declaredMethod.getName();
                        throw new TmacEventBusException(methodName +
                                " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
                    }


                }
            }

            //一层一层判断
            aClass = aClass.getSuperclass();
        }

        return list;

    }

分析
根据传入的订阅者,这里可以想象成是一个自定义的MyActivity。
1.首先我们排除掉系统Activity类。
2.获取到MyActivity所有的方法,通过declaredMethod.getAnnotation(TmacSubscribe.class);获取该方法是否带有自定义注解,如果存在则说明该方法是订阅方法。
3.通过Class[] parameterTypes = declaredMethod.getParameterTypes();获取参数列表,如果该参数个数不是1个则抛出运行时异常.
4.构造TmacSubscribeMethod类,添加到缓存中。
以上就是简单的注册方式。有了注册那必须要有一个反注册方法。

 /**
     * 反注册
     *
     * @param subscriber
     */
    public void unRegister(Object subscriber) {
        List list = cacheMap.get(subscriber);
        if (list != null) {
            cacheMap.remove(subscriber);
        }
    }

好了,订阅者的注册,反注册方法都有了,那么接下来就介绍下发布者的post方法。
首先整理下思路:订阅者首先进行注册,此时缓存中就有了一个订阅者-订阅信息一一对应的关系,从订阅信息中我们可以拿到 方法:Method、参数类型:Class 以及执行线程类型
TmacSubscribeThreadMode。那么通过method.invoke方法我们就可以执行订阅者的订阅方法,同时根据TmacSubscribeThreadMode,配合handler和线程池我们就可以指定订阅方法的执行线程 了。
根据这个思路我们得到如下代码

 /**
     * 发送
     *
     * @param event 事件类型
     */
    public void post(Object event) {
        Set subscribeSet = cacheMap.keySet();
        Iterator subscribeIterator = subscribeSet.iterator();

        while (subscribeIterator.hasNext()) {
            //获取订阅者
            Object subscriber = subscribeIterator.next();
            List list = cacheMap.get(subscriber);
            for (TmacSubscribeMethod tmacSubscribeMethod : list) {
                if (tmacSubscribeMethod.getEventType().isAssignableFrom(event.getClass())) {
                    switch (tmacSubscribeMethod.getTmacThreadMode()) {
                        case MAIN:
                            //订阅者和发布者都在主线程
                            if (Looper.myLooper() == Looper.getMainLooper()) {
                                invoke(tmacSubscribeMethod.getMethod(), subscriber, event);
                            } else {
                                //发布者异步线程发布,接收者在主线程,利用handler切换到主线程
                                handler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        invoke(tmacSubscribeMethod.getMethod(), subscriber, event);
                                    }
                                });

                            }
                            return;
                        case ASYNC:
                            // 发布者在主线程发布, 订阅者在异步线程
                            if (Looper.myLooper() == Looper.getMainLooper()) {
                                executorService.submit(new Runnable() {
                                    @Override
                                    public void run() {
                                        invoke(tmacSubscribeMethod.getMethod(), subscriber, event);
                                    }
                                });
                            } else {
                                //发布者和订阅者都在异步线程
                                invoke(tmacSubscribeMethod.getMethod(), subscriber, event);

                            }

                            break;
                    }
                }
            }


        }
    }

分析
其实比较简单,总的一句话就是:通过缓存我们就可以拿到所有的订阅者以及订阅者方法信息:
TmacSubscribeMethod。根据制定的线程模式配合handler以及线程池做到线程切换指定订阅方法的执行线程。核心仍是 method.invoke("执行类","参数列表")

 private void invoke(Method method, Object subscriber, Object params) {
        try {
            method.invoke(subscriber, params);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

总结

EventBus 的主要实现机制还是基于反射,一句话概括就是:

注册时传入目标类对象,然后利用反射筛选出 @Subscribe 的方法,然后以相同的参数类型为 key,将不同的方法合并为 list 作为 value,得到一个 map 集合;
当用户 post 数据时,再以数据类型为 key,从 map 中取出对应的方法 list,然后遍历 list,再利用反射机制对指定的方法执行 invoke 操作,完成调用;
链接:https://www.jianshu.com/p/4883397870e6
来源:

当然这里只是通过注解+放射的方式完成的一个简单的事件订阅-发布框架。对于EventBus的粘性事件暂未研究。

你可能感兴趣的:(手写简单的EventBus)