Android APT学习
- 编译时技术作用生成模板代码
-
什么是编译时技术?
1.0 学习目标
- 模仿Databing findViewById() 功能,对APT 注解有一个简单实用认识
2.0 重点知识点
- 谷歌注解处理器库 annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
- 谷歌注解处理器库 compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
- 抽象类 AbstractProcessor ,该类属于Java,所以建立module创建JavaLibrary
- 注解处理器增加注解 @AutoService(Processor.class)
3.0 上代码学习
- 创建android项目
-
项目内创建注解的module,选择javalibrary ,名称为annotations
-
项目内创建注解处理器的module,选择 javalibrary,名称为annotations_compiler
- 在项目/app/build.gradle下引入这个俩个moudle
正常引入module都为implementation因为annotations_compiler注解处理器所以改为 annotationProcessor
-
在Module annotations注解中创建BindView 注解,用来传递控件ID(R.id.xxx)
package com.zyj.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 定义注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
-
在Module annotations_compiler注解处理器 中创建AnnotationsCompiler,解析各个界面注解信息
- 思路:
- 初始化Filer ,作用:编译时获取到注解控件ID 写入文件就是把findViewById(R.id.xxx)写入文件。
- 设置注解处理器处理范围,只解读BindView 注解。
- 以下是process方法里面思路:
- 获取使用注解的所有Activity的节点。
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class)- 把所有节点进行分类,Activity和其成员变量归为一类,存入map中。
VariableElement Field 成员变量 、TypeElement class 类 、 PackageElement 包 、 等可查看Element 子类- 分好类,存入map后,通过filer创建java文件,生成代码。
**重点看注释
package com.zyj.anntotations_compiler;
import com.google.auto.service.AutoService;
import com.zyj.annotations.BindView;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
/**
* 注解处理器 用来生成代码
* <p>
* 1. 注解处理器一定要继承一个抽象类 AbstractProcessor (AbstractProcessor 属于javax)
* 2。需要依赖于谷歌服务库(这样注解就会在这边自动处理)
* annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
* compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
* 3.依赖使用注解 @AutoService(Processor.class) 代表这个类就是一个注解处理器的类
*/
@AutoService(Processor.class)
public class AnnotationsCompiler extends AbstractProcessor {
// 注意: 我们调试的时候需要打断点或者打印日志,而在处理器里面不起作用,所以我们通过 Messager去操作
Filer filer;
Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();// 文件
messager = processingEnvironment.getMessager();// 日志
messager.printMessage(Diagnostic.Kind.WARNING, "我们开始可以看日志了!日志类型是警告!");
}
/**
* 因为我们注解处理器不需要都要去处理,
* 所以这个方法是声明注解处理器要处理的注解
*
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
// 1. String 类型里面可以添加包名类名,就能去筛选了
// 2. 注解处理器模块需要依赖注解模块
// 3. 这就说明这个注解处理器要处理的注解就是我们声明的这个注解BindView
types.add(BindView.class.getCanonicalName());
return types;
}
/**
* 1. 方法一 :实现该方法
* 2. 方法二:在该类处理器加注解 @SupportedSourceVersion()
* 必须要有一个版本声明
* 声明支持的java 版本
*
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return processingEnv.getSourceVersion();
}
// 该方法专门用来搜索注解的方法
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 生成代码
// 去搜索出用到了BindView注解的节点
// 如果有多个Activity 则会有多个Element 元素,可通过上下文 activity.findViewById()获取节点元素
// VariableElement Field 成员变量 TypeElement class 类 PackageElement 包 等等都是节点
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);// 根据注解获取节点
// 把每个Activity 和它里面的内容放到一起
HashMap<String, List<VariableElement>> map = new HashMap<>();
for (Element element : elements) {
VariableElement variableElement = (VariableElement) element;
// 获取这个成员变量所有在的类的类名
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();//获取成员变量的上一个节点===》就是这个类
typeElement.getQualifiedName().toString();// 获取类名--- 带包名
String className = typeElement.getSimpleName().toString();// 获取类名----不带包名
List<VariableElement> variableElements = map.get(className);// 通过类名 获取 成员变量
if (variableElements == null) {
variableElements = new ArrayList<>();
map.put(className, variableElements);
}
variableElements.add(variableElement);
}
// 生成代码
if (map.size()>0){
Writer writer = null;
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()){
String className = iterator.next();
List<VariableElement> variableElements = map.get(className);
// 获取包名
String packName = getPackName(variableElements.get(0));
// 创建一个类名
String newName = className+"_ViewBinder";
try {
// 创建 java 文件
JavaFileObject sourceFile = filer.createSourceFile(packName + "." + newName);
writer = sourceFile.openWriter();
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("package "+ packName+";\n");
stringBuffer.append("import android.view.View ;\n");
stringBuffer.append("public class "+ newName +" implements IButterKnifer<"+packName+"."+className+">{\n");
stringBuffer.append("public void bind("+packName+"."+className+" target){\n");
for (VariableElement variableElement :variableElements) {
// 遍历成员变量,每一个变量都生成findViewById代码
// 获取成员变量名字
String variableName = variableElement.getSimpleName().toString();
// 获取到上面的注解所持有的value redID
int resID = variableElement.getAnnotation(BindView.class).value();
stringBuffer.append("target."+variableName+"=target.findViewById("+resID+");\n");
}
stringBuffer.append("}\n}\n");
writer.write(stringBuffer.toString());
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (writer!=null){
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return false;
}
/**
* 根据成员变量获取包名
* @param variableElement
* @return
*/
private String getPackName(VariableElement variableElement) {
Element enclosingElement = variableElement.getEnclosingElement();// 获取上一级元素
PackageElement packageOf = processingEnv.getElementUtils().getPackageOf(enclosingElement);// 获取包节点
String packName = packageOf.getQualifiedName().toString();// 获取到包名
return packName;
}
}
- annotations_compiler注解处理器引入注解module和谷歌库
因为处理器annotations_compiler需要解读注解annotations,所以需要引入module,而引入谷歌库就是为了自动调用该注解处理器类
-
在项目app中新建包为apt,在其中创建IButterKnifer接口,AptActivity和IButterKnife类
- IButterKnifer接口
public interface IButterKnifer<T> {
void bind(T target);
}
- IButterKnife类
传递上下文获取当前Activity内控件ID
public class IButterKnife {
public static void bind(Object target){
String name = target.getClass().getName()+"_ViewBinder";;
try {
Class<?> aClass = Class.forName(name);
if (IButterKnifer.class.isAssignableFrom(aClass)){
// 判断 aClass 是不是 IButterKnifer类或子类
IButterKnifer iButterKnifer = (IButterKnifer) aClass.newInstance();
iButterKnifer.bind(target);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
- AptActivity
import android.os.Bundle;
import android.widget.TextView;
import com.zyj.annotations.BindView;
import com.zyj.obslove.R;
public class AptActivity extends AppCompatActivity {
@BindView(R.id.text1)
TextView text1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_apt);
IButterKnife.bind(this);
text1.setText("---------------------");
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".apt.AptActivity">
<TextView
android:id="@+id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="text1"/>
</LinearLayout>