前言

ButterKnife是一个依赖注入框架,8.0之前是通过反射的方式实现,具体实现可以参考这篇文章​​自定义注解​​,今天我们来看下8.0之后的编译时注解实现方式,编译时注解相比运行时注解效率高,是通过在编译时生成代码的方式来绑定控件。

结构

手写ButterKnife_ide

app:我们的Android项目

butterknife-annotation:java library,定义注解

butterknife-compiler:java library,注解处理器

 

手写ButterKnife_ide_02

编译时报错

手写ButterKnife_java_03

 

解决办法:

在annotation和compiler的gradle模块中,添加

tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}

手写ButterKnife_ide_04

手写ButterKnife_java_05

/**
* 描述:用于绑定变量
*
* @author Create by zxy on 2018/5/10
*/
@Retention(RetentionPolicy.SOURCE) // 注解只在源码级别保留
@Target(ElementType.FIELD) // 注解用在字段上
public @interface BindView {
int value();
}
/**
* 描述:提供bind方法供生成的类调用
*
* @author Create by zxy on 2018/5/10
*/
public interface ViewBinder<T> {
void bind(T target);
}

build.gradle文件

apply plugin: 'java-library'

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
sourceCompatibility = "1.8"
targetCompatibility = "1.8"

手写ButterKnife_ide_06

/**
* 描述:相当于一个监视者,监控源文件中的注解
*
* @author Create by zxy on 2018/5/10
*/

@AutoService(Processor.class) // 注册注解处理器
public class ButterKnifeProcessor extends AbstractProcessor {
//写文件的对象
private Filer mFiler;
private Elements mElementUtils;


@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mElementUtils = processingEnv.getElementUtils();
mFiler = processingEnv.getFiler();
}

/**
* 注解处理器支持的Java版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

/**
* 注解处理器支持的注解名称
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotationTypes = new HashSet<>();
annotationTypes.add(BindView.class.getCanonicalName());
return annotationTypes;
}

/**
* @param roundEnv
*
* package practice.lxn.cn.androidpractice.pojo; // PackageElement
* public class Book implements Parcelable{ // TypeElement
* private int bookId; // VariableElement
* private String bookName; // VariableElement
* public int getBookId() { // ExecutableElement
* return bookId;
* }
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 获取所有包含BindView注解的元素
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
// 需要对不同Activity中的注解进行分类,因为Set集合中包含了所有Activity中的注解
Map<String,List<VariableElement>> activityElementMap = new HashMap<>();
for (Element element : elements) {
VariableElement variableElement = (VariableElement) element;
//获取当前元素对应的Activity名称
String activityName = getActivityName(variableElement);
List<VariableElement> elementList = activityElementMap.get(activityName);
if (elementList == null) {
elementList = new ArrayList<>();
//将Activity名称和它对应的元素集合存放到一起
activityElementMap.put(activityName,elementList);
}
elementList.add(variableElement);
}

// 开始产生Java文件
for (String activityName : activityElementMap.keySet()) {
// 获取Activity对应的带注解的成员
List<VariableElement> elementList = activityElementMap.get(activityName);
// 获取包名
String packageName = getPackageName(elementList.get(0));
// 获取最后生成的文件的名称package practice.lxn.cn.testapp.MainActivity_ViewBinder;
// String viewBinderName = activityName + "_ViewBinder";
/* 需要生成文件的格式
package practice.lxn.cn.testapp;
import practice.lxn.cn.testapp.ViewBinder
public class MainActivity_ViewBinder implements ViewBinder<MainActivity> {
@Override
public void bind(MainActivity target) {
target.btn = (Button)target.findViewById(1231123423432);
}
}*/
//===========================================================================
/* Writer writer;
//MainActivity_ViewBinder
String simpleName = elementList.get(0).getEnclosingElement().getSimpleName().toString() + "_ViewBinder";
try {
// 方式一:通过原生的JavaFileObject拼接
JavaFileObject javaFileObject = mFiler.createSourceFile(viewBinderName);
writer = javaFileObject.openWriter();
writer.write("package " + packageName +";");
writer.write("\n");
writer.write("import " + packageName + ".ViewBinder;");
writer.write("\n");
writer.write("public class " + simpleName + " implements ViewBinder<" + activityName + "> {");
writer.write("\n");
writer.write("public void bind(" + activityName + " target) {");
writer.write("\n");
for (VariableElement element : elementList) {
String variableName = element.getSimpleName().toString();
TypeMirror typeMirror = element.asType();
int id = element.getAnnotation(BindView.class).value();
writer.write("target." + variableName + " = (" + typeMirror + ")target.findViewById(" + id + ");");
writer.write("\n");
writer.write("}");
writer.write("\n");
writer.write("}");

}
} catch (IOException e) {
e.printStackTrace();
}finally{
writer.close(); // 写完需要关闭
}*/
//方式二:通过JavaPoet提供的API
/* 需要生成文件的格式
package practice.lxn.cn.testapp;
import practice.lxn.cn.testapp.ViewBinder
public class MainActivity_ViewBinder implements ViewBinder<MainActivity> {
@Override
public void bind(MainActivity target) {
target.btn = (Button)target.findViewById(1231123423432);
}
}*/
String simpleName = elementList.get(0).getEnclosingElement().getSimpleName().toString();
ClassName viewBinderName = ClassName.get(ViewBinder.class.getPackage().getName(), ViewBinder.class.getSimpleName());
ClassName activityClassName = ClassName.bestGuess(simpleName);
TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(activityClassName + "_ViewBinder")
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ParameterizedTypeName.get(viewBinderName,activityClassName));
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.addParameter(TypeVariableName.get(activityName),"target")
.returns(TypeName.VOID);
for (VariableElement element : elementList) {
String variableName = element.getSimpleName().toString();
TypeMirror typeMirror = element.asType();
int id = element.getAnnotation(BindView.class).value();
methodBuilder.addStatement("target." + variableName +"= (" + typeMirror + ")" + "target.findViewById(" + id + ");");
}
MethodSpec bind = methodBuilder.build();
TypeSpec MainActivity_ViewBinder = typeBuilder.addMethod(bind).build();
JavaFile javaFile = JavaFile.builder(packageName,MainActivity_ViewBinder)
.build();
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}

/**
* 获取包名
*/
private String getPackageName(VariableElement variableElement) {
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
PackageElement packageElement = mElementUtils.getPackageOf(typeElement);
return packageElement.getQualifiedName().toString();
}

/**
* 获取Activity名称
*/
private String getActivityName(VariableElement variableElement) {
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
String packageName = getPackageName(variableElement);
// package practice.lxn.cn.testapp.MainActivity
return packageName + "." + typeElement.getSimpleName().toString();
}
}
apply plugin: 'java-library'

dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':butterknife-annotation')
implementation 'com.google.auto.service:auto-service:1.0-rc3'
// 动态生成Java源文件的API
implementation 'com.squareup:javapoet:1.9.0'
}

tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
sourceCompatibility = "1.8"
targetCompatibility = "1.8"

在ButterKnifeProcessor中,我们可以通过两种方式生成最终的源文件,通过原生的JavaFileObject或者JavaPoet,JavaPoet是square开源的一个项目,通过下面方式引入

 

implementation 'com.squareup:javapoet:1.9.0'

主项目中

手写ButterKnife_ide_07

最终生成的源文件

 

手写ButterKnife_源文件_08

 

package practice.lxn.cn.testapp;

import practice.lxn.cn.butterknife_annotation.ViewBinder;

public class MainActivity_ViewBinder implements ViewBinder<MainActivity> {
public void bind(practice.lxn.cn.testapp.MainActivity target) {
target.btn= (android.widget.Button)target.findViewById(2131427445);;
}
}