APT简介
APT英文全称:Android annotation process tool是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。
Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成源文件和原来的源文件,将它们一起生成class文件。简言之:APT可以把注解,在编译时生成代码。
是一种处理注释的工具,它对源代码文件进行检测并找出其中的 Annotation,根据注解自动生成代码,如果想要自定义的注解处理器能够运行,必须要通过 APT 工具来处理。
简单说:根据规则,帮我们生成代码、生成类文件
编译时注解就是通过 APT 来通过注解信息生成代码来完成某些功能,典型代表有 ButterKnife、Dagger、ARouter 等 ButterKnife 原理分析使用APT来处理
annotation的流程
- 1.定义注解(如@MyButterKnife)
- 2.定义注解处理器
- 3.在处理器里面完成处理方式,通常是生成java代码。
- 4.注册处理器
- 5.利用APT完成如下图的工作内容。
手写ButterKnife
想要完全理解ButterKnife底层的APT技术,手写实现ButterKnife可以帮助更好地吸收这种技术。
准备工作
(1)创建Android工程,并且在此项目中新建一个java Module取名为annotataion,用于存放注解。 注意:必须新建java library而不能是Android lib,为什么只能新建java工程,不能是Android工程后面讲
(2)创建第二个java library取名为annotation_process,故名思议此module是存放注解处理器。
(3)为了简单起见,此处就在annotaiton中创建两个注解BindView和OnClick,分别作用于编译期,因此定于为CLASS。
BindView作用:初始化View成员变量,替开发者实现findViewById。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
OnClick作用:初始化Button等按钮,替用户实现setOnClickListener等等。
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface OnClick {
int[] value();
}
(4)导入项目依赖
app module依赖annotation和annotation_process
anontation_process依赖annotation。从此处可以说明为什么annotation和annotation_process必须定义为java library,android工程可以依赖java工程,但反之不行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sdm1iXSz-1676966714695)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/538be825bf864a1ea7b45d5379fd6519~tplv-k3u1fbpfcp-zoom-1.image)]
(5)在annotation_process的build.grade中添加以下两句依赖用于实现注解处理器。
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc3'
定义注解处理器
1、继承AbstractAnnotation, 2、在其类上添加@AutoService 3、实现process方法
@AutoService(Process.class)
public class AnnotationProcess extends AbstractProcessor {
//通过io流动态生成代码
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> set = new HashSet<>();
set.add(BindView.class.getName());
set.add(OnClick.class.getName());
return set;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//将Activity和其内部所有被OnCLick+BindView标记了的元素一一对应起来
HashMap<TypeElement, ElementForType> hashMap = findAnnotationForActivity(roundEnvironment);
//写文件
if (hashMap.size() != 0) {
Writer writer = null;
Iterator<TypeElement> iterator = hashMap.keySet().iterator();
while (iterator.hasNext()) {
TypeElement element = iterator.next();
ElementForType elementForType = hashMap.get(element);
//获取Activity类名
String className = element.getSimpleName().toString();
//生成新类名
String newClassName = className + "$$ButterKnife";
//获取包名
PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(element);
String packageName = packageElement.getQualifiedName().toString();
JavaFileObject javaFileObject = null;
try {
javaFileObject = filer.createSourceFile(packageName + "." + newClassName);
writer = javaFileObject.openWriter();
//将所有要生成的代码存到StringBuffer中
StringBuffer stringBuffer = getStringBuffer(packageName, newClassName, element, elementForType);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return false;
}
public StringBuffer getStringBuffer(String packageName, String newClazzName,
TypeElement typeElement, ElementForType elementForType) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("package " + packageName + ";\n");
stringBuffer.append("import android.view.View;\n");
stringBuffer.append("public class " + newClazzName + "{\n");
stringBuffer.append("public " + newClazzName + "(final " + typeElement.getQualifiedName() + " target){\n");
if (elementForType != null && elementForType.getViewElements() != null && elementForType.getViewElements().size() > 0) {
List<VariableElement> viewElements = elementForType.getViewElements();
for (VariableElement viewElement : viewElements) {
//获取到类型
TypeMirror typeMirror = viewElement.asType();
//获取到控件的名字
Name simpleName = viewElement.getSimpleName();
//获取到资源ID
int resId = viewElement.getAnnotation(BindView.class).value();
stringBuffer.append("target." + simpleName + " =(" + typeMirror + ")target.findViewById(" + resId + ");\n");
}
}
if (elementForType != null && elementForType.getMethodElements() != null && elementForType.getMethodElements().size() > 0) {
List<ExecutableElement> methodElements = elementForType.getMethodElements();
for (ExecutableElement methodElement : methodElements) {
int[] resIds = methodElement.getAnnotation(OnClick.class).value();
String methodName = methodElement.getSimpleName().toString();
for (int resId : resIds) {
stringBuffer.append("(target.findViewById(" + resId + ")).setOnClickListener(new View.OnClickListener() {\n");
stringBuffer.append("public void onClick(View p0) {\n");
stringBuffer.append("target." + methodName + "(p0);\n");
stringBuffer.append("}\n});\n");
}
}
}
stringBuffer.append("}\n}\n");
return stringBuffer;
}
private HashMap<TypeElement, ElementForType> findAnnotationForActivity(RoundEnvironment roundEnvironment) {
HashMap<TypeElement, ElementForType> hashMap = new HashMap<>();
//获取到所有被BindView标记了的View元素
Set<? extends Element> bindViewSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);
//获取所有被OnClick标记了的Method元素
Set<? extends Element> onClickSet = roundEnvironment.getElementsAnnotatedWith(OnClick.class);
for (Element element : bindViewSet) {
VariableElement bindView = (VariableElement) element;
//获取含有该BindView标记对应的Activity
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
//获取该Activity中所有被注解标记的元素
ElementForType elementForType = hashMap.get(typeElement);
List<VariableElement> variableElementList;
if (elementForType != null) {
//获取该Activity中所有被BindView标记的View元素
variableElementList = elementForType.getViewElements();
if (variableElementList == null) {
variableElementList = new ArrayList<>();
elementForType.setViewElements(variableElementList);
}
} else {
//若ElementForType不存在则创建
elementForType = new ElementForType();
variableElementList = new ArrayList<>();
elementForType.setViewElements(variableElementList);
//判断hashmap中是否保存了elementForType和typeElement
if (!hashMap.containsKey(typeElement)) {
hashMap.put(typeElement, elementForType);
}
}
variableElementList.add(bindView);
}
//onclick同理
for (Element element : onClickSet) {
ExecutableElement onClick = (ExecutableElement) element;
//获取含有该OnClick标记对应的Activity
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
//获取该Activity中所有被注解标记的元素
ElementForType elementForType = hashMap.get(typeElement);
List<ExecutableElement> executableElementList;
if (elementForType != null) {
//获取该Activity中所有被OnClick标记的Method元素
executableElementList = elementForType.getMethodElements();
if (executableElementList == null) {
executableElementList = new ArrayList<>();
elementForType.setMethodElements(executableElementList);
}
} else {
//若ElementForType不存在则创建
elementForType = new ElementForType();
executableElementList = new ArrayList<>();
elementForType.setMethodElements(executableElementList);
//判断hashmap中是否保存了elementForType和typeElement
if (!hashMap.containsKey(typeElement)) {
hashMap.put(typeElement, elementForType);
}
}
executableElementList.add(onClick);
}
return hashMap;
}
}
ButterKnife在编译阶段会为每个使用了BindView、OnClick等注解的类生成一个新类,因此创建了ElementForType用以封装同一个Activity中的所有被BindView+OnCLick等注解的元素。
/**
* 此类用以封装同一个Activity中的所有被BindView+OnCLick注解的元素
*/
public class ElementForType {
//此出的VariableElement代表的控件元素
private List<VariableElement> viewElements;
//此处的ExecutableElement代表的方法元素
private List<ExecutableElement> methodElements;
public ElementForType() {
}
public ElementForType(List<VariableElement> viewElements, List<ExecutableElement> methodElements) {
this.viewElements = viewElements;
this.methodElements = methodElements;
}
public List<VariableElement> getViewElements() {
return viewElements;
}
public void setViewElements(List<VariableElement> viewElements) {
this.viewElements = viewElements;
}
public List<ExecutableElement> getMethodElements() {
return methodElements;
}
public void setMethodElements(List<ExecutableElement> methodElements) {
this.methodElements = methodElements;
}
}
定义ButterKnife
由于APT技术是在编译期替我们生成的一个新类,因此需要注意的是ButterKnife.java中的newClassName类名必须要和AnnotationProcess.java中的newClassName一致,否则无法生效。
public class ButterKnife {
public static void bind(Context context){
//获取类名
String className = context.getClass().getName();
//获取生成的类的构造器
String newClassName = className + "$$ButterKnife";
Constructor<?> constructor = null;
try {
constructor = Class.forName(newClassName).getConstructor();
constructor.newInstance(context);
} catch (Exception e) {
e.printStackTrace();
}
}
}
使用Butterknife
使用和ButterKnife一样即可
public class MainActivity extends AppCompatActivity {
@BindView(R.id.btn1)
private Button btn1;
@BindView(R.id.btn2)
private Button btn2;
@BindView(R.id.btn3)
private Button btn3;
@BindView(R.id.text)
private EditText text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text.setText("xixi");
}
@OnClick({R.id.btn1,R.id.btn2,R.id.btn3})
public void onClick(View view){
switch (view.getId()){
case R.id.btn1:
Log.d("kkjj","按钮1");
break;
case R.id.btn2:
Log.d("kkjj","按钮1");
break;
case R.id.btn3:
Log.d("kkjj","按钮1");
break;
}
}
}
上文解析了Android架构设计技能中;APT技术实现butterknife;更多的Android框架学习及高级Android进阶;可参考《Android核心技术手册》里面内容包含上千个技术知识点。点击查看》》》
结
BUtterKnife通过注解+注解处理器的方式在编译期动态地生成XXX$$ButterKnife.java文件,在此文件中替开发者实现了findViewById、setOnClickListener等等操作。
所以ButterKnife等三方框架仍然通过findViewById、setOnClickListener操作实现的view绑定,监听注册。因此并没有绕过Android原生的开发规则。