概述

在现在的很多开源结构中,咱们经常能在源码中看到注解处理器的影子,比如咱们熟悉的阿里的ARouter,Android开发中的代替findViewById神器黄油刀ButterKnif,事件总线EventBus等都运用了注解处理器APT技能,下图是ARouter的项目结构图部分截图:

框架开发使用注解处理器APT优雅提效

图中红圈部分便是ARouter注解处理器技能的完结模块。运用注解处理器还有一个好处便是能够解决部分功用因反射而带来的损耗问题,留意,这儿不是说注解处理器的技能是代替反射的哈,咱们运用反射其实便是想在程序运转期间动态获取到某个方针,再操作这个方针的对应办法完结咱们想要完结的功用。可是这个过程是耗时的。而注解处理器是在编译时生成咱们想要完结的功用的对应代码,比较典型的便是EventBus的完结从反射技能转为注解处理器的技能的运用。接下来,本文会介绍什么是注解处理器和怎么运用注解处理器优雅的进步咱们的开发效率。

1.什么是注解处理器APT

注解处理器(Annotation Process Tool)顾名思义便是一种处理注解的东西,它能够极大的优化咱们平时写的冗余代码,比较典型的便是Android开发者经常写的findViewById,这类代码基本都是差不多的,人工去写不仅多,并且还容易犯错,运用注解处理器技能优化后就只需求两行代码:

  @DIView(value = R.id.tv_text)
    TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

如上面的代码中,@DIView(value = R.id.tv_text)便是注解处理器的一部分,这个注解便是提供给调用方运用的,然后在自界说的注解中传入要害的参数值,注解处理器拿到这个要害的参数值后再将一些重复性高的代码一致生成,最终再和打包到运用程序中,所以咱们在运用APT技能的时分的自界说注解都是设置在编译期间有用。如ARounter的自界说注解Route的有用规模便是编译期间有用:@Retention(RetentionPolicy.CLASS)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
    /**
     * Path of route
     */
    String path();
    /**
     * Used to merger routes, the group name MUST BE USE THE COMMON WORDS !!!
     */
    String group() default "";
    /**
     * Name of route, used to generate javadoc.
     */
    String name() default "";
    /**
     * Extra data, can be set by user.
     * Ps. U should use the integer num sign the switch, by bits. 10001010101010
     */
    int extras() default Integer.MIN_VALUE;
    /**
     * The priority of route.
     */
    int priority() default -1;
}

其实很好理解,因为咱们运用APT技能主要是为咱们在编译期间生成一些咱们不想写的重复性代码,当代码生成完后,这些注解也就完结了它们的作业。除非这些注解在代码运转的时分还需求反射运用,否则咱们都没有必要将这些注解规模界说成运转时有用。

至此,信任读者已经理解注解处理器是做啥的了,用一句话概括便是,注解处理器是处理咱们自界说注解的东西,它帮助咱们处理生成那些重复性比较高的代码,让咱们不用去关怀和处理那些重复的逻辑。

2.运用场景

那么注解处理器的运用场景主要有哪些呢,其实注解处理器是为了解放开发者的,它是一个面向开发者的东西,它的主要运用场景便是做结构的开发,例如阿里的ARouter路由结构,网络请求结构Retrofit,事件总线EventBus等,所以APT注解处理器技能是一个简化咱们开发的东西,咱们开发结构的时分,能够提取出结构中的很多逻辑差不多,但又大量重复的内容,运用注解处理器去优化。对注解处理器感兴趣的读者,主张去阅览下上面说到的几个结构源码,写的特别好。

3.怎么运用

接下来,到了最要害的时刻了,说一千道一万,不会运用就完蛋,接下来利用我在视频网站上学到的一个相似黄油刀的比如,来介绍APT技能的运用办法。咱们只完结一个功用,便是运用咱们界说的注解去注解Android的View,然后直接运用这个View,如下所示:

@DIActivity
public class MainActivity extends AppCompatActivity {
    @DIView(value = R.id.tv_text)
    TextView textView;
    @DIView(value = R.id.tv_text_1)
    TextView textView1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // bindView办法其实便是代替findViewById的代替
        DIMainActivity.bindView(this);
        textView.setText("hello apt!!!!");
        textView1.setText("hello apt again!!!!");
        textView1.setTextSize(15);
    }
}

咱们运用一个DIActivity注解咱们的类,然后运用@DIView(value = R.id.tv_text) 注解咱们声明的View,然后直接在程序中运用被咱们注解的特点。然后结构就能帮咱们完结这个对应的findViewByID 的逻辑。接下来是完结这个功用的步骤:

3.1 创立注解API模块

这儿咱们需求新建一个Java Library模块,记住一定是Java Library,这个模块主要是用于做自界说注解的界说,给注解处理器和调用者运用。

框架开发使用注解处理器APT优雅提效

然后界说好咱们需求运用到的自界说注解

DIActivity 注解用于咱们要运用的Activity上,咱们便是通过它来将生成的代码创立出的方针赋值给咱们运用的Activity的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface DIActivity {
}

DIView 用于咱们要运用的View上,调用者只需运用这个注解传入View的ID值就能够得到一个View方针了

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface DIView {
    int value() default 0;
}

3.2 创立注解处理器模块

创立完自界说注解类,咱们接下来便是对注解处理器模块的介绍了,结构的主要作业都是在注解处理器内完结,咱们相同新建一个Java Library模块,用于处理咱们界说的注解。如下图所示

框架开发使用注解处理器APT优雅提效

咱们需求留意的是在这个模块中,咱们需求去引进对应的库依靠,来协助咱们完结一些复杂繁琐的作业,咱们需求引进下面几个库:

    implementation project(path: ':annotation') // 咱们界说的注解模块,因为要处理注解,
      // 所以有必要依靠上
    // auto-service 是Google提供的,辅佐咱们开发注解处理器
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
    compileOnly 'com.google.auto.service:auto-service:1.0-rc7'
    // 这个是一个代码生成结构,利用它能够优雅的生成咱们想要的代码
    implementation 'com.squareup:javapoet:1.10.0'

留意:引进com.google.auto.service:auto-service:1.0-rc7这个依靠时运用的是annotationProcessor ,需求留意,否则不会报错,可是也无法生成咱们想要的代码 装备好了就能够进行开发了,处理前面注解的代码如下:

@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class DIProcessor extends AbstractProcessor {
private Elements elementUtils;
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(DIActivity.class.getCanonicalName());
    }
    @Override
    public boolean process(Set<? extends TypeElement> set,
     RoundEnvironment roundEnvironment) 
    {
        Set<? extends Element> elements = 
        roundEnvironment.getElementsAnnotatedWith(DIActivity.class);
        for (Element element:elements){
            // 判别是否为class
            TypeElement typeElement = (TypeElement) element;
            List<? extends Element> allMembers = 
            elementUtils.getAllMembers(typeElement);
            MethodSpec.Builder bindViewMethodSpecBuilder = 
            MethodSpec.methodBuilder("bindView")
                    .addModifiers(Modifier.PUBLIC,Modifier.STATIC)
                    .returns(TypeName.VOID)
                    .addParameter(ClassName.get(typeElement.asType()),"activity");
            for(Element item :allMembers){
                DIView diView = item.getAnnotation(DIView.class);
                if (diView == null) {
                    continue;
                }
                bindViewMethodSpecBuilder.addStatement(String.format("activity.%s = (%s)" 
                +"activity.findViewById(%s)",item.getSimpleName(),
                ClassName.get(item.asType()).toString(),diView.value()+""));
            }
            TypeSpec typeSpec = TypeSpec.classBuilder("DI" + element.getSimpleName())
                    .superclass(TypeName.get(typeElement.asType()))
                    .addModifiers(Modifier.PUBLIC,Modifier.FINAL)
                    .addMethod(bindViewMethodSpecBuilder.build())
                    .build();
            JavaFile javaFile = 
            JavaFile.builder(getPackageName(typeElement),typeSpec).build();
            try {
                javaFile.writeTo(processingEnv.getFiler());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return false;
    }
    private String getPackageName(TypeElement typeElement) {
        return elementUtils.getPackageOf(typeElement).getQualifiedName().toString();
    }
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        elementUtils = processingEnv.getElementUtils();
        super.init(processingEnv);
    }
}

上面的代码很简单,大致意思便是,运用东西类,获取注解的信息,然后生成咱们想要的代码,最终写到文件里面。当咱们要运用对应功用的时分,直接在程序中调用就能够了。在上面代码中出现的如下代码块:

TypeSpec typeSpec = TypeSpec.classBuilder("DI" + element.getSimpleName())
                    .superclass(TypeName.get(typeElement.asType()))
                    .addModifiers(Modifier.PUBLIC,Modifier.FINAL)
                    .addMethod(bindViewMethodSpecBuilder.build())
                    .build();

便是com.squareup:javapoet:1.10.0结构干的事情,以面向方针的方式生成代码,如果没有这个结构,那么咱们需求运用字符串来拼接出咱们想要生成的类。比如EventBus的注解处理器便是用的字符拼接:

框架开发使用注解处理器APT优雅提效

3.3 运用注解

接下来是运用注解,运用之前咱们需求引进相关的依靠,首要,咱们需求运用咱们自界说的注解,所以需求引进注解模块的依靠,其次我咱们需求依靠注解处理器模块,如下所示:

  implementation project(':annotation')
  annotationProcessor project(':annotation-processor')

留意:引用注解处理器模块时要用:annotationProcessor project(':annotation-processor'),是annotationProcessor 不是Implementation,如果运用错误会导致无法生成咱们想要的方针代码

接下来便是运用注解就能够了。

@DIActivity
public class MainActivity extends AppCompatActivity {
    @DIView(value = R.id.tv_text)
    TextView textView;
    @DIView(value = R.id.tv_text_1)
    TextView textView1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // bindView办法其实便是代替findViewById的代替
        DIMainActivity.bindView(this);
        textView.setText("hello apt!!!!");
        textView1.setText("hello apt again!!!!");
        textView1.setTextSize(15);
    }
}

当咱们编译代码的时分,会在下图途径中生成咱们在注解处理器中拼接出的类,看到类的内容其实咱们就理解了结构帮咱们干的事情了。如果没有生成咱们想要生成的类,主张读者去查看下自己的代码中是否又错误,引进依靠是否引错。

框架开发使用注解处理器APT优雅提效
至此,注解处理器APT的内容就讲完了,读者还是很有必要了解这个内容的,因为现在很多结构中都运用这个技能,项目的源码比较简单,这儿就不提供了,需求的能够谈论区留言,留下邮箱,主张读者手动去完结一遍。