EventBus源代码深度剖析

来源:互联网 发布:pscs3软件百度云 编辑:程序博客网 时间:2024/06/16 11:57

    • 分析之前的思考
    • 4个重要的数据结构
    • 注册时的行为
    • post时的行为
    • 解注册时的行为
    • 缓存处理
    • 可以优化的地方

本分析基于的代码可以在这里clone到,强烈建议边看代码边浏览本文,本文章主要分析eventbus的核心思想,因为版本差异,有一些细节可能不太一样,但我建议各位不要陷入细节无法自拔,站在较高角度去吃透它的思想才是我们的目的,因此,一些基本概念已经有很多文章已经讲过,下文不再赘述

分析之前的思考

  • eventbus的用法大家都很清楚,请看下面一种最简单的情况(一定要对照着上述地址的代码看,一些用法和当前版本的EventBus有名称上的差异),我们很清楚,在PostActivity.java中当调用了Bus.getDefault().post("Hello EventBus");这句代码后,RegisterActivity类中的onThreadEvent会被调用,而且是在子线程中.
// 在RegisterActivity.java类中的逻辑// onCreate函数中Bus.getDefault().register(this);// onDestroy函数中Bus.getDefault().unRegister(this);//一个接收函数    @BusReceiver(mode = EventMode.Thread)    public void onThreadEvent(final String event) {        Log.v(TAG, "onThreadEvent=" + Thread.currentThread().getName());        appendLog("onThreadEvent event=" + event                + " thread=" + Thread.currentThread().getName());    } // 在PostActivity.java类中的逻辑 // onCreate函数中 Bus.getDefault().post("Hello EventBus");
  • 我们不妨思考一下,如果是我们自己实现,我们需要考虑哪些问题,我在下面列出了一部分我们需要考虑的核心问题
    • 我们需要保存注册类的哪些信息,该如何保存?
    • 我们需要保存参数的哪些信息,该如何保存?
    • 我们该如何确定哪些函数会被调用?
    • 我们该如何控制他们在特定的线程中运行?
    • 我们需不需要考虑缓存的功能?如何提升框架的性能?
  • 我们接下来就一个一个解决上述的问题,再强调一遍,此时你一定要有一份完整的源代码,我的分析只能作为一个引子,讲解核心的思想,之后还需要你深入代码中把它融会贯通

4个重要的数据结构

  • 标识注解结构非常基础,直接看代码,Bus.EventMode是个枚举类型,有三种值,Sender,Main,Thread,分别标识着不同的线程
@Documented@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface BusReceiver {    // 默认回调的方法在主线程中执行    Bus.EventMode mode() default Bus.EventMode.Main;}
  • 先解决一个重要的问题,我们在存储什么?该如何存储,我们存储的是有特殊注解的方法(Method对象),这个方法中的参数,注册的对象以及相应的注解,所以我们拼装出来了第一个数据结构MethodInfo
public class MethodInfo {    // 方法对象    public final Method method;    // 注册对象的Class对象    public final Class<?> targetType;    // 参数的Class对象    public final Class<?> eventType;    // 没有什么关键的作用    public final String name;    // 注解的mode值    public final Bus.EventMode mode;    public MethodInfo(final Method method, final Class<?> targetClass, final Bus.EventMode mode) {        this.method = method;        this.targetType = targetClass;        this.eventType = method.getParameterTypes()[0];        this.mode = mode;        this.name = targetType.getName() + "." + method.getName()                + "(" + eventType.getName() + ")";    }    // else codes}
  • 再封装一层,做到最大化解耦,让MehodInfo只负责存储相应数据结构的职责,让Subscriber去做具体的执行功能,请注意invoke函数
class Subscriber {    public final MethodInfo method;    public final Object target;    // 我认为下面4个成员变量是多余的,因为可以从MethodInfo中拿到    public final Class<?> targetType;    public final Class<?> eventType;    public final Bus.EventMode mode;    public final String name;    public Subscriber(final MethodInfo method, final Object target) {        this.method = method;        this.target = target;        this.eventType = method.eventType;        this.targetType = method.targetType;        this.mode = method.mode;        this.name = method.name;    }// 使用反射调用注册类的相应函数,传递进来的event是post的内容    public Object invoke(Object event)            throws InvocationTargetException, IllegalAccessException {        return this.method.method.invoke(this.target, event);    }    // else codes}
  • 请注意,Subscriber只是负责了调用函数,但是没有去负责该在哪个线程中去执行,所以说,需要再封装一层,EventEmitter,它是一个Runnable对象,所以我们就可以使用Handler,Executor,控制它到底在哪个线程中执行!
public class EventEmitter implements Runnable {    private static final String TAG = Bus.TAG;    public final Bus bus;    public final Object event;    public final Subscriber subscriber;    public final Bus.EventMode mode;    public final boolean debug;    public EventEmitter(final Bus bus, final Object event,                        final Subscriber subscriber, final boolean debug) {        this.bus = bus;        this.event = event;        this.subscriber = subscriber;        this.mode = subscriber.mode;        this.debug = debug;    }    @Override    public void run() {        try {            if (debug) {                Log.v(TAG, "sending event:[" + event                        + "] to subscriber:[" + subscriber                        + "] at thread:" + Thread.currentThread().getName());            }            subscriber.invoke(event);        } catch (Exception e) {            if (debug) {                Log.e(TAG, "sending event:[" + event + "] to subscriber:["                        + subscriber + "] failed, reason: " + e, e);            }        }    }    @Override    public String toString() {        return "{" +                "event:[" + event +                "] to subscriber:[" + subscriber +                "]}";    }

看完四个数据结构后,感叹它巧妙的设计,利用层层包装解耦,符合单一职责的原则,使结构清晰易于维护

注册时的行为

Bus.getDefault().register(this)

  • 注册时我们主要做了两件事情
  • 第一件事,以注册类的完整类名为key,它其中的所有满足条件的方法为Value,存储到一个集合中
    final static Map<String, Set<MethodInfo>> sMethodCache = new ConcurrentHashMap<String, Set<MethodInfo>>();
  • 第二件事以参数类型为key,符合的方法集合为Value存储为一个ConcurrentHashMap(以方法参数为标准分割函数)
    private final Map<Class<?>, Set<Subscriber>> mSubscriberMap;

  • 我们来看一下这个过程中的重要的方法

    //获得注册对象中所有符合条件的函数,包括父类中的   private Set<MethodInfo> getMethods(Class<?> targetClass) {        // 完整的类名作为缓存的key        String cacheKey = targetClass.getName();        Set<MethodInfo> methods;        // 先看缓存是否有,如果没有去类中找寻符合要求的方法,然后在存储到缓存中        synchronized (Cache.sMethodCache) {            methods = Cache.sMethodCache.get(cacheKey);        }        if (methods == null) {            /**             * 找对象以及对象父类的符合要求的方法             */            methods = mMethodFinder.find(this, targetClass);            synchronized (Cache.sMethodCache) {                Cache.sMethodCache.put(cacheKey, methods);            }        }        return methods;    }
  • 跟入,看mMethodFinder.find(this, targetClass);的具体实现,代码很简单,请去MethodHelp.java类中查看,做一个简单的总结,此类的作用就是筛选出符合要求的Method拼装为MethodInfo集合,注意挑选的规则有以下几条(在isValidMethod函数中)
    • 首先它必须拥有BusReceiver注解,这个是大前提,之后需满足以下四个小条件
    • !Modifier.isPublic(method.getModifiers())函数必须是public的
    • Modifier.isStatic(method.getModifiers())函数不是静态的
    • method.getParameterTypes().length != 1函数只能有一个参数
    • !Modifier.isVolatile(method.getModifiers())这个条件很重要,是修复getDeclaredMethods存在的一个bug,请参考这里
    public static Set<MethodInfo> findSubscriberMethodsByAnnotation(            final Class<?> targetClass) {        final MethodConverter converter = new MethodConverter() {            @Override            public MethodInfo convert(final Method method) {                // check annotation                final BusReceiver annotation = method.getAnnotation(BusReceiver.class);                if (annotation == null) {                    return null;                }                if (!isValidMethod(method)) {                    return null;                }                return new MethodInfo(method, targetClass, annotation.mode());            }        };        return findSubscriberMethods(targetClass, converter);    }        /**     * 根据传入的相应对象的class对象,去检索"那些要被回调的方法",存储起来,并且父类方法都要存储,其实就是加了一个while循环,     * 内部再加入一个for循环     */    public static Set<MethodInfo> findSubscriberMethods(            final Class<?> targetClass, MethodConverter converter) {        Class<?> clazz = targetClass;        final Set<MethodInfo> methods = new HashSet<MethodInfo>();        while (!shouldSkipClass(clazz)) {            final Method[] clsMethods = clazz.getDeclaredMethods();            for (final Method method : clsMethods) {                final MethodInfo methodInfo = converter.convert(method);                if (methodInfo != null) {                    methods.add(methodInfo);                }            }            // search more methods in super class            clazz = clazz.getSuperclass();        }        return methods;    }

post时的行为

  • 存储以参数类型全称为key(如java.lang.String),此参数类型的父类型或者接口类型为value(父类型和接口类型也加入是因为考虑到了兼容性,这样不需要严格的匹配参数就可以调起函数)
    以参数类型为key的原因是因为eventbus确定调用哪个方法就是由参数类型而不是函数名确定的,所以说只要你post时的参数与你声明的回调函数的参数兼容,就可以调用!
 final static Map<String, Set<Class<?>>> sEventTypeCache = new ConcurrentHashMap<String, Set<Class<?>>>();
  • 获得了参数类型的所有兼容的类型后,一次传递需要传递的值和相应的兼容的类型
        for (Class<?> eventType : eventTypes) {            postEventByType(event, eventType);        }
  • 跟入postEventByType函数
    /**     * 发送某个事件给某个特定类型的订阅者     *     * @param event     事件对象     * @param eventType 匹配的事件类型     * @param <E>       事件类型     */    private synchronized <E> void postEventByType(final E event, final Class<?> eventType) {        final Set<Subscriber> subscribers = mSubscriberMap.get(eventType);        if (subscribers == null || subscribers.isEmpty()) {            return;        }        for (Subscriber subscriber : subscribers) {            sendEvent(new EventEmitter(this, event, subscriber, mDebug));        }    }
  • 前面说过EventEmitter的类型和作用,继续跟入sendEvent方法,此方法的作用是根据注解的模式选择相应的类去执行emitter的run方法,此方法逻辑比较简单,请去代码中查看Schedulers类,它会用工厂模式分别创建三个子类,SenderScheduler(直接调用emitter的run方法,以同步方式执行)负责在当前线程中执行,HandlerScheduler(依赖Handler)负责在主线程中执行,ExecutorScheduler(依赖Executor)负责在异步线程中执行,到最后就都会执行到Emitter的run方法中
    public void sendEvent(EventEmitter emitter) {        if (mDebug) {            Log.v(TAG, "send event:" + emitter);        }        if (EventMode.Sender.equals(emitter.mode)) {            mSenderScheduler.post(emitter);        } else if (EventMode.Main.equals(emitter.mode)) {            if (Helper.isMainThread()) {                mSenderScheduler.post(emitter);            } else {                mMainScheduler.post(emitter);            }        } else if (EventMode.Thread.equals(emitter.mode)) {            mThreadScheduler.post(emitter);        }    }
  • 看一下emitter的run方法,非常简单,到最后都会执行过来,只是过来的途径不同(上面分析的三个类负责),由下面可以看到是最终其实执行的是Subscriber的invoke方法,我们上面分析过,这个方法是用反射去调相应的method,到此就分析完毕了,最终就会成功的调用注册类中相应的方法了
    @Override    public void run() {        try {            subscriber.invoke(event);        } catch (Exception e) {}    }

解注册时的行为

  • 不知道大家有没有想过这个问题,post时,调用哪个方法是基于参数去判断的,那么,当我们解注册的时候,如何去确定解注册的那个类中的所有方法,我们需要把这些方法从mSubscriberMap中移除,要保证精确性而不能移除错误,因此,又多出来一个数据结构去做这件事情,它的key就是我们的类对象,它的value就是我们类对象中符合方法的所有的参数对象(想想为什么把参数对象作为value,而不是把方法对象作为value,因为我们有mSubscriberMap,可以根据参数对象取出方法对象,再通过方法对象Subscriber的target属性去和我们当前要解注册的对象对比,如果一致,那么把此方法对象从mSubscriberMap中删除)
    private final Map<Object, Set<Class<?>>> mEventMap;
    public <T> void unregister(final T target) {        if (mDebug) {            Log.v(TAG, "unregister() target:" + target);            mStopWatch.start("unregister()");        }        //mEventMap的添加元素逻辑在register方法中        final Set<Class<?>> eventTypes = mEventMap.remove(target);        if (eventTypes == null || eventTypes.isEmpty()) {            Log.v(TAG, "unregister() no subscriber for target:" + target);            return;        }        for (Class<?> eventType : eventTypes) {            Set<Subscriber> subscribers = mSubscriberMap.get(eventType);            if (subscribers == null || subscribers.isEmpty()) {                continue;            }            synchronized (mSubscriberMap) {                Iterator<Subscriber> it = subscribers.iterator();                while (it.hasNext()) {                    final Subscriber subscriber = it.next();                    if (subscriber.target == target) {                        it.remove();                        }                    }                }            }        }    }

缓存处理

  • events的性能瓶颈在两方面有体现.
    • 第一,就是我们去查找某个类中符合特定要求的方法并存储,因为我们不止要查找当前类,还需要查找父类,所有这是一笔不小的开销
    • 第二,我们要去把所有兼容的参数类型查找并存储,这也是一笔不小的开销
  • 根据以上的描述,我们就用了两个数据结构把他们在第一次search时缓存了起来,一个是完整的类名和对应的所有符合要求的方法,另一个是完整的参数名和所有兼容的类型
    static class Cache {        // key=注册类的完整类名 target.getClass().getName()        // value=注册类包含的合法的@BusReceiver方法对象集合        // targetTypeName->method set        final static Map<String, Set<MethodInfo>> sMethodCache =                new ConcurrentHashMap<String, Set<MethodInfo>>();        // key=事件类型的完整类名        // value=事件类型的所有父类和接口        // eventTypeName-> event type set        final static Map<String, Set<Class<?>>> sEventTypeCache =                new ConcurrentHashMap<String, Set<Class<?>>>();    }
  • 它俩使用和填充位置分别在register()和unRegister()函数中,请在AndroidStudio中自己使用command + F和command + G自行查看.

可以优化的地方

  • 当我们去搜寻一个类中符合要求的方法时,其实没有必要搜寻系统级别的类的方法,比如Object类中的方法,那么我们就可以做一些小小的优化,请看下面代码,代码很简单,不做赘述
    public static boolean shouldSkipClass(final Class<?> clazz) {        if (clazz == null || Object.class.equals(clazz)) {            return true;        }        final String clsName = clazz.getName();        return clsName.startsWith("java.")                || clsName.startsWith("javax.")                || clsName.startsWith("android.")                || clsName.startsWith("com.android.");    }
1 0
原创粉丝点击