Android开发中Java 注解详解

Java 注解 (Annotation)

  1. 初步理解

注解即Annotation:英文翻译成中文的意思是注释、解释、说明的意思。在java中专业名词为注解。

注解可以用来修饰类、方法、属性等,从英文的解释不难看出,注解的作用就是对类、方法、属性的进一步解释说明。

 

既然是解释那到底是解释给谁的,开发者?编译器?还是运行时代码?

 

分别举个我们比较熟悉的例子:

  1. 解释给开发者:

@Override

这个注解应该是大家最熟悉的,子类复写父类方法时,往往会在复写的方法上添加此注解,用于提示开发者,该方法复写自父类。

  1. 解释给编译器:

熟悉Android的人,有一个开源项目是你一定熟悉的,那就是butterknife,其中最常用的注解就是

@BindView

该注解通过java编译时的注解处理器,自动为注解生成findViewById()代码,从而简化开发者大量书写重复代码的繁琐工作。

  1. 解释给运行时代码:

移动端通常处理大量的网络数据,而这些数据往往是使用json格式传输的,所以解析json就离不开大名鼎鼎的Google开源工程gson

Gson中经常会用到的注解就是

@Expose

该字段就是用于标识是否需要将类中的字段打包到json格式中,这个工作就是在运行时gson框架完成的。

 

 

通过上面的三种应用场景的解析,相信你已经基本明白java注解的用途和作用。

  1. 注解的语法

因为平常开发少见,相信有不少的人员会认为注解的地位不高。其实同 classs 和 interface 一样,注解也属于一种类型。它是在 Java SE 5.0 版本中开始引入的概念。现在许多java框架的开发都是基于注解完成的,如butterknife,Dagger2,Eventbus,gson等。

2.1注解定义

注解通过 @interface 关键字进行定义。

public @interface TestAnnotation {

}

 

这样定义好的注解就可以添加到类上面了。

@TestAnnotation

public class Test {

}

 

 

元注解

元注解是可以注解到注解上的注解,java内部定义好了5中元注解标签:

@Retention、@Documented、@Target、@Inherited、@Repeatable 。

我们可以把他们认为是用于定义注解的关键字。

 

@Retention

Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。

它的取值如下: 
- RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。 
- RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。 
- RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

@Retention 相当于注解盖了一张时间戳,时间戳指明了注解存活的时间周期。

@Retention(RetentionPolicy.RUNTIME)

public @interface TestAnnotation {

}

 

上面的代码中,我们指定 TestAnnotation 可以在程序运行周期被获取到,因此它的生命周期非常的长。他可以在程序运行时通过java的反射机制获取到,而SOURCE和CLASS周期的注解就无法通过反射获取。

 

@Documented

顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。

 

@Target

Target 是目标的意思,@Target 指定了注解运用的地方。

当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。

比如只能应用到方法上、类上、方法参数上等等。@Target 有下面的取值

 

ElementType.ANNOTATION_TYPE 可以给一个注解进行注解

ElementType.CONSTRUCTOR 可以给构造方法进行注解

ElementType.FIELD 可以给属性进行注解

ElementType.LOCAL_VARIABLE 可以给局部变量进行注解

ElementType.METHOD 可以给方法进行注解

ElementType.PACKAGE 可以给一个包进行注解

ElementType.PARAMETER 可以给一个方法内的参数进行注解

ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举

 

@Inherited

Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。

说的比较抽象。代码来解释。

@Inherited

@Retention(RetentionPolicy.RUNTIME)

@interface Test {}

 

@Test

public class A {}

 

public class B extends A {}

 

注解Test被 @Inherited 修饰,之后类A被 Test注解,类B继承A,类B也拥有Test这个注解。

 

@Repeatable

Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。

 

什么样的注解会多次应用呢?通常是注解的值可以同时取多个。

 

举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。

@interface Persons {

    Person[]  value();

}

 

@Repeatable(Persons.class)

@interface Person{

    String role default "";

}

 

@Person(role="artist")

@Person(role="coder")

@Person(role="PM")

public class SuperMan{}

 

 

注意上面的代码,@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解。

 

什么是容器注解呢?就是用来存放其它注解的地方。它本身也是一个注解。

 

我们再看看代码中的相关容器注解。

@interface Persons {

    Person[]  value();

}

 

按照规定,它里面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注解过的注解数组,注意它是数组。

 

2.2注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

 

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

public @interface TestAnnotation {

    int id();

    String msg();

}

上面代码定义了 TestAnnotation 这个注解中拥有 id 和 msg 两个属性。在使用的时候,我们应该给它们进行赋值。

赋值的方式是在注解的括号内以 value=”” 形式,多个属性之前用 ,隔开。

@TestAnnotation(id=3,msg="hello annotation")

public class Test {}

 

需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。

注解中属性可以有默认值,默认值需要用 default 关键值指定。比如:

 

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

public @interface TestAnnotation {

    public int id() default -1;

    public String msg() default "Hi";

}

 

TestAnnotation 中 id 属性默认值为 -1,msg 属性默认值为 Hi。

它可以这样应用。

@TestAnnotation()

public class Test {}

因为有默认值,所以也可以不在 @TestAnnotation 后面的括号里面进行赋值了。

另外,还有一种情况。如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。

public @interface Check {

    String value();

}

上面代码中,Check 这个注解只有 value 这个属性。所以可以这样应用。

@Check("hi")

int a;

这和下面的效果是一样的

@Check(value="hi")

int a;

最后,还需要注意的一种情况是一个注解没有任何属性。比如

public @interface Perform {}

那么在应用这个注解的时候,括号都可以省略。

@Perform

public void testMethod(){}

 

需要强调一下,注解可以用来修饰注解:

@Retention(RUNTIME)

@Target(ANNOTATION_TYPE)//定义了修饰注解的注解

public @interface ListenerClass {}

 

@Retention(CLASS)

@Target(METHOD)

@ListenerClass

public @interface OnLongClick {

  @IdRes int[] value() default { View.NO_ID };

}

 

3.注解的用途

注解就相当于给被他修饰的类、方法或成员变量等添加了一个标签,根据这个标签的存在的周期(Retention)可以有不同的用途。

- RetentionPolicy.SOURCE源码阶段

这个级别的注解不会被加载到编译阶段,故只针对一些编辑器特性。

如@Deprecated:

public class Hero {

    @Deprecated

    public void say(){

    }

}

这里标识say方法已经过时,如果强制使用,编辑器会有以下行为。

Hero hero = new Hero();

hero.say();

其实这都是编辑器行为,是编辑器(Android Studio)本身针对java一些预设的注解做的一些行为定制。我们自己新增这个级别的注解是改变不了编辑器行为的,故这个级别的注解对我们开发意义不大。如果你想定义,其实也就只能给开发者自己看看,编辑器是不会理会你的。

- RetentionPolicy.CLASS编译阶段 

这个级别后面详细讲,也是最难的地方,之所以说难,主要是因为平时开发对这一块开发较少,缺乏经验积累。

- RetentionPolicy.RUNTIME 运行阶段

这个界别注解可以被带到jvm中,你可以通过java的反射机制获取到这个注解,并根据注解做相应的处理。

举个例子:

在Gson序列化一个对象时,默认会使用字段的名字作为json中键值对的键,而当我们为对象中字段添加了@SerializedName("test")后,生成的json对应的字段的key就变成了test了。

下面是我自己的应用场景:

App启动时需要加载配置,并将配置文件中的信息加载到指定的指定对象中。

//类定义

public class AppInfo {

    @SerializedName("appId")

    public String appId = "";

    @SerializedName("authKey")

    public String appKey = "";

}

//通过反射加载对象,clazz传入的就是AppInfo.class

//这个方法是伪代码,只为说明问题

public static  <T> T  loadObjectFromConfigFile(Class<T> clazz) {

    //创建反射类的对象实例

     T object = clazz.newInstance();

    //加载配置文件

     Properties properties = new Properties();

     properties.load(inputStream);

    //获取类中所有字段

     Field[] fieldArray = clazz.getDeclaredFields();

     for(Field field : fieldArray) {

        //获取字段上SerializedName类型的注解

         SerializedName serializedName = field.getAnnotation(SerializedName.class);

         if(serializedName != null) {

             //存在注解,则以注解的值为配置文件的key,读取配置文件中的值

             String value = properties.getProperty(serializedName.value(), null);

            if(value != null) {

                if(field.getType() == String.class) {

                    field.setAccessible(true);

                    //将值赋值给对象

                    field.set(object, value);

                }

            }

        }

    }

    return object;

}

 

上面例子中是从配置文件中读取值,然后赋值给对象,这个过程类似Gson从json数据中读取值,然后赋值给对象的过程。

 

4. 编译阶段注解详解

4.1 注解处理器简介

注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具。简单的说,在源代码编译阶段,通过注解处理器,我们可以获取源文件内注解(Annotation)相关内容。

 

由于注解处理器可以在程序编译阶段工作,所以我们可以在编译期间通过注解处理器进行我们需要的操作。比较常用的用法就是在编译期间获取相关注解数据,然后动态生成.java源文件(让机器帮我们写代码),通常是自动产生一些有规律性的重复代码,解决了手工编写重复代码的问题,大大提升编码效率。

比较典型的开源项目有:

butterknife,Dagger2,EventBus,Retrofit等

 

4.2注解处理器使用

前面我们已经懂得了注解的基本语法,下面介绍下注解配合注解处理器的用法。

1.创建一个android测试工程

打开android studio 3.+

创建一个android空的hello world工程。

 

2.创建注解java lib。

点击工程,右键创建一个java lib。

 

 

创建完成后,打开MyBindView.java文件,创建一个自己用于测试的注解类

@Retention(RetentionPolicy.CLASS)

@Target(ElementType.FIELD)

public @interface MyBindView {

    int value() default -1;

}

 

 

实际项目中你可能需要在当前模块的gradle文件中添加如下两个依赖,该测试项目中不需要。

compileOnly "com.google.android:android:4.1.1.4"//android jar 包

api "com.android.support:support-annotations:27.0.2"//android 注解包

 

3.创建注解处理器java lib

跟创建注解lib步骤相似,这里创建的模块名为annotation-processor,类名为MyProcessor。

创建完成后如下图:

 

在gradle文件中添加如下依赖

//注解解析器依赖注解包

implementation project(':annotation-lib')

//注解解析用到的工具包

implementation "com.google.auto:auto-common:0.10"

//一个开源工程,用于生成java代码

api "com.squareup:javapoet:1.10.0"

//auto service注解包,用于向jvm注册注解处理器

compileOnly "com.google.auto.service:auto-service:1.0-rc4"

//解析注解中R.id.xxx使用到的工具包

compileOnly files(org.gradle.internal.jvm.Jvm.current().getToolsJar())

 

下面实现MyProcessor.java

想要在编译时注册注解处理器,MyProcessor类必须要继承

javax.annotation.processing.AbstractProcessor

且要在该类上添加@AutoService(Processor.class)注解。

@AutoService(Processor.class) :向javac注册我们这个自定义的注解处理器,这样,在javac编译时,才会调用到我们这个自定义的注解处理器方法。

 

4. AbstractProcessor方法介绍

1.init(ProcessingEnvironment env):每个Annotation Processor必须有一个空的构造函数。编译期间,init()会自动被注解处理工具调用,并传入ProcessingEnviroment参数,通过该参数可以获取到很多有用的工具类: Elements , Types , Filer **等等

 

2.process(Set<? extends TypeElement> annoations, RoundEnvironment roundEnv):Annotation Processor扫描出的结果会存储进roundEnv中,可以在这里获取到注解内容,编写你的操作逻辑。注意,process()函数中不能直接进行异常抛出,否则的话,运行Annotation Processor的进程会异常崩溃,然后弹出一大堆让人捉摸不清的堆栈调用日志显示.

3.getSupportedAnnotationTypes(): 该函数用于指定该自定义注解处理器(Annotation Processor)是注册给哪些注解的(Annotation),注解(Annotation)指定必须是完整的包名+类名(eg:com.example.MyAnnotation)

举个简单例子:

MyBindView.class.getCanonicalName()

这句代码获取的就是完成包名加类名。

 

注意,如果不通过此方法告诉jvm你的注解处理器处理哪些注解,则jvm不会回调process方法。

 

4.getSupportedSourceVersion():用于指定你的java版本,一般返回:SourceVersion.latestSupported()。当然,你也可以指定具体java版本:

return SourceVersion.RELEASE_7;

  1. 给demo添加依赖

要使用这个注解处理器,必须在demo的gragle文件中添加依赖。

普通库使用implementation,注解处理器要使用annotationProcessor关键字(之前是使用apt,现在google已经废弃第三方apt插件,改为统一使用annotationProcessor)。

dependencies {

 implementation project(':annotation-lib')

 annotationProcessor project(':annotation-processor')

}

 

在代码代码中使用自己定义的MyBindView注解

public class MainActivity extends AppCompatActivity {

@MyBindView(123)

TextView textView;

 

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

   }

}

 

4.3调试注解处理器

1.添加日志信息

注解处理是在编译阶段完成的,故无法使用logcat查看,想要添加log信息必须使用注解处理器提供的log类,Message,该类通过AbstractProcessor的init参数ProcessingEnvironment中获取。

日志方法使用:

private void log(String msg) {

    mMessager.printMessage(Diagnostic.Kind.NOTE, msg);

}

 

注意Error级别的日志会导致编译器编译失败。

 

通过上面的方法,再点击菜单栏Build ->Rebuild Preject重新编译程序,就可以在Message窗口看到日志输出了。

 

注意Android studio 3.1以上 把Message窗口 与 buid 合并了,

默认不会显示原来message的信息,如果想查看原来版本的message信息,

只需要 打开下图这个开关

2.断点调试

断点调试第一步,添加remote调试模块

在菜单栏中点击Run->Edit Configurations

 

名字自己起一个,这里使用的是test,其他参数都不要改动,注意将1处的内容拷贝下来,然后点击ok

 

断点调试第二步配置jvm参数,

这一步有两种方式可用,如果你遇到一种配置方式无效则可以尝试使用另一种方式

方式一:

从上图可以看出,我们项目中目前有3个模块,注解定义模块(annotation-lib)、注解处理模块(annotation-processor)和应用模块(app)。

注解实际是使用在app层面的,点击as右侧的gradle窗口,选择app->other->compileDebugJavaWithJavac->右键Create Configuration。

图中VM options填入上一步拷贝的参数。

 

方式二:

在工程根目录中找到gradle.properties文件,

在文件中添加如下配置,其中jvmargs就是上一步拷贝的内容

org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

org.gradle.daemon=true

注意,如果该配置文件中已经存在org.gradle.jvmargs配置,则将上面jvm参数添加到已有配置后面即可,注意使用空格隔开,如下图:

第三步,添加断点

跟普通java程序添加断点一样,点击代码左侧面板就能添加和取消断点了

 

断点调试第三步,开启远端调试端口

点击1处,然后选择第一步创建的remote调试模块2,然后点击3处的debug按钮。

此时4处状态如图,代表启动成功。

如下图则是启动成功

否则可能会看到如下错误:

Error running 'test': Unable to open debugger port (localhost:5005): java.net.ConnectException "Connection refused: connect"

遇到这个错误你需要检查如下几个地方:

1.在windows下使用netstat -ano|findstr 5005命令也没有看到被其他程序占用端口,

2. 检查一下android studio是否开启别的工程,并且在别的工程启用了此端口(就是别的工程有没有使用java的5005端口开启远程调试服务)

3. 如果1、2检查没有问题,那咨询一下公司IT是否禁用5005端口。

4.如果1、2、3都没有问题,那恭喜你,极有肯能是防火墙万岁。

打开防火墙高级设置,在入站规则中添加java.exe的入站权限,如下图,java.exe位于android studio安装目录\jre\bin\java.exe

新建规则->程序->选择android studio安装目录下的java.exe:

 

 

 

5.如果上面4中方法都试过了,那你只能祈祷一分钟,上帝保佑,重启一下电脑试试吧。

6.重启以后还不行,是的,换个端口再试试(比如将端口换成50005,注意步骤一和步骤二的端口要保持一致)。

 

断点调试第四步:重新编译程序

经过上面的步骤,远程调试模块创建好了,java虚拟机启动参数设置完成,调试服务启动成功,那最后一步就是编译程序了:

点击菜单栏Build->Rebuild Project就可以进入断点了。

如果还是不行,则点击Build->Clean Project先清空一下工程,然后再Rebuild Project。

如果遇到下图中GBK的不可映射字符错误,

则需要再annotation-processor模块的gradle文件中添加java编译字符集配置。

tasks.withType(JavaCompile) {

    options.encoding = "UTF-8"

}

 

 

5.实现一个简单demo

5.1.使用自定义注解demo代码

此处demo主要是在4.2注解处理器使用篇幅的基础上,在MyProcessor中实现解析MyBindView注解并生成绑定类,然后在app模块中实现MyBindViewUtils实现绑定。

下面是生成的绑定类MainActivity_MyViewBinding.java

package com.example.guozr.annotationtest;

 

import android.support.annotation.UiThread;

 

public class MainActivity_MyViewBinding {

  @UiThread

  public MainActivity_MyViewBinding(MainActivity target) {

    target.textView = (android.widget.TextView) target.findViewById(R.id.textView_hello_world);

  }

}

 

MyBindViewUtils.java代码

import android.app.Activity;

 

import java.lang.reflect.Constructor;

import java.lang.reflect.InvocationTargetException;

 

public class MyBindViewUtils {

    public static void bind(Activity activity) {

        Class clazz = activity.getClass();

        //利用java反射反射生成的类

        Class<?> bindingClass = clazz.getClassLoader().loadClass(clazz.getName() + "_MyViewBinding");

//反射构造方法并执行,构造方法中实现自动查找和赋值功能

        Constructor<?> constructor = bindingClass.getConstructor(clazz);

        constructor.newInstance(activity);

}

 

在MainActivity中使用写好的工具类

public class MainActivity extends AppCompatActivity {

 

    //添加自动绑定注解

    @MyBindView(R.id.textView_hello_world)

    TextView textView;

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        //调用绑定方法

        MyBindViewUtils.bind(this);

        //测试一下是否生效

        textView.setText("bind view Sucess");

    }

}

 

最后运行结果是ok的。

 

通过上面代码可以看出,反射调用和使用自定义注解都很简单,关键是自动生成的绑定类MainActivity_MyViewBinding.java的生成过程。

 

5.2绑定类生成

注解的解析主要是在我们自定义的MyProcessor中完成的,下面介绍下复写的部分方法

 

@Override

public synchronized void init(ProcessingEnvironment processingEnvironment) {

super.init(processingEnvironment);

//Filer,java文件生成后写入的地方

mFiler = processingEnvironment.getFiler();

//gradle运行日志记录的帮助类

mMessager = processingEnvironment.getMessager();

//解析注解的辅助类

mElementUtils = processingEnvironment.getElementUtils();

//jvm编译环境的一些选项,可以自己添加

    mOptions = processingEnvironment.getOptions();

try {

    //获取类编译时原始数据的类,比如@MyBindView(R.id.text)

    //我们正常只能获取id的整型值,要想获取原始R.id.text

    //这个字符串,就必须使用到该类

        trees = Trees.instance(processingEnv);

    } catch (IllegalArgumentException ignored) {

    }

    logi("MyProcessor  init ...");

}

 

@Override

public Set<String> getSupportedAnnotationTypes() {

Set<String> annotations = new LinkedHashSet<>();

//返回包含自定义注解的集合,否则jvm不会掉process方法

   annotations.add(MyBindView.class.getCanonicalName());

   return annotations;

}

 

@Override

public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { 

    for (Element element : roundEnvironment.getElementsAnnotatedWith(MyBindView.class)) {

       try {

           //所有注解转到parseBindView方法执行

           parseBindView(element);

       } catch (Exception e) {

            mMessager.printMessage(Diagnostic.Kind.ERROR, e.getMessage());

        }

    }

   return false;

}

 

private void parseBindView(Element element) {

    //获取@Mybindview修饰的成员变量的修饰符

    Set<Modifier> modifiers = element.getModifiers();

    //如果是不可访问或不可改变的成员变量,则报错

    if(modifiers.contains(Modifier.PRIVATE) or modifiers.contains(Modifier.FINAL)) {

        loge("不合法的修饰符");

    }

    //获取拥有该成员的类元素

    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    //如果不是类的成员变量则报错

    if(enclosingElement.getKind() != ElementKind.CLASS) {

        loge("不合法的成员");

    }

    //如果类是私有的,则报错

    if (enclosingElement.getModifiers().contains(Modifier.PRIVATE)) {

        loge("不合法的类访问属性");

    }

    TypeMirror elementType = element.asType();

    //TypeKind.DECLARED代表java语言中定义的类或接口类型

    if(elementType.getKind() != TypeKind.DECLARED) {

        loge("MyBindView只修饰成员变量");

    }

 

    DeclaredType declaredType = (DeclaredType) elementType;

 

    //解析完了注解,接下来使用javapoet生成java文件

    //javapoet使用请参考github上介绍:https://github.com/square/javapoet

    //各个类的含义建议参考jdk文档java.compiler模块

    //获取包名

    String packageName = MoreElements.getPackage(element).getQualifiedName().toString();

    logi("packageName : " + packageName);

    String className = enclosingElement.getQualifiedName().toString().substring(

            packageName.length() + 1).replace('.', '$');

    logi("className : " + className);

 

    //被注解标识的字段的名称

    String name = element.getSimpleName().toString();

    logi("element name : " + name);

 

    //被注解标识的字段的类型(如android.view.TextView)

    TypeName type = TypeName.get(elementType);

    logi("element type : " + type);

 

    //自己根据注解所在类的类名创建一个新的类

    ClassName bindingClassName = ClassName.get(packageName, className + "_MyViewBinding");

 

    //生成java类

    TypeSpec.Builder classBuilder = TypeSpec.classBuilder(bindingClassName.simpleName())

            .addModifiers(Modifier.PUBLIC);

 

    Id myBindViewId = elementToId(element, MyBindView.class, element.getAnnotation(MyBindView.class).value());

 

    //生成构造方法

    MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()

            .addAnnotation(ClassName.get("android.support.annotation", "UiThread"))

            .addModifiers(Modifier.PUBLIC)

            .addParameter(TypeName.get(enclosingElement.asType()), "target")

             //构造方法中添加findViewById代码

            .addStatement("target.$L = ($L) target.findViewById($L)", name, type.toString(), myBindViewId.code);

 

    //将构造方法添加到类中

    classBuilder.addMethod(constructorBuilder.build());

    //将类写入java文件中

    JavaFile javaFile = JavaFile.builder(packageName, classBuilder.build()).build();

    try {

        javaFile.writeTo(mFiler);

    } catch (IOException e) {

        loge("Unable to write binding for type " + enclosingElement.getSimpleName() + ", error : " + e.getMessage());

    }

 

}

 

最后将代码工程在这里,大家可以自己下载运行

https://download.csdn.net/download/guozhongrui000/10658885

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值