手写ButterKnife
Posted lxn_李小牛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手写ButterKnife相关的知识,希望对你有一定的参考价值。
前言
ButterKnife是一个依赖注入框架,8.0之前是通过反射的方式实现,具体实现可以参考这篇文章自定义注解,今天我们来看下8.0之后的编译时注解实现方式,编译时注解相比运行时注解效率高,是通过在编译时生成代码的方式来绑定控件。
结构
app:我们的android项目
butterknife-annotation:java library,定义注解
butterknife-compiler:java library,注解处理器
编译时报错
解决办法:
在annotation和compiler的gradle模块中,添加
tasks.withType(JavaCompile)
options.encoding = "UTF-8"
/**
* 描述:用于绑定变量
*
* @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"
/**
* 描述:相当于一个监视者,监控源文件中的注解
*
* @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'
主项目中
最终生成的源文件
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);;
以上是关于手写ButterKnife的主要内容,如果未能解决你的问题,请参考以下文章
动脑学院-手写ButterKnife框架(不包含自动生成代码)