参考:https://www.jianshu.com/p/39fc66aa3297
github:https://github.com/JakeWharton/butterknife
一、使用方法
首先在项目build.gradle
中添加如下依赖
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
...
}
dependencies {
...
// ButterKnife
implementation 'com.jakewharton:butterknife:10.2.3'
// 下载butterknife-compiler jar包
implementation 'com.jakewharton:butterknife-compiler:10.2.3'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
...
}
其中implementation 'com.jakewharton:butterknife-compiler:10.2.3'
是为了下载butterknife-compiler.jar
包,方便看ButterKnifeProcessor.java
源码。sync后项目中应该出现如下文件:
接下来新建一个Demo
public class ButterKnifeActivity extends AppCompatActivity {
...
@BindView(R.id.tv_loading)
TextView mTvLoading;
@BindView(R.id.rc_images)
RecyclerView mRecyclerView;
@OnClick(R.id.btn_scroll)
void scroll() {
startAnimator();
}
private Unbinder mUnBinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_butter_knife);
mUnBinder = ButterKnife.bind(this);
...
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy");
mUnBinder.unbind();
...
}
注意:
ButterKnife
标签注解的变量都不能用private
和static
修饰,原因后面解释。
二、原理分析
直觉告诉我们应该从ButterKnife.bind(this)
开始分析,那我们先看这部分源码:
@NonNull
@UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
getDecorView()
返回的是当前Activity的顶层View,是一个FrameLayout
,接着往下看:
@NonNull
@UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
// 获取要绑定target对应的Class
Class<?> targetClass = target.getClass();
// 根据Class获取target对象构造器,该构造器继承了Unbinder
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
// 根据构造器反射生成Unbinder子类实例
return constructor.newInstance(target, source);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
}
...
}
先通过target.getClass()
获取target对应的Class,再通过findBindingConstructorForClass(targetClass)
生成一个继承了Unbinder
的构造器Constructor
,最后通过反射生成这个Unbinder
的一个实例,也就是后面说到的xxx_ViewBinding.java
,至此ButterKnife.bind(this)
操作结束。我们重点看下findBindingConstructorForClass(targetClass)
是怎么生成Unbinder
的:
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
@Nullable
@CheckResult
@UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
// 现在缓存中查找是否已经存在该class,如果存在,则直接返回Constructor
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
return bindingCtor;
}
// 缓存中不存在,如果clsName是系统相关的,那么直接返回null
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")
|| clsName.startsWith("androidx.")) {
return null;
}
try {
// 通过ClassLoader加载类,此处涉及类加载模型(双亲委派模型)
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
} catch (ClassNotFoundException e) {
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
}
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
在生成Constructor<? extends Unbinder>
实例的过程中,先在LinkedHashMap
中查找是否已经有缓存,如果有则直接返回,否则通过类加载器加载,通过拼接类名“clsName_ViewBinding”加载,然后用bindingClass.getConstructor(cls, View.class)
通过xxx_ViewBinding.java
的构造方法生成xxx_ViewBinding.java
实例。
看到这里你是否有个疑问:
项目里我明明没有手动创建
xxx_ViewBinding.java
类,ClassLoader
怎么能加载到这个类呢?
相信大家在用ButterKnife
的时候,肯定注意到生成了很多xxx_ViewBinding.java
类,他们都在这里:
我们看看
xxx_ViewBinding.java
里面都做了啥:
public class ButterKnifeActivity_ViewBinding implements Unbinder {
private ButterKnifeActivity target;
private View view7f090071;
@UiThread
public ButterKnifeActivity_ViewBinding(ButterKnifeActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public ButterKnifeActivity_ViewBinding(final ButterKnifeActivity target, View source) {
this.target = target;
View view;
target.mTvLoading = Utils.findRequiredViewAsType(source, R.id.tv_loading, "field 'mTvLoading'", TextView.class);
target.mRecyclerView = Utils.findRequiredViewAsType(source, R.id.rc_images, "field 'mRecyclerView'", RecyclerView.class);
view = Utils.findRequiredView(source, R.id.btn_scroll, "method 'scroll'");
view7f090071 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.scroll();
}
});
}
@Override
@CallSuper
public void unbind() {
ButterKnifeActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.mTvLoading = null;
target.mRecyclerView = null;
view7f090071.setOnClickListener(null);
view7f090071 = null;
}
}
>>Utils.java
public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who, Class<T> cls) {
View view = findRequiredView(source, id, who);
return castView(view, id, who, cls);
}
public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
if (view != null) {
return view;
}
}
public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
try {
return cls.cast(view);
} catch (ClassCastException e) {
...
}
}
这个生成类中持有了我们自己Activity的引用,View的binding都转化成了findViewById(id)
。接下来我们看看这些xxx_ViewBinding.java
是怎么生成的。
三、注解处理器
首先,要生成这个类就要先得到这个类必须的基础信息,这就涉及到了annotationProcessor
技术,和 APT(Annotation Processing Tool)
技术类似,它是一种注解处理器,项目编译时对源代码进行扫描检测找出存活时间为RetentionPolicy.CLASS
的指定注解,然后对注解进行解析处理,进而得到要生成的类的必要信息,然后根据这些信息动态生成对应的 java 类,至于如何生成 java 类就涉及到了后边要说的 JavaPoet技术。
摘自:https://www.jianshu.com/p/39fc66aa3297
ButterKnife注解处理器的核心逻辑在ButterKnifeProcessor.java
中,该类有1500多行,以下截取基本框架部分:
// 该注解实现注解处理器的注册,注册到 javac 后,在项目编译时就能执行注解处理器了
@AutoService(Processor.class)
@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC)
@SuppressWarnings("NullAway") // TODO fix all these...
public final class ButterKnifeProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
String sdk = env.getOptions().get(OPTION_SDK_INT);
if (sdk != null) {
try {
this.sdk = Integer.parseInt(sdk);
} catch (NumberFormatException e) {
env.getMessager()
.printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '"
+ sdk
+ "'. Falling back to API 1 support.");
}
}
debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
try {
// Get original ProcessingEnvironment from Gradle-wrapped one or KAPT-wrapped one.
for (Field field : processingEnv.getClass().getDeclaredFields()) {
if (field.getName().equals("delegate") || field.getName().equals("processingEnv")) {
field.setAccessible(true);
ProcessingEnvironment javacEnv = (ProcessingEnvironment) field.get(processingEnv);
trees = Trees.instance(javacEnv);
break;
}
}
} catch (Throwable ignored2) {
}
}
}
@Override
public Set<String> getSupportedOptions() {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
builder.add(OPTION_SDK_INT, OPTION_DEBUGGABLE);
if (trees != null) {
builder.add(IncrementalAnnotationProcessorType.ISOLATING.getProcessorOption());
}
return builder.build();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
// 获取支持的注解列表
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(BindAnim.class);
annotations.add(BindArray.class);
annotations.add(BindBitmap.class);
annotations.add(BindBool.class);
annotations.add(BindColor.class);
annotations.add(BindDimen.class);
annotations.add(BindDrawable.class);
annotations.add(BindFloat.class);
annotations.add(BindFont.class);
annotations.add(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);
return annotations;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk, debuggable);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
}
ButterKnifeProcessor
继承AbstractProcessor
,并重写以下五个方法:
-
public synchronized void init(ProcessingEnvironment env);
完成minSdk的判断,初始化需要用到的辅助类:
Types: typeUtils
:用来对类型进行操作的工具类。
具体用法参考http://itmyhome.com/java-api/javax/lang/model/util/Types.html
Filer: filer
:用来生成java类文件。
具体用法参考http://itmyhome.com/java-api/javax/annotation/processing/Filer.html
Trees: trees
: -
public Set<String> getSupportedOptions();
获取注解处理器可处理的注解操作。 -
public Set<String> getSupportedAnnotationTypes();
该方法返回一个Set<String>
,代表ButterKnifeProcessor
要处理的注解类的名称集合,即 ButterKnife支持的注解。 -
public SourceVersion getSupportedSourceVersion();
获取当前系统支持的 java 版本。 -
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env);
处理target类信息收集和生成对应java类流程。重点看下这个方法:
第一步:收集注解信息
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
...
// env.getElementsAnnotatedWith(BindView.class)获取所有使用BindView注解的元素
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
try {
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
...
// 根据上面找到的所有TypeElement和BindingSet返回所有父类元素
Map<TypeElement, ClasspathBindingSet> classpathBindings =
findAllSupertypeBindings(builderMap, erasedTargetNames);
// 将builderMap中的数据添加到队列中
Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
new ArrayDeque<>(builderMap.entrySet());
Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
while (!entries.isEmpty()) {
// 出列
Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
// 拿到第一个Entry对象
TypeElement type = entry.getKey();
BindingSet.Builder builder = entry.getValue();
// 根据TypeElement查找父类元素
TypeElement parentType = findParentType(type, erasedTargetNames, classpathBindings.keySet());
// 父类为null,则直接将当前TypeElement放入bindingMap中
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
// 父类不为null,则根据父类TypeElemet在bindingMap查找(BindingSet implements BindingInformationProvider)
BindingInformationProvider parentBinding = bindingMap.get(parentType);
// 如果从bindingMap中拿到的BindingInformationProvider为null,则从classpathBindings中查找(ClasspathBindingSet implements BindingInformationProvider)
if (parentBinding == null) {
parentBinding = classpathBindings.get(parentType);
}
// 最终找到父类BindingInformationProvider
if (parentBinding != null) {
// 为当前BindingSet设置父Binding,并更新bindingMap对应位置元素
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
} else {
// Has a superclass binding but we haven't built it yet. Re-enqueue for later.
// 再次入列
entries.addLast(entry);
}
}
}
return bindingMap;
}
这里以@BindView
为例,其他逻辑类似。先解释一下上面代码用到的几个类:
-
TypeElement
:代表使用了 ButterKnife 的类,即 Activity、Fragment等。 -
BindingInformationProvider
:BindingSet内部接口,提供获取绑定类类名的方法和是否需要一个View用来绑定的判断:
interface BindingInformationProvider {
boolean constructorNeedsView();
ClassName getBindingClassName();
}
-
BindingSet
:实现BindingInformationProvider
,用来存储要生成类的基本信息以及注解元素的相关信息。 -
ClasspathBindingSet
:实现BindingInformationProvider
,并在构造方法中获取绑定类类名。
总结:整个findAndParseTargets()
方法流程是先将解析得到的所有注解和绑定信息存放在builderMap
和erasedTargetNames
中,中间通过子TypeElement
找到其父类绑定关系,并更新到子类中,最终将整合的绑定关系以TypeElement
为key,BindingSet
为value存到bindingMap
中并返回。
下面看看parseBindView()
是怎么解析注解的:
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
// 首先要注意,此时element是VariableElement类型的,即成员变量,比如前面例子中用到的mTvLoading
// enclosingElement是当前元素的父类元素,一般就是我们使用ButteKnife时定义的View类型成员变量所在的类,可以理解为之前例子中的ButterKnifeActivity
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 进行相关校验
// 1、isInaccessibleViaGeneratedCode(),先判断当前元素的是否是private或static类型,
// 再判断其父元素是否是一个类以及是否是private类型。
// 2、isBindingInWrongPackage(),是否在系统相关的类中使用了ButteKnife注解
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
// TypeMirror表示Java编程语言中的一种类型。类型包括基元类型,声明的类型(类和接口类型),数组类型,类型变量和空类型。
// 还表示了通配符类型参数,可执行文件的签名和返回类型,以及与包和关键字void相对应的伪类型。
TypeMirror elementType = element.asType();
// 如果当前元素是类的成员变量
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
Name qualifiedName = enclosingElement.getQualifiedName();
Name simpleName = element.getSimpleName();
// 判断当前元素是否是 View 的子类,或者是接口,不是的话抛出异常
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
if (elementType.getKind() == TypeKind.ERROR) {
note(element, "@%s field with unresolved type (%s) "
+ "must elsewhere be generated as a View or interface. (%s.%s)",
BindView.class.getSimpleName(), elementType, qualifiedName, simpleName);
} else {
error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
BindView.class.getSimpleName(), qualifiedName, simpleName);
hasError = true;
}
}
if (hasError) {
return;
}
// 获得元素使用BindView注解时设置的属性值,即 View 对应的xml中的id
int id = element.getAnnotation(BindView.class).value();
// 尝试获取父元素对应的BindingSet.Builder
BindingSet.Builder builder = builderMap.get(enclosingElement);
// resourceId,比如上面例子里的R.id.tv_loading
Id resourceId = elementToId(element, BindView.class, id);
if (builder != null) {
// 返回绑定元素的名称,比如mTvLoading
String existingBindingName = builder.findExistingBindingName(resourceId);
if (existingBindingName != null) {
error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
BindView.class.getSimpleName(), id, existingBindingName,
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
} else {
// 创建一个新的BindingSet.Builder并返回,并且以enclosingElement 为key添加到builderMap中
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
// 判断当前元素是否使用了Nullable注解
boolean required = isFieldRequired(element);
builder.addField(resourceId, new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
enclosingElement是当前元素的父类元素,一般就是我们使用ButteKnife时定义的View类型成员变量所在的类,可以理解为之前例子中的ButterKnifeActivity
第一步即校验绑定信息是否合法,包括绑定元素修饰符是否合法(非private,非static)和绑定类是否是系统相关类,另外对绑定元素类型(View,Interface)进行校验。
现在可以回到开头提到的问题,为什么绑定元素不能用private和static修饰:用private修饰的元素在生成的xxx_ViewBinding类中无法通过target类直接访问(当然可以用反射,但那样的话性能就会打折扣);static修饰的元素存在内存泄漏风险。
第二步是通过传入的参数进解析出元素id,类型等信息并更新到当前的BindingSet.Builder
中。下面看看getOrCreateBindingBuilder()
是怎么更新BindingSet.Builder
的:
private BindingSet.Builder getOrCreateBindingBuilder(
Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder == null) {
builder = BindingSet.newBuilder(enclosingElement);
builderMap.put(enclosingElement, builder);
}
return builder;
}
这段比较简单,接着看newBuilder()
里面做了什么:
static BindingSet.Builder newBuilder(TypeElement enclosingElement) {
TypeMirror typeMirror = enclosingElement.asType();
// 判断当前父类元素信息
boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);
TypeName targetType = TypeName.get(typeMirror);
if (targetType instanceof ParameterizedTypeName) {
targetType = ((ParameterizedTypeName) targetType).rawType;
}
// 获取父类元素的包名
ClassName bindingClassName = getBindingClassName(enclosingElement);
// 判断父类元素是不是final类型
boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
return new BindingSet.Builder(targetType, bindingClassName, enclosingElement, isFinal, isView, isActivity,
isDialog);
}
综上,所以BindingSet
主要保存了要生成的目标类的基本特征信息,以及类中使用了 ButterKnife 注解的元素(成员变量)的信息,这样一个BindingSet就和一个使用了ButterKnife 的类对应了起来。至此,要生成的目标类基本信息就收集就完成了。下一步就是生成java文件了。
四、JavaPoet
JavaPoet是一个生成.java文件的开源框架,ButterKnife就是JavaPoet来生成java文件的。回到process()
方法:
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
// 根据收集的绑定类信息生成JavaFile实例
JavaFile javaFile = binding.brewJava(sdk, debuggable);
try {
// 生成java文件,写入磁盘
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
JavaPoet如何生成java文件的过程这里就不分析了,感兴趣的朋友可以去研究一下。
五、总结
可以看出 ButterKnife 整个过程是在项目编译阶段完成的,主要用到了 annotationProcessor 和 JavaPoet 技术,使用时通过生成的辅助类完成操作,并不是在项目运行时通过注解加反射实现的,所以并不会影响项目运行时的性能,可能仅在项目编译时有略微的影响。
网友评论