注解(Annotation)-Android讲解

3,332 阅读15分钟

对于注解(Annotation),我们在项目中经常会使用到,所以我们有必要知道注解(Annotation)是怎么工作的,是怎么帮我们把复杂的业务逻辑解耦的;怎么自定义自己的注解(Annotation)

注解(Annotation)有两种,一种是运行时注解, 一种是编译时注解,下面我们一一介绍

元注解

我们先来简单介绍一下元注解;元注解就是用来定义自定义注解的注解,我们常用的元注解就四个@Target@Documented@Retention@Inherited

  • @Target: 用来指定注解的使用范围;比如类、方法、字段等

    public enum ElementType {
        TYPE, // 类声明
        FIELD, // 字段声明
        METHOD, //方法声明
        PARAMETER, //参数声明
        CONSTRUCTOR, //构造函数声明
        LOCAL_VARIABLE, // 局部变量声明
        ANNOTATION_TYPE, // 注释类型声明
        PACKAGE, // 包声明
        TYPE_PARAMETER, // 类型参数声明(常用于泛型的类型参数进行注解)
        TYPE_USE; // 类型使用声明(常用于泛型的类型参数进行注解)
    
        private ElementType() {
        }
    }
    
  • @Documented: 用来指定被标注的注解会包含在javadoc中

  • @Retention: 用来指定注解的生命周期,比如源码、class、运行时

    public enum RetentionPolicy {
        SOURCE, // 源码
        CLASS, // class
        RUNTIME; // 运行时
    
        private RetentionPolicy() {
        }
    }
    
  • @Inherited: 指定子类可以继承父类的注解,只能是类上的注解,方法和字段的注解不能被继承

运行时注解

运行时注解是指程序在运行的过程中,通过反射去获取方法、属性等成员的注解信息,来实现一些业务逻辑

比如给属性自动赋值

定义BindValue如下:

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindValue {
    public String value() default "";
}

然后定义一个解析BindValue的处理类

public class BindValueProcessor {

    public static void bind(Object object) {
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 通过field.getAnnotation拿到BindValue注解信息
            // method、class等都有getAnnotation方法获取注解信息
            BindValue bindValue = field.getAnnotation(BindValue.class);
            try {
                if (bindValue != null) {
                    // 给field 赋值 为 @BindValue 注解上的 value 值
                    // 如果field是private,则需要调用setAccessible
                    // field.setAccessible(true);
                    field.set(object, bindValue.value());
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

最后再使用@BindValue注解

public class AnnotationActivity extends AppCompatActivity {

	// 给name赋值为 张三
    @BindValue("张三")
    public String name;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_annotation);
        // 调用此方法解析@BindValue注解
        BindValueProcessor.bind(this);
    }
}

运行时注解其实比较简单,就是通过反射获取对应成员的注解信息,然后做相应处理

上面的demo比较简单,主要是为了讲解运行时注解的工作原理,然后怎么去方便的定义一个自己的运行时注解

下面我们来看看运行时注解 在 Android 中的一些案例

EventBus

看源码得知EventBus的对注解的解析有两种方案,一种是编译时解析(生成索引类),一种是运行时解析,下面我们来看看EventBus的运行时解析注解的源码

先看看findSubscriberMethods的实现,这里面就有注解解析两种方案的实现分支

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    ...  

    // ignoreGeneratedIndex 默认为false
    if (ignoreGeneratedIndex) { 
    	// 运行时解析注解(使用反射)
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
    	// 使用编译时解析注解生成的索引(注意 需要手动开启索引)
    	// 如果在findUsingInfo没有找到索引,则还是使用反射
        subscriberMethods = findUsingInfo(subscriberClass);
    }
	...  
}

下面我们来看看EventBus反射的具体实现

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        // while循环--读取父类的@Subscribe注解的信息
        while (findState.clazz != null) {
            findUsingReflectionInSingleClass(findState);
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }
	
	// 这个方法就是真正使用反射去解析注解的方法了
    private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        for (Method method : methods) {
            int modifiers = method.getModifiers();
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
                        Class<?> eventType = parameterTypes[0];
                        if (findState.checkAdd(method, eventType)) {
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                        }
                    }
                } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                    String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                    throw new EventBusException("@Subscribe method " + methodName +
                            "must have exactly 1 parameter but has " + parameterTypes.length);
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException(methodName +
                        " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
            }
        }
    }

其实上面的代码 跟 我一开始写的 demo 原理一样,只是EventBus考虑到一些复杂的业务场景,做了一些封装而已;比如将method@Subscribe注解上的信息封装成SubscriberMethod对象并保存起来,最后在调用EventBus.getInstance().post(Event)时,找到对应的SubscriberMethod对象,然后根据注解上的threadMode信息等在调用method.invoke()方法时做线程切换等

Lifecycle

我们再来看一个Android里非常常用的组件库Lifecycle, 它的@OnLifecycleEvent也是运行时注解

废话不多说,直接看源码

// 解析Class @OnLifecycleEvent注解,并封装成CallbackInfo对象
CallbackInfo getInfo(Class klass) {
    CallbackInfo existing = mCallbackMap.get(klass);
    if (existing != null) {
        return existing;
    }
    existing = createInfo(klass, null);
    return existing;
}

private CallbackInfo createInfo(Class klass, @Nullable Method[] declaredMethods) {
    Class superclass = klass.getSuperclass();
    Map<MethodReference, Lifecycle.Event> handlerToEvent = new HashMap<>();
    if (superclass != null) {
    	// 使用递归--不断解析父类的@OnLifecycleEvent注解
        CallbackInfo superInfo = getInfo(superclass);
        if (superInfo != null) {
            handlerToEvent.putAll(superInfo.mHandlerToEvent);
        }
    }

	// 遍历所有的接口,解析@OnLifecycleEvent注解
    Class[] interfaces = klass.getInterfaces();
    for (Class intrfc : interfaces) {
        for (Map.Entry<MethodReference, Lifecycle.Event> entry : getInfo(
                intrfc).mHandlerToEvent.entrySet()) {
            // 发现重复定义,会覆盖
            verifyAndPutHandler(handlerToEvent, entry.getKey(), entry.getValue(), klass);
        }
    }
	
    // 真正使用反射--解析@OnLifecycleEvent注解
    Method[] methods = declaredMethods != null ? declaredMethods : getDeclaredMethods(klass);
    boolean hasLifecycleMethods = false;
    for (Method method : methods) {
        OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
        if (annotation == null) {
            continue;
        }
        hasLifecycleMethods = true;
        Class<?>[] params = method.getParameterTypes();
        int callType = CALL_TYPE_NO_ARG;
        if (params.length > 0) {
            callType = CALL_TYPE_PROVIDER;
            if (!params[0].isAssignableFrom(LifecycleOwner.class)) {
                throw new IllegalArgumentException(
                        "invalid parameter type. Must be one and instanceof LifecycleOwner");
            }
        }
        Lifecycle.Event event = annotation.value();

        if (params.length > 1) {
            callType = CALL_TYPE_PROVIDER_WITH_EVENT;
            if (!params[1].isAssignableFrom(Lifecycle.Event.class)) {
                throw new IllegalArgumentException(
                        "invalid parameter type. second arg must be an event");
            }
            if (event != Lifecycle.Event.ON_ANY) {
                throw new IllegalArgumentException(
                        "Second arg is supported only for ON_ANY value");
            }
        }
        if (params.length > 2) {
            throw new IllegalArgumentException("cannot have more than 2 params");
        }
        MethodReference methodReference = new MethodReference(callType, method);
        // 发现重复定义,会覆盖
        verifyAndPutHandler(handlerToEvent, methodReference, event, klass);
    }
    CallbackInfo info = new CallbackInfo(handlerToEvent);
    mCallbackMap.put(klass, info);
    mHasLifecycleMethods.put(klass, hasLifecycleMethods);
    return info;
}

Lifecycle其实就是将class@OnLifecycleEvent注解信息封装一个CallbackInfo对象,然后再封装成一个GenericLifecycleObserver对象, 并缓存在Lifecycling的缓存中,然后在当生命周期发生变化的时候在dispatchEvent方法中调用GenericLifecycleObserveronStateChanged方法,最后调用mMethod.invoke(target)方法实现回调

static class ObserverWithState {
    State mState;
    GenericLifecycleObserver mLifecycleObserver;

    ObserverWithState(LifecycleObserver observer, State initialState) {
    	// 如果缓存里有 则直接从缓存中取,否则开始解析observer的@OnLifecycleEvent注解信息,并缓存起来
        mLifecycleObserver = Lifecycling.getCallback(observer);
        mState = initialState;
    }

    void dispatchEvent(LifecycleOwner owner, Event event) {
        State newState = getStateAfter(event);
        mState = min(mState, newState);
        // 如果是反射的实现,则mLifecycleObserver 是ReflectiveGenericLifecycleObserver对象
        mLifecycleObserver.onStateChanged(owner, event);
        mState = newState;
    }
}
    
class ReflectiveGenericLifecycleObserver implements GenericLifecycleObserver {
    private final Object mWrapped;
    private final CallbackInfo mInfo;

    ReflectiveGenericLifecycleObserver(Object wrapped) {
        mWrapped = wrapped;
        mInfo = ClassesInfoCache.sInstance.getInfo(mWrapped.getClass());
    }

    @Override
    public void onStateChanged(LifecycleOwner source, Event event) {
    	// 在invokeCallbacks方法中会调用 mMethod.invoke(target) 方法实现回调
        mInfo.invokeCallbacks(source, event, mWrapped);
    }
}

可见实现一个运行时注解其实非常简单,直接使用反射获取注解信息,然后做相应的逻辑封装处理即可

运行时注解有一个很大的问题就是性能问题,因为使用了java的反射机制;所以这就需要你自己去做衡量了,去做各种优化处理了

下面我们来看看编译时注解

编译时注解

编译时注解是指注解处理器(Annotation Processor)在代码编译的过程中扫描和处理代码中的注解(Annotation); 正所谓是在代码编译的过程中处理,所以一般对代码运行性能没什么影响;

由于编译时注解不能对已有的java类做任何修改,所以一般都是用来生成新的java文件来做相应的业务处理

怎么定义一个编译时注解

  1. 创建自己的注解处理器

    由于Android项目是没办法使用javax包下的AbstractProcessor类,所以必须要使用AndroidStudio新建一个java library(File -> new Module -> 选择Java Library), 然后定义一个AbstractProcessor的子类, 并实现对应的方法

    public class BindValueProcessor extends AbstractProcessor {
    
    	/**
         * 初始化
         * @param processingEnvironment
         */
    	@Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
        }
        
        /**
         * 支持的java版本号
         * 推荐使用SourceVersion.latestSupported()
         */
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        /**
         * 定义这个注解处理器处理哪些注解(必须重写,否则不会处理任何注解,即不会走process回调)
         */
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> types = new LinkedHashSet<>();
            types.add(BindValue.class.getCanonicalName());
            return types;
        }
    
        /**
         * 基本上不需要重写这个方法
         * 主要是定义在build.gradle文件下配置的arguments支持的参数
         * 定义之后可以在process方法中使用processingEnv.getOptions().get(key)获取value;
         * javaCompileOptions {
         *    annotationProcessorOptions {
         *        arguments = [ key : 'value' ]
         *    }
         * }
         */
        @Override
        public Set<String> getSupportedOptions() {
            return super.getSupportedOptions();
        }
    
        /**
         * 核心方法--在这个方法里解析注解信息,并生成新的java文件等
         * @param annotations
         * @param roundEnvironment
         * @return
         */
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
        	...
            return true;
        }
    }
    

    对于AbstractProcessor的各个方法的解释请看对应方法上的注释,其中非常重要的方法是对于process方法的实现,下面会介绍process方法的实现

  2. 注册注解处理器

    定义完你的注解处理器之后(BindValueProcessor)之后,然后就需要注册你的注解处理器

    main下新建resources文件夹,然后在resources下新建META-INF文件夹,然后在META-INF下新建services文件夹,最后在services下新建javax.annotation.processing.Processor文件;即文件结构为main/resources/META-INF/services/javax.annotation.processing.Processor

    最后打开javax.annotation.processing.Processor文件将你的注解处理器添加到里面(包名+类名); 比如我demo中的com.fc.annotation.annotation.BindValueProcessor; 当编辑javax.annotation.processing.Processor文件的时候,其实AndroidStudio有提示, 会列出所有可配置的注解处理器,选择你自己的注解处理器即可

  3. 在app中使用你的注解处理器

    直接在build.gradle添加依赖即可

    dependencies {
    	...
    	
    	// 针对Java
    	annotationProcessor project(":library")
    	// 针对Kotlin
    	kapt project(":library")
    }
    

    这里是直接依赖的你的上面新建的Java Library module, 你可以将这个Library发版到maven仓库,然后再去依赖maven仓库里的library, 这不是我要说的重点,所以对于怎么发版到maven仓库 请自己研究

到这里的话,整个流程都做的差不多了,现在就差process方法的具体实现了,这也是对于一个新手比较迷茫的地方,因为对于javax.annotation.processing包下的各种Api不熟,不知道怎么用;下面我们先来做一下API 扫盲工作

API 扫盲

  1. Messager对象(在控制台上打印日志)

    对于日志打印,一般大家直接想到的是直接使用LogSystem.out.println(), 当你用这些方法输出日志的时候,你会发现控制台没有任何你的日志;对于注解处理器,如果想要输出日志,就需要使用它提供的Messager对象

     // 输出警告类型的日志
     processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, message);
     
     // 输出错误类型的日志,会导致build失败
     processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message);
     
     // 输出记录类型的日志,大多数情况下不会控制台不会输出这种类型的日志
     // 如果要输出这种类型的日志,则需要加上 --info 或 --debug(比如 ./gradlew assembleDebug --info)
     processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message);
    

    常用的日志类型就这三种,大多数情况下控制台只会输出WARNINGERROR类型的日志,如果要输出其它类型的日志,需要在gradlew命令上加上--info 或 --debug参数;

    processingEnvAbstractProcessor的一个成员变量

    知道输出日志后,我们就可以配合日志 来调试我们的代码了,下面介绍去怎么解析 FieldMethodClass,分别对应注解(Annotation)FieldMethodClass上的应用

  2. 解析Field - VariableElement

    VariableElement表示一个属性、enum 常量、方法或构造方法参数、局部变量或异常参数;

    下面对于FieldMethodClass的解析都以下面的代码为例

    @BindValue
    public class AnnotationActivity<T, K extends List> extends AppCompatActivity implements Runnable {
    
        @BindValue
        public String name;
    
        @BindValue
        public void test(View view, String a, List<String> list) {
    
        }
        ...
    }
    

    首先我们在process方法中可以获取被@BindValue注解的全部Element元素集合

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
    	// 获取 被@BindValue注解的Element元素集合,下面主要介绍VariableElement、ExecutableElement、TypeElement
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindValue.class);
        ...
    }
    

    比如我们要解析被@BindValue注解的name属性

    /**
     * 解析被注解的field
     * @param element
     */
    private void resolveVariableElement(VariableElement element) {
        StringBuffer buffer = new StringBuffer();
        // 获取修饰符 public、static、final等等
        for (Modifier modifier : element.getModifiers()) {
            buffer.append(modifier.toString()).append(" ");
        }
        // 获取field 类型
        buffer.append(element.asType().toString()).append(" ");
        // 获取field 名称
        buffer.append(element.getSimpleName());
        processingEnv.getMessager().printMessage("====VariableElement: "+ buffer.toString());
    }
    

    输出的日志如下:

    ====VariableElement: public java.lang.String name
    

    由于VariableElement也可以表示方法的参数,所以 对于方法参数的解析可以使用上面的api

  3. 解析Method - ExecutableElement

    ExecutableElement表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素

    比如我们要解析被@BindValue注解的test方法

    /**
     * 解析被注解的method
     * @param element
     */
    private void resolveExecutableElement(ExecutableElement element) {
        StringBuffer buffer = new StringBuffer();
        // 获取修饰符 public、static、final等等
        for (Modifier modifier : element.getModifiers()) {
            buffer.append(modifier.toString()).append(" ");
        }
        // 获取返回 类型
        buffer.append(element.getReturnType().toString()).append(" ");
        // 获取方法 名称
        buffer.append(element.getSimpleName());
        // 获取方法 参数
        buffer.append("(");
        List<? extends VariableElement> parameters = element.getParameters();
        for (VariableElement parameterElement : parameters) {
            // 获取方法 参数类型和参数名
            buffer.append(parameterElement.asType().toString() + " " + parameterElement.getSimpleName() + ", ");
        }
        buffer.append(")");
        println("====ExecutableElement: "+ buffer.toString());
    }
    

    输出的日志如下:

     ====ExecutableElement: public void test(android.view.View view, java.lang.String a, java.util.List<java.lang.String> list)
    
  4. 解析Class - TypeElement

    TypeElement表示一个类或接口程序元素

    比如我们要解析被@BindValue注解的AnnotationActivity类的信息

    /**
     * 解析被注解的class、interface、enum
     * @param element
     */
    private void resolveTypeElement(TypeElement element) {
        StringBuffer buffer = new StringBuffer();
        // 获取修饰符 public、static、final等等
        for (Modifier modifier : element.getModifiers()) {
            buffer.append(modifier.toString()).append(" ");
        }
    
        // 获取被注解的class 是类、接口、还是枚举
        if (element.getKind() == ElementKind.CLASS) { // class
            buffer.append(element.getKind().name().toLowerCase()).append(" ");
        } else if (element.getKind() == ElementKind.INTERFACE) { // interface
            buffer.append(element.getKind().name().toLowerCase()).append(" ");
        } else if (element.getKind() == ElementKind.ENUM) { // enum
            buffer.append(element.getKind().name().toLowerCase()).append(" ");
        }
    
        // 获取class 名称
        buffer.append(element.getSimpleName());
    
        // 获取定义的class范型信息(即"<>"指定的信息)
        List<? extends TypeParameterElement> patterns = element.getTypeParameters();
        if (patterns != null && !patterns.isEmpty()) {
            buffer.append("<");
            for (TypeParameterElement typeParameterElement : patterns) {
                DeclaredType typeMirror = (DeclaredType) typeParameterElement.getBounds().get(0);
                if (typeMirror.toString().equals("java.lang.Object")) { // 如果没有指定范型的类型,则是Object
                    buffer.append(typeParameterElement.getSimpleName());
                } else {
                    buffer.append(typeParameterElement.getSimpleName() + " extends " + typeMirror.asElement().getSimpleName());
                }
                buffer.append(", ");
            }
            buffer.delete(buffer.length() - 2, buffer.length());
            buffer.append(">");
        }
        buffer.append(" ");
    
        // 获取继承的父类信息
        TypeMirror superMirror = element.getSuperclass();
        if (superMirror instanceof DeclaredType) {
        		// 父类也是TypeElement类型,所以直接强转
            TypeElement superElement = (TypeElement) processingEnv.getTypeUtils().asElement(superMirror);
            // 又可以 使用resolveTypeElementSet解析父类
            // resolveTypeElementSet(superElement);
            buffer.append("extends " + superElement.getSimpleName()).append(" ");
            // 解析父类的范型信息 如上操作即可
        }
    
        // 获取实现的接口
        List<? extends TypeMirror> interfaceTypeMirror = element.getInterfaces();
        if (interfaceTypeMirror != null && !interfaceTypeMirror.isEmpty()) {
            buffer.append("implements").append(" ");
            for (TypeMirror typeMirror : interfaceTypeMirror) {
                // 接口也是TypeElement类型,所以直接强转
                TypeElement interfaceElement = (TypeElement) processingEnv.getTypeUtils().asElement(typeMirror);
                // 又可以 使用resolveTypeElementSet解析interface
                // resolveTypeElementSet(interfaceElement);
                buffer.append(interfaceElement.getSimpleName()).append(", ");
            }
            // 删除最后的", "
            buffer.delete(buffer.length() - 2, buffer.length());
        }
    
        println("====TypeElement: "+ buffer.toString());
    }
    

    输出日志如下:

    ====TypeElement: public class AnnotationActivity<T, K extends List> extends AppCompatActivity implements Runnable
    

    上面的代码对于class的解析比较长,因为写的比较全,考虑到了父类、接口、范型等等,对于FeildMethod的解析 如果需要考虑范型,也可以使用上面的getTypeParameters()去处理

    最后再看看process现在的实现

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
        for (TypeElement annotation : annotations) {
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotation);
            for (Element element : elements) {
                if (element instanceof ExecutableElement) {
                    resolveExecutableElement((ExecutableElement) element);
                } else if (element instanceof VariableElement) {
                    resolveVariableElement((VariableElement) element);
                } else if (element instanceof TypeElement) {
                    resolveTypeElement((TypeElement) element);
                } else {
                    
                }
            }
        }
        return true;
    }
    
  5. Filer对象(生成java文件)

    Filer对象的API比较少,用起来也比较简单,常用来生成文件, 比如

    try {
        Filer filer = processingEnv.getFiler();
        JavaFileObject sourceFile = filer.createSourceFile("Test");
        BufferedWriter writer = new BufferedWriter(sourceFile.openWriter());
        writer.write("public class Test {\n");
        writer.write("      public static void main(String[] args) {\n");
        writer.write("          System.out.println(\"test\");\n");
        writer.write("      }\n");
        writer.write("}\n");
        writer.flush();
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    在实际的应用,一般结合第三方的javapoet工具生成java文件

上面的代码只是介绍了怎么去解析FieldMethodClass上的信息,下面我们就要根据这些信息去生成Java文件了

生成一个Java文件

下面就以一个简单的Demo为例;比如 对于 下面的java文件

public class AnnotationActivity extends AppCompatActivity {
    @BindValue("张三")
    public String name;

    @BindValue("李四")
    public String name2;
    ...
}

需要生成如下java文件:

public final class AnnotationActivity_Binding {
  public final void bind(AnnotationActivity target) {
    target.name = "张三";
    target.name2 = "李四";
  }
}

这里是使用javapoet工具生成java文件,对于javapoet的api文档(文档写的比较全),请自行去github查看,

  1. 先定义生成一个类的实现

    // 缓存 TypeSpec.Builder
    private Map<Name, TypeSpec.Builder> typeSpecCache = new HashMap<>();
    // 定义一个生成class的 TypeSpec.Builder
    private TypeSpec.Builder createOrGetTypeSpecBuilder(TypeElement typeElement) {
        Name name = typeElement.getQualifiedName();
        TypeSpec.Builder typeSpecBuilder = typeSpecCache.get(name);
        if (typeSpecBuilder == null) {
        	// 定义新的class的类名(原类名+"_Binding")
        	// (即: public final class AnnotationActivity_Binding)
            typeSpecBuilder = TypeSpec.classBuilder(typeElement.getSimpleName() + "_Binding")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
            typeSpecCache.put(name, typeSpecBuilder);
        }
        return typeSpecBuilder;
    }
    
  2. 再定义生成一个方法的实现

    // 缓存 MethodSpec.Builder
    private Map<Name, MethodSpec.Builder> bindMethodCache = new HashMap<>();
    // 定义一个生成bind方法的 MethodSpec.Builder
    private MethodSpec.Builder createOrGetBindMethodSpecBuilder(VariableElement element) {
        Name name = ((TypeElement) element.getEnclosingElement()).getQualifiedName();
        MethodSpec.Builder builder = bindMethodCache.get(name);
        if (builder == null) {
        	// 通过 element.getEnclosingElement() 可以获取到 name 属性所在的 class信息(即AnnotationActivity)
            TypeName typeName = TypeName.get(element.getEnclosingElement().asType());
            if (typeName instanceof ParameterizedTypeName) { // 处理范型(如果AnnotationActivity支持范型,则一定要处理)
                typeName = ((ParameterizedTypeName) typeName).rawType;
            }
            // 定义bind方法(即: public final void bind(AnnotationActivity target))
            builder = MethodSpec.methodBuilder("bind")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addParameter(typeName, "target");
            bindMethodCache.put(name, builder);
        }
        return builder;
    }
    
  3. 然后再给bind方法添加参数和方法体的具体实现

    private void createAndImplBindMethod(VariableElement element) {
        // 检查是不是private属性
        boolean privateFlag = false;
        for (Modifier modifier : element.getModifiers()) {
            if (modifier == Modifier.PRIVATE) {
                privateFlag = true;
                break;
            }
        }
    
        BindValue bindValue = element.getAnnotation(BindValue.class);
        MethodSpec.Builder methodSpecBuilder = createOrGetBindMethodSpecBuilder(element);
        if (privateFlag) { // 如果是private,就使用set方法赋值
            String name = element.getSimpleName().toString();
            String fieldSetterName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
            methodSpecBuilder.addStatement("target.$N($S)", fieldSetterName, bindValue.value());
        } else { // 否则直接使用属性赋值
            methodSpecBuilder.addStatement("target.$N = $S", element.getSimpleName(), bindValue.value());
        }
    }
    
  4. 生成java文件

    private void createJavaFile(Set<? extends Element> elements) {
        for (Element element : elements) {
            if (element instanceof VariableElement) {
                createOrGetTypeSpecBuilder((TypeElement) element.getEnclosingElement());
                createAndImplBindMethod((VariableElement) element);
            }
        }
        createFile();
    }
    
    private void createFile() {
        for (Name name : typeSpecCache.keySet()) {
            TypeSpec.Builder typeSpecBuilder = typeSpecCache.get(name);
            MethodSpec.Builder methodBuilder = bindMethodCache.get(name);
            if (methodBuilder != null) {
                typeSpecBuilder.addMethod(methodBuilder.build());
            }
            // 生成的java文件,与被注解的java文件在同一个package下
            String packageName = name.toString().substring(0, name.toString().lastIndexOf("."));
            JavaFile javaFile = JavaFile.builder(packageName, typeSpecBuilder.build()).build();
            try {
                // 使用javapoet工具的api生成java文件
                javaFile.writeTo(processingEnv.getFiler());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    所以最后的process的实现如下:

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
        for (TypeElement annotation : annotations) {
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotation);
           	 // 之前写测试代码已没用,直接注释掉
            // for (Element element : elements) {
            //     if (element instanceof ExecutableElement) {
            //         resolveExecutableElement((ExecutableElement) element);
            //     } else if (element instanceof VariableElement) {
            //         resolveVariableElement((VariableElement) element);
            //     } else if (element instanceof TypeElement) {
            //         resolveTypeElement((TypeElement) element);
            //     } else {
            //          println("====BindValueProcessor other====");
            //     }
            // }
            createJavaFile(elements);
        }
        return true;
    }
    
  5. 使用新生成的Java文件

    public class BindValueManager {
    
        public static void bind(Object target) {
            String className = target.getClass().getName() + "_Binding";
            try {
                Class clazz = Class.forName(className);
                Constructor constructor = clazz.getConstructor();
                // 创建AnnotationActivity_Binding对象
                Object object = constructor.newInstance();
                Method method = clazz.getDeclaredMethod("bind", target.getClass());
                // 调用AnnotationActivity_Binding对象的bind方法
                method.invoke(object, target);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
    @BindValue
    public class AnnotationActivity extends AppCompatActivity {
    
        @BindValue("张三")
        public String name;
    
        @BindValue("李四")
        public String name2;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_annotation);
            // 重要
            BindValueManager.bind(this);
        }
        ...
    }
    

到这里,编译时注解运行的基本原理和使用方法介绍的差不多了, 至于编译时注解的android案例有很多,比如ButterknifeEventBus(也支持编译生成索引文件)、dagger等等,这里我就不一一介绍了,请自行阅读源码

由于JavaxAPI对于Android开发,基本上很少用,所以对其可能会不熟悉,而我上面只是简单的介绍了一下常用的api,所以其它的api请自行查看文档 java API 中文文档