详解Android注解 Annotation

来源:互联网 发布:python写服务器端 编辑:程序博客网 时间:2024/06/09 18:30

概念及作用

概念

  • 注解即元数据,就是源代码的元数据
  • 注解为在代码中添加信息提供了一种形式化的方法,可以在后续中更方便的 使用这些数据
  • Annotation是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的一种工具。

作用

  • 生成文档
  • 跟踪代码依赖性,实现替代配置文件功能,减少配置。如一些注解在编译时进行格式检查,如@Override等
  • 每当你创建描述符性质的类或者接口时,一旦其中包含重复性的工作,就可以考虑使用注解来简化与自动化该过程

java中内置的注解

接着看看Java提供的内置注解,主要有5个,如下:

  • @Override:表示当前的方法将覆盖父类中的方法。要保证父类中有一个同名的方法,否则编译报错。
  • @Deprecated:表示某个程序元素(类,方法等)已经过时,当其他程序使用已过时的类、方法时,编译器会出现警告信息。
  • @SuppressWarnings:指示被该Annotation修饰的程序元素(以及该程序元素的所有子元素)忽略指定的编译警告。当使用SuppressWarnings来关闭编译警告时,一定要在括号里使用name=value的形式为该Annotation成员变量设置值。
  • @SafeVaragrs:当把一个不带泛型的对象赋值给一个带泛型的变量时,会发生“堆污染”警告。SafeVaragrs是Java7专门为抑制“堆污染”警告提供的。
  • @FunctionalInterface:Java8规定,如果接口中只有一个抽象方法,该接口就是函数式接口,FunctionalInterface就是用来指定某个接口必须是函数式接口,它只能修饰接口。

元注解

自定义注解的时候用到的,也就是自定义注解的注解;元注解的作用就是负责注解其他注解。Java8定义了5个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。

@Target

用于描述注解的使用范围,取值(ElementType)有:

  • ElementType.CONSTRUCTOR:用于描述构造器
  • ElementType.METHOD:用于描述方法
  • ElementType.FIELD:用于描述成员变量
  • ElementType.LOCAL_VARIABLE:用于描述局部变量
  • ElementType.PACKAGE:用于描述包
  • ElementType.PARAMETER:用于描述参数
  • ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明
  • ElementType.ANNOTATION_TYPE:用于描述Annotation
/** 两种方式均可,如果只需要为value成员变量指定值,可以直接在后面括号里指定value成员变量的值,无需name=value的形式*/@Target(value = ElementType.FIELD)@Target(ElementType.FIELD)

@Retention

表示需要在什么级别保存该注释信息,用于描述注解的生命周期,取值(RetentionPoicy)有:

  • RetentionPoicy.SOURCE:在源文件中有效(即源文件保留),仅出现在源代码中,会被编译器丢弃
  • RetentionPoicy.CLASS:在class文件中有效(即class保留),被编译在class文件中
  • RetentionPoicy.RUNTIME:在运行时有效(即运行时保留),编译在class文件中

@Documented

将注解包含在javadoc文档中

@Inherited

允许子类继承父类中的注解

自定义注解

格式

public @interface 注解名{  定义体}

规则

  • 修饰符只能是public 或默认(default)
  • 参数成员只能用基本类型byte,short,int,long,float,double,boolean八种基本类型和String,Enum,Class,annotations及这些类型的数组
  • 如果只有一个参数成员,最好将名称设为”value”
  • 注解元素必须有确定的值,可以在注解中定义默认值(使用default),也可以使用注解时指定,非基本类型的值不可为null,常使用空字符串或0作默认值
  • 在表现一个元素存在或缺失的状态时,定义以下特殊值来表示,如空字符串或负值
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface TestAnnotation {    public int id() default -1;    public String name() default "";}

注解处理器类库

java.lang.reflect.AnnotatedElement

Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:

  • Class:类定义
  • Constructor:构造器定义
  • Field:类的成员变量定义
  • Method:类的方法定义
  • Package:类的包定义

java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。

AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的四个方法来访问Annotation信息:

  • T getAnnotation(Class<\T> annotationClass):返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
  • T getDeclaredAnnotation(Class<\T> annotationClass):返回直接修饰该程序元素、指定类型的注解,该方法将忽略继承的注解,如果该类型注解不存在,则返回null。
  • Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
  • Annotation[] getDeclaredAnnotations():返回直接修饰该程序元素的所有注解,该方法将忽略继承的注解。
  • boolean is AnnotationPresent(Class<\? extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
  • T[] getAnnotationsByType(Class<\T> annotationClass):与getAnnotations基本相似,由于Java8新增了重复注解,需要使用该方法返回该程序元素上存在的、指定类型的多个注解。
  • T[] getDeclaredAnnotationsByType(Class<\T> annotationClass):与getDeclaredAnnotations基本相似,由于Java8新增了重复注解,需要使用该方法返回直接修饰该程序元素、指定类型的多个注解,该方法将忽略继承的注解。

实例演示

下面程序通过使用Annotation来简化事件点击编程,传统点击事件

/***********注解声明***************/@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface MyInject {    int value();}@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MyClick {    int[] value();}
/***********注解使用***************/public class RunActivity extends AppCompatActivity {    @MyInject(R.id.button1)    public Button button1;    @MyInject(R.id.button2)    public Button button2;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_run);        MyViewUtils.inject(this);//注解处理器    }    @MyClick({R.id.button1, R.id.button2})    public void submit(View view){        Toast.makeText(this, ((Button)view).getText(), Toast.LENGTH_SHORT).show();    }}
/***********注解处理器***************/public class MyViewUtils {    public static void inject(final Activity activity){        //反射属性        Field[] declaredFields = activity.getClass().getDeclaredFields();        for (int i = 0; i < declaredFields.length; i++) {            Field field = declaredFields[i];            field.setAccessible(true);            MyInject annotation = field.getAnnotation(MyInject.class);            if (annotation!=null) {                int id = annotation.value();                View view = activity.findViewById(id);                try {                    field.set(activity, view);                } catch (Exception e) {                    e.printStackTrace();                }            }        }        //反射方法        Method[] declaredMethods = activity.getClass().getDeclaredMethods();        for (int i = 0; i < declaredMethods.length; i++) {            final Method method = declaredMethods[i];            method.setAccessible(true);            MyClick annotation = method.getAnnotation(MyClick.class);            if (annotation!=null) {                int[] value = annotation.value();                for (int j : value) {                    int id = j;                    final View btn = activity.findViewById(id);                    btn.setOnClickListener(new View.OnClickListener() {                        @Override                        public void onClick(View v) {                            try {                                method.invoke(activity, btn);                            } catch (Exception e) {                                e.printStackTrace();                            }                        }                    });                }            }        }    }}

这里写图片描述

Java 8 中注解新特性

重复注解@Repeatable

Java 8以前,同一个程序元素前最多只能使用一个相同类型的Annotation,如果需要使用多个相同类型Annotation,则必须使用Annotation容器。

@Results({@Result(name="success", location="succ.jsp"),@Result(name="fail", location="fail.jsp")})public Action fooAction(){...}

注:实质是@Results只包含一个名为value,值为Result[]的成员变量。

java8新增@Repeatable元注解,表示被修饰的注解可以用在同一个程序元素加上多个相同的注解(包含不同的属性值),其实Repeatable只是语法糖而已。

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@Repeatable(value = Result.class) //可以多次添加public @interface Result {      String name();     String location();  } 
@Result(name="success", location="succ.jsp")@Result(name="fail", location="fail.jsp")public Action fooAction(){...}

类型注解 Type Annotation

Java8 为ElementType枚举新增了 ElementType.TYPE_PARAMETER以及ElementType.USE,@Target(ElementType.TYPE_USE)被称为类型注解,可用于任何用到类型的地方。

/**初始化对象时*/String myString = new @NotNull String();//对象类型转化时myString = (@NonNull String) str;//使用 implements 表达式时class MyList<T> implements @ReadOnly List<@ReadOnly T>{...}//使用 throws 表达式时public void validateValues() throws @Critical ValidationFailedException{...}

编译时注解

什么是编译时注解

上面讲到自定义注解,我们的实现方式是运行时注解,都是在运行时通过反射的方式来实现的,这样必然会带来一个问题,那就是性能的损耗。那么有没有更好的实现方式呢?既可以实现注入,还能保证性能无损耗?那就是编译时注解

APT(Annotation Processing Tool)是一种注解处理工具,它可以根据源文件中的Annotation动态生成额外的源文件和其他文件,文件具体内容由Annotation 处理器编写者决定,APT还会编译动态生成的源文件和以前的源文件,将它们一起生成class文件。在运行时期,这类注解是没有的,会依靠动态生成的类做一些操作,因为没有反射,效率和直接调用方法没什么区别。
还记得我们之前说Android 6.0 运行时权限处理时使用的PermissionsDispatcher库吗,使用的正是编译时注解。其他编译时注解库还有butterknife、ParcelableGenerator等。

实现编译时注解

这里我们还是实现一个View注入的框架。

(1)准备

编写此类框架,一般需要建立多个module,例如本文即将实现的例子:

  • annotation 用于存放注解等,Java模块
  • compiler 用于编写注解处理器,Java模块
  • api 用于给用户提供使用的API,本例为Andriod模块
  • app 主模块,本例为Andriod模块

对于module间的依赖,因为编写注解处理器需要依赖相关注解,所以:

compiler依赖annotation

我们在使用的过程中,会用到注解以及相关API,所以

app依赖api和compiler(依赖compiler不是个好做法,后面介绍更适当的方法)api依赖annotation

(2)注解模块

注解模块,主要用于存放一些注解类

//编译时注解这里到CLASS即可,运行时注解需要RUNTIME@Retention(RetentionPolicy.CLASS)@Target(ElementType.FIELD)public @interface Bind{    int value(); //这里我们需要在使用时传入一个id}

(3)注解处理器

1)AbstractProcessor几个方法

这里定义一个注解处理器 MyProcessor,每一个处理器都是继承于AbstractProcessor,并要求必须复写 process() 方法,通常我们使用会去复写以下4个方法:

/**  * 每一个注解处理器类都必须有一个空的构造函数,默认不写就行;  */  public class MyProcessor extends AbstractProcessor {      /**      * init()方法会被注解处理工具调用,并输入ProcessingEnviroment参数,可以帮助我们去初始化一些辅助类(Elements、Filer、Messager、Types、Filer等):     Filer mFileUtils; 跟文件相关的辅助类,生成JavaSourceCode.     Elements mElementUtils;跟元素相关的辅助类,帮助我们去获取一些元素相关的信息。     Messager mMessager;跟日志相关的辅助类。 Element几个子类:  - VariableElement //一般代表成员变量  - ExecutableElement //一般代表类中的方法  - TypeElement //一般代表代表类  - PackageElement //一般代表Package  */      @Override      public synchronized void init(ProcessingEnvironment processingEnv) {          super.init(processingEnv);      }      /**      * 这相当于每个处理器的主函数main(),你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。      * 输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素      * @param annotations   请求处理的注解类型      * @param roundEnv  有关当前和以前的信息环境      * @return  如果返回 true,则这些注解已声明并且不要求后续 Processor 处理它们(一个元素多个注解,后面的注解将不可用);      *          如果返回 false,则这些注解未声明并且可能要求后续 Processor 处理它们      */    @Override      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {          return false;    }      /**      * 这里必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称      * @return  注解器所支持的注解类型集合,如果没有这样的类型,则返回一个空集合      */      @Override      public Set<String> getSupportedAnnotationTypes() {          Set<String> annotataions = new LinkedHashSet<String>();          annotataions.add(Bind.class.getCanonicalName());          return annotataions;      }      /**      * 指定使用的Java版本,通常这里返回SourceVersion.latestSupported(),默认返回SourceVersion.RELEASE_7      * @return  使用的Java版本      */      @Override      public SourceVersion getSupportedSourceVersion() {          return SourceVersion.latestSupported();      }  }  

上面注释说的挺清楚了,我们需要处理的工作在 process() 方法中进行,等下给出例子。对于 getSupportedAnnotationTypes() 方法标明了这个注解处理器要处理哪些注解,返回的是一个Set 值,说明一个注解处理器可以处理多个注解。除了在这个方法中指定要处理的注解外,还可以通过注解的方式来指定(SourceVersion也一样):

@SupportedSourceVersion(SourceVersion.RELEASE_7)  @SupportedAnnotationTypes("com.example.Bind")  public class MyProcessor extends AbstractProcessor {      // ...  } 

因为兼容的原因,特别是针对Android平台,建议使用重载 getSupportedAnnotationTypes() 和 getSupportedSourceVersion()方法代替@SupportedAnnotationTypes 和@SupportedSourceVersion

2)process的实现

process中的实现,相比较会比较复杂一点,一般你可以认为两个大步骤:

  • 收集信息
  • 生成代理类(本文把编译时生成的类叫代理类)

什么叫收集信息呢?就是根据你的注解声明,拿到对应的Element,然后获取到我们所需要的信息,这个信息肯定是为了后面生成JavaFileObject所准备的。

例如本例,我们会针对每一个类生成一个代理类,例如CompileActivity我们会生成一个CompileActivity$$ViewInjector。那么如果多个类中声明了注解,就生成多个代理类,这里需要:

  • 一个类对象,代表具体某个类的代理类生成的全部信息,本例中为ProxyInfo
  • 一个集合,存放上述类对象(到时候遍历生成代理类),本例中为Map
@Override    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {        messager.printMessage(Diagnostic.Kind.NOTE , "process...");        //process可能会多次调用,避免生成重复的代理类,避免生成类的类名已存在异常        mProxyMap.clear();        /**收集信息*/        Set<? extends Element> elesWithBind = roundEnv.getElementsAnnotatedWith(Bind.class);        for (Element element : elesWithBind) {            /**检查element类型*/            checkAnnotationValid(element, Bind.class);            VariableElement variableElement = (VariableElement) element;            //class type            TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();            //类的全路径            String fqClassName = classElement.getQualifiedName().toString();            ProxyInfo proxyInfo = mProxyMap.get(fqClassName);            if (proxyInfo == null) {                proxyInfo = new ProxyInfo(elementUtils, classElement);                mProxyMap.put(fqClassName, proxyInfo);            }            Bind bindAnnotation = variableElement.getAnnotation(Bind.class);            int id = bindAnnotation.value();            proxyInfo.injectVariables.put(id , variableElement);        }        /**生成代理类*/        for (String key : mProxyMap.keySet()) {            ProxyInfo proxyInfo = mProxyMap.get(key);            try {                JavaFileObject jfo = processingEnv.getFiler().createSourceFile(                        proxyInfo.getProxyClassFullName(),                        proxyInfo.getTypeElement());                Writer writer = jfo.openWriter();                //生成Java代码                writer.write(proxyInfo.generateJavaCode());                writer.flush();                writer.close();            } catch (IOException e) {                error(proxyInfo.getTypeElement(),  "Unable to write injector for type %s: %s",                        proxyInfo.getTypeElement(), e.getMessage());            }        }        return true;    }

生成Java代码的方法都在ProxyInfo里面

   public String generateJavaCode() {        StringBuilder builder = new StringBuilder();        builder.append("// Generated code. Do not modify!\n");        builder.append("package ").append(packageName).append(";\n\n");        builder.append("import com.hx.*;\n");        builder.append("import com.hx.api.ViewInject;\n");        builder.append('\n');        builder.append("public class ").append(proxyClassName).append(" implements " + ProxyInfo.PROXY + "<" + typeElement.getQualifiedName() + ">");        builder.append(" {\n");        generateMethods(builder);        builder.append('\n');        builder.append("}\n");        return builder.toString();    }    private void generateMethods(StringBuilder builder) {        builder.append("@Override\n ");        builder.append("public void inject(" + typeElement.getQualifiedName() + " host, Object source ) {\n");        for (int id : injectVariables.keySet()) {            VariableElement element = injectVariables.get(id);            String name = element.getSimpleName().toString();            String type = element.asType().toString();            builder.append(" if(source instanceof android.app.Activity){\n");            builder.append("host." + name).append(" = ");            builder.append("(" + type + ")(((android.app.Activity)source).findViewById( " + id + "));\n");            builder.append("\n}else{\n");            builder.append("host." + name).append(" = ");            builder.append("(" + type + ")(((android.view.View)source).findViewById( " + id + "));\n");            builder.append("\n};");        }        builder.append("  }\n");    }

这里主要就是靠收集到的信息,拼接完成的代理类对象了,看起来会比较头疼,不过我给出一个生成后的代码,对比着看会很多。

// Generated code. Do not modify!package com.hx.annotation;import com.hx.*;import com.hx.api.ViewInject;public class CompileActivity$$ViewInject implements ViewInject<com.hx.annotation.CompileActivity> {@Overridepublic void inject(com.hx.annotation.CompileActivity host, Object source ) { if(source instanceof android.app.Activity){    host.image = (android.widget.ImageView)(((android.app.Activity)source).findViewById(2131427369)); }else{    host.image = (android.widget.ImageView)(((android.view.View)source).findViewById(2131427369)); };  if(source instanceof android.app.Activity){   host.text = (android.widget.TextView)(((android.app.Activity)source).findViewById(2131427415)); }else{   host.text = (android.widget.TextView) (((android.view.View)source).findViewById(2131427415)); };  }}

这里注意下,生成的代码实现了一个接口ViewInjector,该接口是为了统一所有的代理类对象的类型,到时候我们需要强转代理类对象为该接口类型,调用其方法;接口是泛型,主要就是传入实际类对象,例如CompileActivity,因为我们在生成代理类中的代码,实际上就是实际类.成员变量的方式进行访问,所以,使用编译时注解的成员变量一般都不允许private修饰符修饰(有的允许,但是需要提供getter,setter访问方法)。

上面是完全采用拼接的方式编写Java代码,是不是看的头晕了,你也可以使用一些开源库,来通过Java api的方式来生成代码,例如:
javapoet
A java API for generating .java source files.有兴趣自己研究去。

3)运行注解处理器

上面第二步注解处理器写好了,这时如果你直接编译或者运行工程的话,是看不到任何输出的,这里还要做的一步操作是指定注解处理器的所在(必须要用一个服务文件来注册它),需要做如下操作:

1、在 compiler 库的 main 目录下新建 resources 资源文件夹;2、在 resources文件夹下建立 META-INF/services 目录文件夹;3、在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;4、在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径;

javax.annotation.processing.Processor文件:

com.example.ViewInjectProcessor

来看下整个目录结构:
这里写图片描述

(4)API模块

有了代理类之后,我们一般还会提供API供用户去访问,例如本例的访问入口是:

//Activity中ViewInjector.injectView(Activity);//Fragment中,获取ViewHolder中ViewInjector.injectView(this, view);

模仿了butterknife,第一个参数为宿主对象,第二个参数为实际调用findViewById的对象;当然在Activity中,两个参数就一样了。

API一般如何编写呢?
其实很简单,只要你了解了其原理,这个API就干两件事:

  • 根据传入的host寻找我们生成的代理类:例如CompileActivity->CompileActivity$$ViewInjector。
  • 强转为统一的接口,调用接口提供的方法。

这两件事应该不复杂,第一件事是拼接代理类名,然后反射生成对象,第二件事强转调用。

public class ViewInjector {    private static final String SUFFIX = "$$ViewInject";    public static void injectView(Activity activity)    {        ViewInject proxyActivity = findProxyActivity(activity);        proxyActivity.inject(activity, activity);    }    public static void injectView(Object object, View view)    {        ViewInject proxyActivity = findProxyActivity(object);        proxyActivity.inject(object, view);    }    private static ViewInject findProxyActivity(Object activity)    {        try        {            Class clazz = activity.getClass();            Class injectorClazz = Class.forName(clazz.getName() + SUFFIX);            return (ViewInject) injectorClazz.newInstance();        } catch (ClassNotFoundException e)        {            e.printStackTrace();        } catch (InstantiationException e)        {            e.printStackTrace();        } catch (IllegalAccessException e)        {            e.printStackTrace();        }        throw new RuntimeException(String.format("can not find %s , something when compiler.", activity.getClass().getSimpleName() + SUFFIX));    }}

代码很简单,拼接代理类的全路径,然后通过newInstance生成实例,然后强转为ViewInject,调用代理类的inject方法。

这里一般情况会对生成的代理类做一下缓存处理,比如使用Map存储下,不用再生成,这里我们就不去做了。

以上我们就完成了一个编译时注解框架的编写。

(5)编译
到这里我们重新编译下工程就应该有输出了,如果没看到输出则先清理下工程再编译,如下两个操作:
这里写图片描述

生成的代理文件在哪里呢?其实在app模块的build目录下:\app\build\generated\source\apt\debug\com\hx\annotation
这里写图片描述

在注解处理器的process方法中我们打了一句log信息

messager.printMessage(Diagnostic.Kind.NOTE , "process...");

那么log信息从哪里看呢?如果你从Android Monitor中看,保证你看不出任何输出,其实需要在Gradle Console中查看信息
这里写图片描述
这里的输出log可以用来在编译过程中做简单调试用。 更多的调试注解大家可以查看这篇文章如何debug自定义AbstractProcessor, 我这里就不过多赘述了。

(6)高效处理方式

上面的处理方式有如下几个问题:

  • 要在指定的目录下建立文件,并且还要把注解处理类,写入其中,这个导致很容易出错
  • 注解处理类不应该被打包到APK中来增加apk的大小,它只有在编译时被用到

(1)先看第一个问题:
AutoService注解处理器是Google开发的,用来生成 META-INF/services/javax.annotation.processing.Processor 文件的,你只需要在你定义的注解处理器上添加 @AutoService(Processor.class) 就可以了,简直不能再方便了。
首先compiler模块添加依赖:

compile 'com.google.auto.service:auto-service:1.0-rc2'

添加好以后就可以直接用了,在我们之前定义的注解处理器上使用:

@AutoService(Processor.class)  public class ViewInjectProcessor extends AbstractProcessor {      // ...  }

一句话完全搞定!如果要使用 autoservice的话,需要把配置文件给删掉,不然默认的配置文件会吧autoservice给覆盖掉,生成不出来文件,这两种方式选择一种就可以了。

(2)再看第二个问题:
这就需要使用到Android-apt了,那么什么是android-apt呢?
大体来讲它有两个作用:

  • 能在编译时期去依赖注解处理器并进行工作,但在生成 APK 时不会包含任何遗留的东西
  • 能够辅助 Android Studio 在项目的对应目录中存放注解处理器在编译期间生成的文件

这个就可以很好地解决上面我们遇到的问题了,来看下怎么用。
首先在整个工程的 build.gradle 中添加如下两段语句:

buildscript {      repositories {          jcenter()          mavenCentral()  // add      }      dependencies {          classpath 'com.android.tools.build:gradle:2.1.2'          classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'  // add      }  }  

在主项目(app)的 build.gradle 中也添加两段语句:

apply plugin: 'com.android.application'  apply plugin: 'com.neenbedankt.android-apt' // add  ...  dependencies {    compile fileTree(include: ['*.jar'], dir: 'libs')    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {        exclude group: 'com.android.support', module: 'support-annotations'    })    compile 'com.android.support:appcompat-v7:25.3.1'    testCompile 'junit:junit:4.12'    compile project(':Api')//    compile project(':complier')  替换为    apt project(':complier')}

同步一下,这样就OK了,重新运行可以很好地工作了。

看一下生成的apk
这里写图片描述
使用apt的方式由于注解处理类没有被打包到APK中,APK由2288KB缩小到1450KB,是不是很酷。

Demo下载地址

原创粉丝点击