Android ButterKnife 注解与依赖注入框架 – 编译时注解(二)
原文:https://ke.qq.com/course/130901
创建模块
建立依赖
以下配置可能出错,切换新旧配置方法。参考:这里
新方法
apply plugin: 'java-library'
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
// 注解处理器要起作用必须进行注册。自动注册(类似于四大组件)
api 'com.google.auto.service:auto-service:1.0-rc3'
compile project(path: ':butterknife_annotations')
}
// 控制生成文件的代码格式,可以省略
tasks.withType(JavaCompile){
options.encoding="UTF-8"
}
// ViewBinder.java
/**
* 用于绑定一个 Activity。
* ButterKnife.bind(this);
* 使用 ButterKnife 时,绑定 Activity 的功能
*/
public interface ViewBinder<T> {
public void bind(T target);
}
// activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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=".MainActivity">
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
/**
* 绑定控件的注解
* @BindView(R.id.btn) Button btn;
*
* 注解的作用目标,按钮等(局部变量)
* @Target(ElementType.FIELD)
*
* 选择其他的没有问题,为了节省资源,提高APP运行效率这里选择只在源码阶段有效
* @Retention(RetentionPolicy.SOURCE)
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
int value(); // @BindView(R.id.btn) 括号中的值
}
// ButterKnifeProcess.java
import com.google.auto.service.AutoService;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
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.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.JavaFileObject;
import app.zowneo.butterknife_annotations.BindView;
/*
*
* 这个类就是 APT,可以存在多个。
* .java 源码编译成为 .class 时,处理 .java 中的注解信息。
*
* 要具备注解处理器的功能,这个类必须继承
*
* 注册注解处理器
* @AutoService(Processor.class)
*
* 初始化的工作也可以使用注解的方式完成
*/
@AutoService(Processor.class)
public class ButterKnifeProcess extends AbstractProcessor {
//所有的注解处理器都要完成初始化工作
/*
* 1. 告诉APT,我们需要处理那些注解,
* 这里需要处理的是@BindView(R.id.btn) 中的@BindView。
* @Override 这样的不需要处理
* 设置我们支持的注解的类型,返回的类型是注解类型的名字
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();// 使用其他的集合也可以
types.add(BindView.class.getCanonicalName());//注解类型的名字
/*
* 也可以监听系统的注解
* types.add(Override.class.getCanonicalName());
*/
return types;
}
/*
* 2. 设置支持的 JDK 的版本。
* latestSupported 最新的版本。
* */
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/*
* 3. 设置一个生成(自动生成文件)源文件的对象并初始化。
* processingEnvironment 其实就是注解处理器的上下文环境。
* processingEnvironment.getFiler() 该环境变量除了在下面的方法中出现,
* 还在 public abstract class AbstractProcessor implements javax.annotation.processing.Processor {
* protected javax.annotation.processing.ProcessingEnvironment processingEnv;
* private boolean initialized;
* ...}
* 父类中出现,如果使用的过程中成员变量找不到,就使用父类中的。
* */
Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
}
/**
* 在这个方法中,完成注解处理器要完成的工作
*
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
/*
* roundEnvironment:
* 能拿到我们要处理注解的对应的文件如 MainActivity 在其变成 class 文件的过程中
* 对里面的注解信息进行扫描,将下面注解提供的信息,
* 以结构化文档的形式进行保存,如TypeELement、VariableElement、ExecuteableElement
* @BindView(R.id.btn)
* Button btn;
*
* 结构化文档:层次分明(java、html)
* public class Snake { // TypeELement
* private int a; // VariableElement
* private Snake other; // VariableElement
* public Snake() {} // ExecuteableElement
* public void method() {} // ExecuteableElement
* }
* */
/*
* 获取和 BindView 相关的信息。
* 例如有十个按钮,就会保存十个这样的元素
* (Activity(2个),Activity2(3个),Activity3(5个)
* elements 是TypeELement等的父类,不区分类型,
* 每个按钮存在哪一个 Activity 中,需要我们自己进行处理区分。
* 使用一个Map:key 保存 Activity,value 保存每个按钮。
*/
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
// 分类 key == 类名 value == 成员变量的集合
Map<String, List<VariableElement>> cacheMap = new HashMap<>();
// 开始分类
for (Element element : elements) {
VariableElement variableElement = (VariableElement) element;
// key 保存的形式 app.zowneo.mybutterknife.MainActivity
String activityName = getActivityName(variableElement);
// 存放 key (Activity)的集合
List<VariableElement> list = cacheMap.get(activityName);
// 第一次获取时,里面是空的
if (list == null) {
list = new ArrayList<>();
cacheMap.put(activityName, list);
}
list.add(variableElement);// 这里面放的就是成员变量
}
// 分类完成开始生成文件
Iterator<String> iterator = cacheMap.keySet().iterator();
while (iterator.hasNext()) {
// 获取文件名(含包名)app.zowneo.mybutterknife.MainActivity
String activity = iterator.next();
// 文件中的成员变量,这里指的是 Button 等
List<VariableElement> cacheElements = cacheMap.get(activity);
String packageName = getPackageName(cacheElements.get(0));
// 生成文件名 MainActivity_ViewBinder
String newActivityBinder = activity + "_ViewBinder";
String activitySimpleName = cacheElements.get(0).getEnclosingElement()
.getSimpleName().toString() + "_ViewBinder";
// 生成文件
Writer writer = null;
try {
// 默认生成为文件路径在 apt 路径下
JavaFileObject sourceFile = filer.createSourceFile(newActivityBinder);
writer = sourceFile.openWriter();// 打开文件流,开始写文件
/*
* package app.zowneo.mybutterknife;
* import app.zowneo.mybutterknife.ViewBinder;
* public final class MainActivity_ViewBinder implements ViewBinder{
* public void bind(app.zowneo.mybutterknife.MainActivity target) {
* target.btn = (android.widget.Button)target.findViewById(2131165218);
* }
*}
* */
writer.write("package " + packageName + ';');
writer.write("\n");
writer.write("import " + packageName + ".ViewBinder;");
writer.write("\n");
writer.write("public final class " + activitySimpleName
+ " implements " + "ViewBinder<" + activity + ">{");
writer.write("\n");
writer.write(" public void bind(" + activity + " target) {\n");
// 可能有多个控件,需要使用循环来写
for (VariableElement variableElement : cacheElements) {
BindView bindView = variableElement.getAnnotation(BindView.class);
// 获取成员变量名
String fieldName = variableElement.getSimpleName().toString();
// 获取成员变量的类型
TypeMirror typeMirror = variableElement.asType();
// 写入
writer.write(" target." + fieldName + " = (" + typeMirror.toString()
+ ")target.findViewById(" + bindView.value() + ");");
writer.write("\n");
}
writer.write(" }\n}");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return false;
}
/*
* 得到包名
* */
private String getPackageName(VariableElement variableElement) {
/*
* 得到被包裹元素的上一级元素,这里是如
* @BindView(R.id.btn)
* Button btn;
* 的上一级元素,也就是类元素 MainActivity。
* 更上一级的元素,也就是包元素。
* */
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
String packageName = processingEnv.getElementUtils().getPackageOf(typeElement)
.toString();
// app.zowneo.mybutterknife
return packageName;
}
/*
* 得到类名
* */
private String getActivityName(VariableElement variableElement) {
String packageName = getPackageName(variableElement);
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
String className = typeElement.getSimpleName().toString();
// app.zowneo.mybutterknife.MainActivity
return packageName + '.' + className;
}
}
// ButterKnifer.java
/*
* 给用户的接口
* */
public class ButterKnifer {
public static void bind(Activity activity) {
String className = activity.getClass().getName() + "_ViewBinder";
// 不能使用 new 的方式,因为这个文件还没有生成
try {
Class<?> viewBinderClass = Class.forName(className);
ViewBinder viewBinder = (ViewBinder) viewBinderClass.newInstance();
// 实际上是我们生成的实现类,完成的绑定
viewBinder.bind(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}