一、原理简介
ButterKnife框架原理的是采用APT编译时技术,主要运用到注解+注解处理器的方式动态地为添加了BindView等注解的成员或方法生成类文件,开发者无需自己手写findViewById等等重复的代码,简化了开发者的工作量。
二、手写ButterKnife
想要完全理解ButterKnife底层的APT技术,手写实现ButterKnife可以帮助更好地吸收这种技术。
2.1准备工作
(1)创建Android工程,并且在此项目中新建一个java Module取名为annotataion,用于存放注解。
注意:必须新建java library而不能是Android lib,为什么只能新建java工程,不能是Android工程后面讲
了解了ButterKnife的原理后,今天我就带领大家手写一个简易的ButterKnife。因为ButterKnife使用到了编译时注解+反射,所以在手写项目之前,小伙伴们需要先熟悉下注解(Annotation)、反射和AbstractProcessor相关的知识。
话不多说,我们新建一个项目,命名为ButterknifeViewBind。这里先梳理下我们的逻辑,首先我们需要新建一个annotation库,专门用来存放我们的注解代码,其次还需要创建一个compiler库,用来放置编译的相关逻辑,最后我们还需要新建一个util工具库,提供注册的入口,供我们的业务使用。整个项目的目录结构如下:
工程结构
首先看下butterknife-annotations库,很简单,我们只声明了BindView这个注解类,相关代码如下:
package com.demo.butterknife_annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
好了,注解有了,现在我们需要做的就是针对我们特定的注解进行处理,这个时候我们的ButterKnifeProcessor就登场了:
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
private static final ClassName UI_THREAD =
ClassName.get("androidx.annotation", "UiThread");
Map<TypeElement, List<FieldBinding>> map = new HashMap<>();
private Types mTypeUtils;
private Elements mElementUtils;
private Filer mFiler;
private Messager mMessager;
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
//初始化我们需要的基础工具
mTypeUtils = env.getTypeUtils();
mElementUtils = env.getElementUtils();
mFiler = env.getFiler();
mMessager = env.getMessager();
}
@Override
public SourceVersion getSupportedSourceVersion() {
//支持的Java版本
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
//支持的注解
Set<String> annotations = new LinkedHashSet<>();
annotations.add(BindView.class.getCanonicalName());
return annotations;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//解析特定注解,生成Java文件
for (Element annotatedElement : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
if (annotatedElement.getKind() != ElementKind.FIELD) {
error(annotatedElement, "Only field can be annotated with @%s",
BindView.class.getSimpleName());
return true;
}
analysisAnnotated(annotatedElement);
}
for (Map.Entry<TypeElement, List<FieldBinding>> item :
map.entrySet()) {
TypeElement enclosingElement = item.getKey();
String packageName = ((PackageElement) enclosingElement.getEnclosingElement()).getQualifiedName().toString();
String className = enclosingElement.getSimpleName().toString();
ClassName originClassName = ClassName.get(packageName, className);
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
//System.out.println("packageName -------------------" + packageName);
//System.out.println("className -------------------" + className);
//System.out.println("originClassName -------------------" + originClassName.toString());
//System.out.println("bindingClassName -------------------" + bindingClassName.toString());
//构造方法中拼接代码
CodeBlock.Builder codeBuilder = CodeBlock.builder();
for (FieldBinding fieldBinding : item.getValue()) {
codeBuilder.addStatement("target.$L=($T)target.findViewById($L)", fieldBinding.getFieldName(),
fieldBinding.getType(), fieldBinding.getFieldId());
}
//创建构造方法
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addAnnotation(UI_THREAD)
.addParameter(originClassName, "target")
.addCode(codeBuilder.build());
//创建类
TypeSpec typeSpec = TypeSpec.classBuilder(bindingClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(constructor.build())
.build();
try {
//生成java文件
JavaFile.builder(packageName, typeSpec).build().writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
private void error(Element e, String msg, Object... args) {
mMessager.printMessage(
Diagnostic.Kind.ERROR,
String.format(msg, args),
e);
}
private void analysisAnnotated(Element element) {
TypeElement activityElement = (TypeElement) element.getEnclosingElement();
List<FieldBinding> activityBinding = map.get(activityElement);
if (activityBinding == null) {
activityBinding = new ArrayList<>();
map.put(activityElement, activityBinding);
}
// Verify that the target type extends from View.
TypeMirror elementType = element.asType();
TypeName type = TypeName.get(elementType);
int fieldId = element.getAnnotation(BindView.class).value();
String fieldName = element.getSimpleName().toString();
FieldBinding fieldBinding = new FieldBinding(fieldId, fieldName, type);
activityBinding.add(fieldBinding);
}
}
可以看到ButterKnifeProcessor使用了@AutoService注解,这个是用来做什么的呢?AutoService是Google开发的注解处理器,用来生成 META-INF/services/javax.annotation.processing.Processor 文件的,简化了我们创建注解处理器的流程。
AutoService的使用方式很简单,第一步,在我们butterknife_compiler库的buid.gradle文件中添加相关依赖:
implementation ‘com.google.auto.service:auto-service:1.0-rc3’
annotationProcessor ‘com.google.auto.service:auto-service:1.0-rc3’
接着在我们创建的注解处理器上添加@AutoService(Processor.class)注解就OK了,有没有很简单哈哈。
我们接着看下ButterKnifeProcessor的process方法,该方法在项目中的作用就是解析@ BindView注解,生成“ _ViewBinding.java”文件。在生成“ _ViewBinding.java”文件的过程中,使用到了JavaPoet开源库,what? 这又是什么?其实JavaPoet 就是用来辅助我们生成 Java 代码的一个 Java Library。同样我们需要在buid.gradle文件中添加依赖:
implementation ‘com.squareup:javapoet:1.8.0’
对JavaPoet 的API操作不太了解的童鞋可以查下相关博客,笔者这里也是一边查博客一边写的,一把辛酸泪啊~
好了到现在为止ButterKnifeProcessor也被我们搞定了,接下来就只剩下util工具库,提供注册的入口,这个就easy多了,只涉及到反射操作,我们首先新建butterknifeinject library,接着创建ButterknifeInject类,该类只有一个bind方法,代码如下:
package com.demo.butterknifeinject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public final class ButterknifeInject {
public static void bind(Object target) {
try {
String clsName = target.getClass().getName();
Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
try {
Constructor constructor = bindingClass.getConstructor(target.getClass());
try {
System.out.println("constructor-----------newInstance- before");
constructor.newInstance(target);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
大功告成,最后我们测试一下。在app的build.gradle文件中添加我们的library依赖:
annotationProcessor project(‘:butterknife-compiler’)
implementation project(‘:butterknife-annotations’)
implementation project(‘:butterknifeInject’)
ok,在MainActivity中我们现在就可以这么写啦:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_test)
TextView mTvTest;
@BindView(R.id.btn_test)
Button mBtnTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterknifeInject.bind(this);
initData();
}
private void initData(){
mBtnTest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTvTest.setText("哈哈哈哈哈嗝");
}
});
}
}
最后我们run一把,一切正常,不出意外的话我们的“ _ViewBinding.java”文件已经生成了,我们看下:
1577184824451.png