初识Android高级应用之 APT
APT 是什么?
可以理解添加注解就可以预编译生成代码的一种方式。
分别包括三种module
1.apt-annotation (java libarary):这里需要定义一些注解类 比如:@BindView
2.apt-library :这里需要借助反射来获取并绑定一些数据 如BufferKnife.bind(this);
3.apt-processor (java libarary): 这里通过定义的注解 在APP的generated生成代码。
他们的依赖关系:
apt-processor 因为需要获取注解 ,则需要依赖apt-annotation
APP ,则需要依赖apt-annotation ,apt-library,.apt-processor 三个。
这里对Element说明一下:
然后是分别处理我们自定义的三个注解,得到注解标致的value值也就是权限requestCode和声明的方法名,装载到mProxyMap集合中
代码中的roundEnv.getElementsAnnotatedWith返回的其实就是每个注解所标注的方法元素集合。
element有多个子集,介绍几个常见的
TypeElement------类型元素,注意枚举对应的时类,注解对应的接口
ExecutableElement------一般是指可执行的方法元素
VariableElement------一般指常量,变量 ,异常对象元素
pakeageElement-----一般指包元素,可以获得包信息
TypeParamterElement----这个也是比较常用的,一般指继承类实现的接口,或者泛型对象元素
下面以案例的方式来讲解:
1.apt-annotation
@Retention(RetentionPolicy.CLASS) //编译时
@Target(ElementType.FIELD)//注解的类型 字段
public @interface BindView {
int value();
}
3.apt-processor
这里是核心 千万别出错,否则无法生成Java文件
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
@AutoService(Processor.class)
public class LoBindViewProcessor extends AbstractProcessor
@AutoService(Processor.class)
public class LoBindViewProcessor extends AbstractProcessor {
private Elements elementUtils;
private Messager environmentMessager;
private HashMap<String , ClassCreatorProxy> mHashMapCache = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnvironment.getElementUtils();
environmentMessager = processingEnvironment.getMessager();
}
@Override
public Set<String> getSupportedOptions() {
HashSet hashSet = new LinkedHashSet();
hashSet.add(BindView.class.getCanonicalName());//com.xxx.BindView.class
return hashSet;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
mHashMapCache.clear();
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
//字段
VariableElement variableElement = (VariableElement) element;
// 获取成员变量所在的类。(父元素的全限定名)
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
// // 获取类的全限定名。com.xx.xx.MainActivity.class
String fullClassName = classElement.getQualifiedName().toString();
System.out.println(fullClassName);
//elements的信息保存到mProxyMap中
ClassCreatorProxy proxy = mHashMapCache.get(fullClassName);
if (proxy == null) {
proxy = new ClassCreatorProxy(elementUtils, classElement);
mHashMapCache.put(fullClassName, proxy);//根据com.xx.xx.MainActivity.class 对应一个ClassCreatorProxy解析器
}
BindView bindAnnotation = element.getAnnotation(BindView.class);
int id = bindAnnotation.value();
// 将同一个类的注解的所有成员变量放到一个ClassInfo类中
proxy.putElement(id, variableElement);
}
for (String key : mHashMapCache.keySet()) {
ClassCreatorProxy proxyInfo = mHashMapCache.get(key);
//通过square公司的库生成Java文件
JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCode2()).build();
try {
// 生成文件
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
environmentMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
return true;
}
}
ClassCreatorProxy的实现如下:
public class ClassCreatorProxy {
private String mBindingClassName;
private String mPackageName;
private TypeElement mTypeElement;
private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();
public ClassCreatorProxy(Elements elementUtils, TypeElement classElement) {
this.mTypeElement = classElement;
PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);
String packageName = packageElement.getQualifiedName().toString();
String className = mTypeElement.getSimpleName().toString();//MainActivity
this.mPackageName = packageName;
this.mBindingClassName = className + "_ViewBinding";
}
public void putElement(int id, VariableElement element) {
mVariableElementMap.put(id, element);//3234,字段的修饰类型
}
/**
* 创建Java代码
*
* @return
*/
public String generateJavaCode() {
StringBuilder builder = new StringBuilder();
builder.append("package ").append(mPackageName).append(";\n\n");
builder.append("import com.example.gavin.apt_library.*;\n");
builder.append('\n');
builder.append("public class ").append(mBindingClassName);
builder.append(" {\n");
generateMethods(builder);
builder.append('\n');
builder.append("}\n");
return builder.toString();
}
/**
* 加入Method
* 手动拼接
* @param builder
*/
private void generateMethods(StringBuilder builder) {
builder.append("public void bind(" + mTypeElement.getQualifiedName() + " host ) {\n");
// host.mButton = (android.widget.Button)(((android.app.Activity)host).findViewById( 2131165218));
for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);//字段限定
String name = element.getSimpleName().toString();//mButton
String type = element.asType().toString();//Button
builder.append("host." + name).append(" = ");
builder.append("(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));\n");
}
builder.append(" }\n");
}
public String getProxyClassFullName() {
return mPackageName + "." + mBindingClassName;
}
public TypeElement getTypeElement() {
return mTypeElement;
}
//======================
/**
* 创建Java代码
* javapoet
*
* @return
*/
public TypeSpec generateJavaCode2() {
TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethods2())
.build();
return bindingClass;
}
/**
* 加入Method
* javapoet
*/
private MethodSpec generateMethods2() {
ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(host, "host");//Activity
for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String name = element.getSimpleName().toString();
String type = element.asType().toString();
// host.mButton = (android.widget.Button)(((android.app.Activity)host).findViewById( 2131165218));
methodBuilder.addCode("host." + name + " = " + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));");
}
return methodBuilder.build();
}
public String getPackageName() {
return mPackageName;
}
}
apt-library
然后就可以通过反射进行调用赋值了:
//apt-library
public class BindViewTools {
public static void bind(Activity activity) {
Class clazz = activity.getClass();
try {
Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
Method method = bindViewClass.getMethod("bind", activity.getClass());
method.invoke(bindViewClass.newInstance(), activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
APP中使用:
@BindView(R.id.tv)
TextView mTextView;
@BindView(R.id.btn)
Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BindViewTools.bind(this);
mTextView.setText("bind TextView ");
mButton.setText("bind Button ");
}
最终生成的文件如下:
public class MainActivity_ViewBinding {
public void bind(MainActivity host) {
host.mButton = (android.widget.Button)(((android.app.Activity)host).findViewById( 2131165218));
host.mTextView = (android.widget.TextView)(((android.app.Activity)host).findViewById( 2131165321));
}
}
---------------------------------------------------
APP的依赖
implementation project(':apt-annotation')
implementation project(':apt-library')
annotationProcessor project(':apt-processor')
apt-processor的依赖
implementation 'com.google.auto.service:auto-service:1.0-rc4'
implementation 'com.squareup:javapoet:1.10.0'
implementation project(':apt-annotation')
可在如下目录查看:
![](https://img.haomeiwen.com/i737573/e37a31b14d3f1ce0.png)
最后补充说明一下:就是特别注意Androidstudio3.4.2的版本无法正常生成Java文件。如果确认代码没有问题,还是无法生成Java文件,建议降低一下gradle 和 Application 中 com.android.tools.build:gradle 的版本
我这里是
distributionUrl=https://services.gradle.org/distributions/gradle-4.4-all.zip
classpath 'com.android.tools.build:gradle:3.1.1'
我这里就被坑了好久,com.android.tools.build 从 3.4.2降到3.1.1就可以生成Java文件了。