adapter = new SimpleAdapter(this);
listOfThings.setAdapter(adapter);
}
}
调用 gradle build 命令,我们在相应的目录下将可以看到生成类似这样的代码。
public class SimpleActivity_ViewBinding implements Unbinder {
protected T target;
private View view2130968578;
private View view2130968579;
@UiThread
public SimpleActivity_ViewBinding(final T target, View source) {
this.target = target;
View view;
target.title = Utils.findRequiredViewAsType(source, R.id.title, “field ‘title’”, TextView.class);
target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, “field ‘subtitle’”, TextView.class);
view = Utils.findRequiredView(source, R.id.hello, “field ‘hello’, method ‘sayHello’, and method ‘sayGetOffMe’”);
target.hello = Utils.castView(view, R.id.hello, “field ‘hello’”, Button.class);
view2130968578 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.sayHello();
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View p0) {
return target.sayGetOffMe();
}
});
view = Utils.findRequiredView(source, R.id.list_of_things, “field ‘listOfThings’ and method ‘onItemClick’”);
target.listOfThings = Utils.castView(view, R.id.list_of_things, “field ‘listOfThings’”, ListView.class);
view2130968579 = view;
((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
target.onItemClick(p2);
}
});
target.footer = Utils.findRequiredViewAsType(source, R.id.footer, “field ‘footer’”, TextView.class);
target.headerViews = Utils.listOf(
Utils.findRequiredView(source, R.id.title, “field ‘headerViews’”),
Utils.findRequiredView(source, R.id.subtitle, “field ‘headerViews’”),
Utils.findRequiredView(source, R.id.hello, “field ‘headerViews’”));
}
@Override
@CallSuper
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException(“Bindings already cleared.”);
target.title = null;
target.subtitle = null;
target.hello = null;
target.listOfThings = null;
target.footer = null;
target.headerViews = null;
view2130968578.setOnClickListener(null);
view2130968578.setOnLongClickListener(null);
view2130968578 = null;
((AdapterView<?>) view2130968579).setOnItemClickListener(null);
view2130968579 = null;
this.target = null;
}
}
总的来说,大概可以分为以下几步:
-
在编译的时候扫描注解,并做相应的处理,生成 java 代码,生成 Java 代码是调用 javapoet 库生成的。
-
当我们调用 ButterKnife.bind(this); 方法的时候,他会根据类的全限定类型,找到相应的代码,并执行。完成 findViewById 和 setOnClick ,setOnLongClick 等操作。
第一步:在编译的时候扫描注解,并做相应的处理,生成 java 代码。这一步,可以拆分为几个小步骤:
-
定义我们的注解,声明我们的注解是否保存到 java doc 中,可以作用于哪些区域(Filed ,Class等),以及是源码时注解,编译时注解还是运行时注解等)
-
继承 AbstractProcessor,表示支持哪些类型的注解,支持哪些版本,
-
重写 process 方法,处理相关的注解,存进 Map 集合中
-
根据扫描到的注解信息(即 Map 集合),调用 javapoet 库生成 Java 代码。
我们知道 ButterKnife 自定义很多的注解,有 BindArray,BindBitmap,BindColor,BindView 等,这里我们以 BindView 为例子讲解就 OK 了,其他的也是基本类似的,这里就不再讲解了。
//编译时注解
@Retention(CLASS)
//成员变量, (includes enum constants)
@Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
我们先来看一些基本方法:在 init 方法里面得到一些辅助工具类,这样有一个好处,确保工具类是单例的,因为 init 方法只会在初始化的时候调用。如果对注解还不了解的话,建议先阅读这一篇博客,Android 编译时注解 —— 语法详解
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
//辅助工具类
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
}
接着重写 getSupportedAnnotationTypes 方法,返回我们支持的注解类型。
@Override
public Set getSupportedAnnotationTypes() {
Set 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(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(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);
return annotations;
}
接下来来看我们的重点, process 方法。所做的工作大概就是拿到我们所有的注解信息,存进 map 集合,遍历 map 集合,做相应的 处理,生成 java 代码。
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
// 拿到所有的注解信息,TypeElement 作为 key,BindingSet 作为 value
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
// 遍历 map 里面的所有信息,并生成 java 代码
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, “Unable to write binding for type %s: %s”, typeElement, e
.getMessage());
}
}
return false;
}
这里我们进入 findAndParseTargets 方法,看里面到底是怎样将注解信息存进 map 集合的?
findAndParseTargets 方法里面 针对每一个自定义注解(BindArray,BindBitmap,BindColor,BindView) 等都做了处理,这里我们重点关注 @BindView 的处理即可。其他注解的处理思想也是一样的。
我们先来看一下 findAndParseTargets 方法的前半部分,遍历 env.getElementsAnnotatedWith(BindView.class) 集合,并调用 parseBindView 方法去转化。
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set erasedTargetNames = new LinkedHashSet<>();
scanForRClasses(env);
// Process each @BindView element.
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
// we don’t SuperficialValidation.validateElement(element)
// so that an unresolved View type can be generated by later processing rounds
try {
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
// 后半部分,待会再讲
}
可以看到牵绊部分的主要逻辑在 parseBindView 方法里面,主要做了以下几步操作:
- 判断被注解 @BindView 修饰的成员变量是不是合法的,private 或者 static 修饰的,则出错。
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 判断是否被注解在属性上,如果该属性是被 private 或者 static 修饰的,则出错
// 判断是否被注解在错误的包中,若包名以“android”或者“java”开头,则出错
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, “fields”, element)
|| isBindingInWrongPackage(BindView.class, element);
// Verify that the target type extends from View.
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及其子类或者Interface
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;
}
// Assemble information on the field.
int id = element.getAnnotation(BindView.class).value();
// 根据所在的类元素去查找 builder
BindingSet.Builder builder = builderMap.get(enclosingElement);
QualifiedId qualifiedId = elementToQualifiedId(element, id);
// 如果相应的 builder 已经存在
if (builder != null) {
// 验证 ID 是否已经被绑定
String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
// 被绑定了,出错,返回
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 {
// 如果没有相应的 builder,就需要重新生成,并别存放到 builderMap 中
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
parseBindView 方法分析完毕之后,我们在回过头来看一下 findAndParseTargets 方法的后半部分,主要做的工作是对 bindingMap 进行重排序。
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
// 省略前半部分
// Associate superclass binders with their subclass binders. This is a queue-based tree walk
// which starts at the roots (superclasses) and walks to the leafs (subclasses).
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();
TypeElement type = entry.getKey();
BindingSet.Builder builder = entry.getValue();
//获取 type 的父类的 TypeElement
TypeElement parentType = findParentType(type, erasedTargetNames);
// 为空,存进 map
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
// 获取 parentType 的 BindingSet
BindingSet parentBinding = bindingMap.get(parentType);
if (parentBinding != null) {
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;
}
到这里为止,我们已经分析完 ButterKnifeProcessor 是怎样处理注解的相关知识,并存进 map 集合中的,下面我们回到 process 方法,看一下是怎样生成 java 模板代码的。
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
// 拿到所有的注解信息,TypeElement 作为 key,BindingSet 作为 value
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
// 遍历 map 里面的所有信息,并生成 java 代码
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
// 生成 javaFile 对象
JavaFile javaFile = binding.brewJava(sdk);
try {
// 生成 java 模板代码
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, “Unable to write binding for type %s: %s”, typeElement, e
.getMessage());
}
}
return false;
}
生成代码的核心代码只有这几行
// 生成 javaFile 对象
JavaFile javaFile = binding.brewJava(sdk);
try {
// 生成 java 模板代码
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, “Unable to write binding for type %s: %s”, typeElement, e
.getMessage());
}
跟踪进去,发现是调用 square 公司开源的库 javapoet 开生成代码的。关于 javaPoet 的使用可以参考官网地址
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
总结
首先是感觉自己的基础还是不够吧,大厂好像都喜欢问这些底层原理。
另外一部分原因在于资料也还没有看完,一面时凭借那份资料考前突击恶补个几天居然也能轻松应对(在这里还是要感谢那份资料,真的牛),于是自我感觉良好,资料就没有怎么深究下去了。
之前的准备只涉及了Java、Android、计网、数据结构与算法这些方面,面对面试官对其他基础课程的考察显得捉襟见肘。
下一步还是要查漏补缺,进行针对性复习。
最后的最后,那套资料这次一定要全部看完,是真的太全面了,各个知识点都涵盖了,几乎我面试遇到的所有问题的知识点这里面都有!在这里也免费分享给大家,希望大家不要犯和我一样的错误呀!!!一定要看完!
获取方式:点击我的GitHub
[外链图片转存中…(img-ga5jhw48-1711383314571)]
[外链图片转存中…(img-YXQAVJ4Y-1711383314571)]
[外链图片转存中…(img-ajAoiNHH-1711383314571)]
[外链图片转存中…(img-U60zANVQ-1711383314572)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-bO81TmGA-1711383314572)]
总结
首先是感觉自己的基础还是不够吧,大厂好像都喜欢问这些底层原理。
另外一部分原因在于资料也还没有看完,一面时凭借那份资料考前突击恶补个几天居然也能轻松应对(在这里还是要感谢那份资料,真的牛),于是自我感觉良好,资料就没有怎么深究下去了。
之前的准备只涉及了Java、Android、计网、数据结构与算法这些方面,面对面试官对其他基础课程的考察显得捉襟见肘。
下一步还是要查漏补缺,进行针对性复习。
最后的最后,那套资料这次一定要全部看完,是真的太全面了,各个知识点都涵盖了,几乎我面试遇到的所有问题的知识点这里面都有!在这里也免费分享给大家,希望大家不要犯和我一样的错误呀!!!一定要看完!
[外链图片转存中…(img-DOcft82w-1711383314573)]
[外链图片转存中…(img-KqBoT16J-1711383314573)]
[外链图片转存中…(img-nuTOkq0u-1711383314573)]
获取方式:点击我的GitHub