美文网首页
EventBus源码解析开篇

EventBus源码解析开篇

作者: 夜色流冰 | 来源:发表于2018-06-27 15:46 被阅读27次

    开篇说明

    上篇博文《Otto源码解读》简单分析了Otto的实现原理,总的来说就是检索收集各个注册对象中的@Subscribe方法,然后用反射method.invoke(targetObj,event)执行之,但是对这些注解方法的检索收集是在运行时期进行的,所以效率上难免会有些不尽人意。本篇博文就运用AbstractProcessor将注解方法的检索放在编译期间就搞定,实现自己的Otto。

    当然本文的Otto没有实用性价值,因为在EventBus这中强大的面前,自己的只是简单的小儿科,通过这篇博文也是对自己有关知识点的巩固,项目源码在此

    通过本篇博文将会了解到:

    • AbstractProcessor的使用方法
    • 注解加反射的强大作用
    • 从中体会EventBus的实现原理,方便以后对EventBus进行源码解析

    本文的核心就是将Otto原来的运行时检索@Subscribe方法的过程,通过AbstractProcessor转移到编译时进行

    也就是说在编译期间将应用中标有@Subscribe的方法收集成起来,放入某个static变量的集合中去:

     private static final Map<Class<?>, SubscriberInfo> SUBSCRIBERES;
    

    为了博文的连贯性,本篇不会再赘述AbstractProcessor的使用方法,不明白的可以先阅读Android编译时注解APT实战体会下;或者下载博主的源码来看。

    闲言少叙,立马开车。

    AbstractProcessor的在本文的主要目的就是:

    • 检索收集@Subscribe方法
    • 将检索到的方法集合动态生成一个java类:

    下面进行详细说明。

    对@Subscribe方法进行扫描

    对于AbstractProcessor来说,是在其process方法里面进行的:

    /**
    *SupportedAnnotationTypes标明要扫描的注解*类
    *AutoService注解帮助自动生成resources/META-INF/services/javax.annotation.processing.Processor文件
    *
    */
    @SupportedAnnotationTypes("com.yanqiu.otto.bus.subscribe.Subscribe")
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    @AutoService(Processor.class)
    public class SubscribeProcessor extends AbstractProcessor {
        private Messager mMessager;
        private Filer mFiler;
        private Types mTypes;
    
        private final Map<TypeElement, Set<ExecutableElement>> methodsByClass = new HashMap<>();
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEv) {
       
            //1、收集各个类中的@Subscribe方法
            collectSubscribesMethod(annotations, roundEv);
    
            //2、将收集到的信息动态创建
            createJavaFile();
         
            return false;
        }
        
    }
    

    下面就来看看collectSubscribesMethod方法都做了什么:

     private final Map<TypeElement, Set<ExecutableElement>> methodsByClass = new HashMap<>();
     
      private void collectSubscribesMethod(Set<? extends TypeElement> annotations, RoundEnvironment roundEv) {
            //遍历@Subscribe标注的元素
            for (Element annotatedElement : roundEv.getElementsAnnotatedWith(Subscribe.class)) {
                //如果是方法
                if (annotatedElement instanceof ExecutableElement) {
                    ExecutableElement method = (ExecutableElement) annotatedElement;
                    if (checkHasNoErrors(method)) {
                        //通过getEnclosingElement获取方法所在的类,比如MainActivity中sub1和sub2方法的classElement就是com.apt.otto.MainActivity
                        TypeElement classElement = (TypeElement) method.getEnclosingElement();
                        Set<ExecutableElement> methods = methodsByClass.get(classElement);
                        if (methods == null) {
                            methods = new HashSet<ExecutableElement>();
                            methodsByClass.put(classElement, methods);
                        }
                        methods.add(method);
                    }
                }
            }//end for
    
        }
        
    }
    

    collectSubscribesMethod主要是遍历类中标注有@Subscribe的方法,在这里有有个Element的概念,在这里用下面的列表简单解释一下各个Element和java代码中的对应关系:

    代码 Element
    package com.apt.otto.Demo PackageElement
    public class Demo{ TypeElement
    private String value; VariableElement
    public Demo() ExecuteableElement
    public void setValue(String value) ExecuteableElement

    而我们的@Subscribe注解是作用在方法上的,所以我们在collectSubscribesMethod需要关注ExecuteableElement:

     if (annotatedElement instanceof ExecutableElement){
        。。。。
     }
    

    同样的,因为还需要知道标注了@Subscribe的某个方法位于哪个类中,Element的getEnclosingElement()可以做到这一点,比如:

    package com.apt.otto;
    public class MainActivity extends Activity {
    
        @Subscribe
        public void sub1(Demo demo) {
    
        }
    }    
    

    扫描获取大sub1这个ExecuteableElement对象,然后该对象调用getEnclosingElement(),打印其toString()将获取到com.apt.otto.MainActivity这一串信息。而这一信息就做为methodsByClass这个map的key来存储了

    当然一个类中的多个方法可以添加@Subscribe注解,是一对多的关系,所以methodsByClass的value就是一个Set集合来存储一个类中的多个注解方法。

    动态创建java类

    经过collectSubscribesMethod处理之后,我们得到了methodsByClass这个map,现在就需要将该map所保存的数据写到一个类里面,所以看看createJavaFile方法都做了什么:

    private void createJavaFile() {
           //省略部分代码
            BufferedWriter writer = null;
            try {
                String packageName = "com.yanqiu.otto.auto";
                JavaFileObject sourceFile = mFiler.createSourceFile(packageName + ".SubscribeUtils");
                writer = new BufferedWriter(sourceFile.openWriter());
                writer.write("package " + packageName + ";\n");
                writer.write("import com.yanqiu.otto.bus.subscribe.SubscriberMethodInfo;\n");
                writer.write("import com.yanqiu.otto.bus.subscribe.SubscriberInfo;\n");
                writer.write("import com.yanqiu.otto.bus.SubscriberInfoFinder;\n");
                writer.write("import java.util.HashMap;\n");
                writer.write("import java.util.Map;\n\n");
                writer.write("/** This class is generated by OttBus, do not edit. */\n");
                writer.write("public class SubscribeUtils implements SubscriberInfoFinder{\n");
                writer.write("    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBERES;\n\n");
                writer.write("    static {\n");
                writer.write("        SUBSCRIBERES = new HashMap<Class<?>, SubscriberInfo>();\n\n");
                writeIndexLines(writer);
                writer.write("    }\n\n");
                writer.write("    private static void addSubscribeInfo(SubscriberInfo info) {\n");
                writer.write("        SUBSCRIBERES.put(info.getSubscriberClass(), info);\n");
                writer.write("    }\n\n");
                writer.write("    @Override\n");
                writer.write("    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n");
                writer.write("        SubscriberInfo info = SUBSCRIBERES.get(subscriberClass);\n");
                writer.write("        if (info != null) {\n");
                writer.write("            return info;\n");
                writer.write("        } else {\n");
                writer.write("            return null;\n");
                writer.write("        }\n");
                writer.write("    }\n");
                writer.write("}\n");
    
    
            } catch (IOException e) {
              
            } finally {
               //省略部分代码
            }
        }
    
    

    最终我们动态创建的类如下:

    public class SubscribeUtils implements SubscriberInfoFinder{
       //注解方法的集合,key是目标类class对象,value是该类对应的注解方法集合
        private static final Map<Class<?>, SubscriberInfo> SUBSCRIBERES;
    
        static {
            SUBSCRIBERES = new HashMap<Class<?>, SubscriberInfo>();
    
            addSubscribeInfo(new SubscriberInfo(com.apt.otto.MainActivity.class ,new SubscriberMethodInfo[] {
                new SubscriberMethodInfo("sub2", String.class),
                new SubscriberMethodInfo("sub1", com.apt.otto.Demo.class),
                new SubscriberMethodInfo("sub3", String.class)
            }));
        
        }
    
        private static void addSubscribeInfo(SubscriberInfo info) {
            SUBSCRIBERES.put(info.getSubscriberClass(), info);
        }
    
        @Override
        public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
            SubscriberInfo info = SUBSCRIBERES.get(subscriberClass);
            if (info != null) {
                return info;
            } else {
                return null;
            }
        }
    }
    
    

    通过生成的代码SubscribeUtils我们发现主要是将:

     private final Map<TypeElement, Set<ExecutableElement>> methodsByClass = new HashMap<>();
    

    映射成了:

     private static final Map<Class<?>, SubscriberInfo> SUBSCRIBERES;
    

    其中SUBSCRIBERES这个map的key指的是@Subscribe方法所在的类信息,由methodsByClass的key映射而成;而methodsByClass的value则映射成SubscriberInfo一个对象。该对象包含了如下信息:

    public class SubscriberInfo {
    
        //@Subscribe的宿主,比如MainActivity.class
        private final Class subscriberClass;
        //某类中标有@Subscribe方法信息集合
        private final SubscriberMethodInfo[] methodInfos;
    }    
        
    

    其中SubscriberInfo的methodInfos数组则是由methodsByClass的value这个set集合映射而来。
    比如通过扫描MainActvity这个类之后:

    public class MainActivity extends Activity {
    
        @Subscribe
        public void sub1(Demo demo) {
    
        }
    
        @Subscribe
        public void sub2(String view) {
        }
    
        @Subscribe
        public void sub3(String view) {
    
        }
    }
    

    我们得到一个如下的SubscriberInfo对象:

    SubscriberMethodInfo[] methods=new SubscriberMethodInfo[] {
                new SubscriberMethodInfo("sub2", String.class),
                new SubscriberMethodInfo("sub1", com.apt.otto.Demo.class),
                new SubscriberMethodInfo("sub3", String.class)
            }
    
    //扫描得到的SubscriberInfo对象
    new SubscriberInfo(com.apt.otto.MainActivity.class ,methods)
    

    而我们的ExecutableElement则对应的变成了SubscriberMethodInfo,SubscriberMethodInfo封装的信息如下:

    public class SubscriberMethodInfo {
    
        //方法名
        final String methodName;
        //方法参数的类型
        final Class<?> paramClass;
    
    }
    

    到此为止由AbstractProcessor扫描注解到创建java文件的过程分析完毕,需要注意的是动态创建的类SubscribeUtils实现了SubscriberInfoFinder接口:

     class SubscribeUtils implements SubscriberInfoFinder{
     }
    
    //SubscriberInfoFinderji恶口信息
    public interface SubscriberInfoFinder {
        SubscriberInfo getSubscriberInfo(Class<?> subscriberClass);
    }
    

    之所以定义这个接口,除了方便扩展之外,还有个原因就是SubscribeUtils编译之后的为止在app/build/generated/source/apt/release/之下,我们没办法在别的module中使用,除非在build.gralde中配置依赖信息。因为有了接口,所以我们使用起来很方便,见如下代码:

    public class Bus {
        private SubscriberInfoFinder subscriberInfoFinder;
    
        public Bus(SubscriberInfoFinder subscriberInfoFinder) {
          
            this.subscriberInfoFinder = subscriberInfoFinder;
        }
    

    这样我们在Mactivity中就可以这样调用:

    //将动态生成的SubscribeUtils传入的Bus中
     private static Bus bus = new Bus(new SubscribeUtils());
    

    怎么使用SubscribeUtils

    通过上面的解释,我们获取注解文件的映射信息,但是我们怎么使用呢?

    通过Otto源码解读,我们知道最终需要通过:

    method.invoke(targetObj,event)
    

    来完成整个调用。那么现在我们还缺少两种对象,一个是Method对象,一个就是targetObj这个对象。

    获取Method对象

    那么Method对象是怎么产生的呢?我们知道获取一个类的Method对象可以通过下面的方式:

      Method method = subscriberClass.getDeclaredMethod(methodName, paramType);
    

    需要一个类的Class对象,方法名称,方法参数类型,我们就可以得到一个Methodd对象。而上文中的SubscriberMethodInfo对象正好包含了这些信息。且SubscriberInfo中包含了SubscriberMethodInfo[] methodInfos数组,所以在SubscriberInfo提供了getSubscriberMethods方法:

       public synchronized SubscriberMethod[] getSubscriberMethods() {
            int length = methodInfos.length;
            SubscriberMethod[] methods = new SubscriberMethod[length];
            for (int i = 0; i < length; i++) {
                SubscriberMethodInfo info = methodInfos[i];
                methods[i] = createSubscriberMethod(info.methodName, info.paramClass);
            }
            return methods;
        }
        
        public SubscriberMethod createSubscriberMethod(String methodName, Class<?> paramType) {
           
                //根据方法名和参数获取subscriberClass的对应方法
                Method method = subscriberClass.getDeclaredMethod(methodName, paramType);
                return new SubscriberMethod(method, paramType);
        
        }
    

    该方法返回了个SubscriberMethod数组,SubscriberMethod对象封装了如下信息:

    SubscriberMethod {
        public final Method method;
        public final Class<?> eventType;
    }
    

    峰回路转,终于看到了熟悉的Method对象,可以说整个流程下来没什么难度,就是为了下面这一行代码服务:

     Method method = subscriberClass.getDeclaredMethod(methodName, paramType);
    

    并最终生成了SubscriberMethod。

    因为运行method需要具体的Object对象,所以Bus类提供了register方法:

    
     public void register(Object subscriber) {
            Class<?> subscriberClass = subscriber.getClass();
    
            //获取Mactivity对应的SubscriberInfo对象
            SubscriberInfo subscriberInfo = subscriberInfoFinder.getSubscriberInfo(subscriberClass);
            
            
            //获取Mactivity中对应的注解方法
            SubscriberMethod[] subscriberMethods = subscriberInfo.getSubscriberMethods();
            synchronized (this) {
                for (SubscriberMethod subscriberMethod : subscriberMethods) {
                    subscribe(subscriber, subscriberMethod);
                }
            }
        }
        
        private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
            //搜索subscriber对象的事件类型
            Class<?> eventType = subscriberMethod.eventType;
    
            //封装事件处理类
            EventHandler eventHandler = new EventHandler(subscriber, subscriberMethod);
           
            CopyOnWriteArrayList<EventHandler> eventHandlers = eventHandlerMap.get(eventType);
            if (eventHandlers == null) {
                eventHandlers = new CopyOnWriteArrayList<>();
                eventHandlerMap.put(eventType, eventHandlers);
            }
    
            eventHandlers.add(eventHandler);
        }
    
    

    通过注册方法将subscriber对象与subscriberMethod绑定形成了EventHandler对象,需要注意的是事件可能被多个注解方法处理,所以eventType和EventHandler是一对多的关系,所以Bus类中提供了:

    private final Map<Class<?>, CopyOnWriteArrayList<EventHandler>> eventHandlerMap;
    
    

    EventHandler封装的数据如下:

    public class EventHandler {
        final Object subscriber;
        final SubscriberMethod subscriberMethod;
    }    
    

    那么我们在发送事件的时候,就能正确的运行注解方法了,看看Bus类post方法做了什么:

     public void post(Object event) {
            //获取事件类型
            Class<?> targetType = event.getClass();
           
           //获取匹配事件的处理类集合
            List<EventHandler> eventHandlers = eventHandlerMap.get(targetType);
            if (eventHandlers != null) {
                for (EventHandler eventHandler : eventHandlers) {
                    if (eventHandler.isMatch(targetType)) {   //方法处理
                        eventHandler.handleEvent(event);
                    }
                }
            }
    
        }
    

    找到能处理targetType事件的EventHandle列表,循环之,调用handleEvent处理即可:

    public void handleEvent(Object event) {
         //简单的反射调用
         subscriberMethod.method.invoke(subscriber, event);
           
        }
    

    到此为止,分析完毕,如果觉得读起来很拗口(因为本文写起来都觉得很啰嗦)可以参考博客源码
    来进行分析理解,还是有一定的学习参考价值的。因为本文的代码参考了EventBus的源码,可以说是EventBus的迷你版,窥一斑而知全豹,从中也可以体会到EventBus的实现原理,若果想分析EventBusy源码的话,本篇博文也算是起到穿针引线的作用。

    总之核心就是扫描注解信息,生成java文件,然后将注解的方法获取对应的Method对象进行invoke调用,仅此而已。只不过处理的过程中涉及的对象比较多,所以本文写起来比较啰哩啰嗦,如有不当之处,欢迎批评指正

    最后使用demo如下:

    public class Demo {
        private static Bus bus = new Bus(new SubscribeUtils());
    
        public Demo() {
            bus.register(this);
        }
    
        @Subscribe
        public void subScribeDemo(String msg) {
            System.out.println("收到了消息:  " + msg);
        }
    
        public void destroy() {
            bus.unregister(this);
        }
    
        public static void main(String args[]) {
            Demo demo = new Demo();
            bus.post("Hello Apt Otto");
            demo.destroy();
            bus.post("Hello Apt Otto again");
        }
    }
    
    

    参考资料:

    Android编译时注解APT实战

    EventBus源码

    Otto源码

    博客源码

    Otto源码解读

    相关文章

      网友评论

          本文标题:EventBus源码解析开篇

          本文链接:https://www.haomeiwen.com/subject/phzbyftx.html