EventBus源码解析

开篇说明

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

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

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

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

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

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

 private static final Map, 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> methodsByClass = new HashMap<>();

    @Override
    public boolean process(Set annotations, RoundEnvironment roundEv) {
   
        //1、收集各个类中的@Subscribe方法
        collectSubscribesMethod(annotations, roundEv);

        //2、将收集到的信息动态创建
        createJavaFile();
     
        return false;
    }
    
}

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

 private final Map> methodsByClass = new HashMap<>();
 
  private void collectSubscribesMethod(Set 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 methods = methodsByClass.get(classElement);
                    if (methods == null) {
                        methods = new HashSet();
                        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, SubscriberInfo> SUBSCRIBERES;\n\n");
            writer.write("    static {\n");
            writer.write("        SUBSCRIBERES = new HashMap, 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, SubscriberInfo> SUBSCRIBERES;

    static {
        SUBSCRIBERES = new HashMap, 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> methodsByClass = new HashMap<>();

映射成了:

 private static final Map, 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的话,如下图:
EventBus源码解析_第1张图片
其中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)

用图片直观表示如下图:
EventBus源码解析_第2张图片
SubscriberMethodInfo封装的信息如下:

public class SubscriberMethodInfo {

    //方法名
    final String methodName;
    //方法参数的类型
    final Class paramClass;

}

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

 class SubscribeUtils implements SubscriberInfoFinder{
 }

//SubscriberInfoFinder接口信息
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封装了方法名和方法参数类型
        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 final Map, CopyOnWriteArrayList> eventHandlerMap;
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        //搜索subscriber对象的事件类型
        Class eventType = subscriberMethod.eventType;

        //封装事件处理类
        EventHandler eventHandler = new EventHandler(subscriber, subscriberMethod);
       
        CopyOnWriteArrayList eventHandlers = eventHandlerMap.get(eventType);
        if (eventHandlers == null) {
            eventHandlers = new CopyOnWriteArrayList<>();
            eventHandlerMap.put(eventType, eventHandlers);
        }

        eventHandlers.add(eventHandler);
    }

注解的时候先获取subscriber的SubscriberInfo对象(见上图),然后获取subscriber的标有注解的方法组成的SubscriberMethod数据对象。
然后将每个SubscriberMethod转换成EventHandler(新版本的EventBus名字改成了Subscription) ,所以EventHandler 就如下图所示:
EventBus源码解析_第3张图片
通过注册方法将subscriber对象与subscriberMethod绑定形成了EventHandler对象,需要注意的是事件可能被多个注解方法处理,所以eventType和EventHandler是一对多的关系,所以Bus类中提供了:

//key为事件类型,value为EventHandler
private final Map, CopyOnWriteArrayList> eventHandlerMap;

EventHandler封装的数据如下:

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

以上面的demo代码MainActivity来看,最终eventHandlerMap形成构成的数据如下:
EventBus源码解析_第4张图片

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

 public void post(Object event) {
        //获取事件类型
        Class targetType = event.getClass();
       
       //获取匹配事件的处理类集合
        List 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源码解读

你可能感兴趣的:(框架源码分析)