上篇文章《Java编译时注解处理器(APT)详解》中学习了Java APT技术在Android中的使用,并且我们知道,当前Android开发中常用的许多框架都使用了APT技术,并且ButterKnife就是利用APT来实现的。那么本篇内容我们就来探究一下ButterKnife的实现原理。
一、ButterKnife的bind过程
当然,在探究源码,还是首先应该了解如何使用,我们以ButterKnife的BindView为例。首先在Activity的onCreate方法中通过ButterKnife.bind()方法绑定,然后为TextView添加BindView注解即可拿到TextView的实例。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.zhpan.app;
public class MainActivity extends AppCompatActivity { @BindView(R.id.text_view) TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); } }
|
ButterKnife是怎么做到通过注解获取TextView的实例的呢?我们就从bind方法开始吧,点击ButterKnife的bind方法,其代码如下:
1 2 3 4 5
| public static Unbinder bind(@NonNull Activity target) { View sourceView = target.getWindow().getDecorView(); return createBinding(target, sourceView); }
|
可以看到在bind方法中通过Activity拿到了DecorView(至于不懂DecorView是什么的同学可以自行Google)。然后调用了createBinding方法,并传入Activity对象和DecorView两个参数。我们追踪createBinding的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| private static Unbinder createBinding(@NonNull Object target, @NonNull View source) { Class<?> targetClass = target.getClass(); Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) { return Unbinder.EMPTY; }
try { return constructor.newInstance(target, source); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InstantiationException e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } if (cause instanceof Error) { throw (Error) cause; } throw new RuntimeException("Unable to create binding instance.", cause); } }
|
上述代码中通过findBindingConstructorForClass()方法得到了某个类的构造方法,并在接下来的代码中通过反射 constructor.newInstance(target, source)实例化了这个类,我们点进findBindingConstructorForClass()方法看这里做了什么。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) { Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls); if (bindingCtor != null) { 如果已存在直接return if (debug) Log.d(TAG, "HIT: Cached in binding map."); return bindingCtor; } String clsName = cls.getName(); if (clsName.startsWith("android.") || clsName.startsWith("java.")) { if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); return null; } try { Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding"); bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class); } catch (ClassNotFoundException e) { if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName()); bindingCtor = findBindingConstructorForClass(cls.getSuperclass()); } catch (NoSuchMethodException e) { throw new RuntimeException("Unable to find binding constructor for " + clsName, e); } BINDINGS.put(cls, bindingCtor); return bindingCtor; }
|
直接看第7行,通过 cls.getName()得到了bind方法中参数的名字,由于我们是再MainActivity中调用的bind方法,因此,这里拿到的名字就是“com.zhpan.app.MainActivity”,接下来再第13行中通过ClassLoader拿到了一个名字是”clsName”+”_ViewBinding”的类,也就是名字为MainActivity_ViewBinding的类。我们在项目里搜索一下,发现果真能找到这个类,其代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.zhpan.app;
public class MainActivity_ViewBinding implements Unbinder { private MainActivity target;
@UiThread public MainActivity_ViewBinding(MainActivity target) { this(target, target.getWindow().getDecorView()); }
@UiThread public MainActivity_ViewBinding(MainActivity target, View source) { this.target = target;
target.mTextView = Utils.findRequiredViewAsType(source, R.id.text_view, "field 'mTextView'", TextView.class); }
@Override @CallSuper public void unbind() { MainActivity target = this.target; if (target == null) throw new IllegalStateException("Bindings already cleare![在这里插入图片描述](https://img-blog.csdnimg.cn/20190825162306549.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTIxNTcz,size_16,color_FFFFFF,t_70)d."); this.target = null;
target.mTextView = null; } }
|
在这个类的构造方法中我们可以看到
1
| target.mTextView = Utils.findRequiredViewAsType(source, R.id.text_view, "field 'mTextView'", TextView.class);
|
通过这句代码给Activity中的mTextView赋了值,到这里也就不奇怪为什通过一个BindView的注解就得到了mTextView的对象了。而MainActivity_ViewBinding这个类是从哪里来的呢?想必看过上篇文章同学应该都知道了,就是通过APT在代码编译期间自动生成的。其实后面的代码其实已经没有必要去看了,无非就是通过AbstractProcessor来处理注解,然后根据注解自动生成所需代码的。但是写文章要有始有终,也本着负责任的态度,还是深入ButtereKnife内部一探究竟。
二、探究ButterKnife的注解处理器ButterKnifeProcessor
现在我们来看一下ButterKnife的代码的模块结构:
看到butterknife-annotations和butterknife-compiler很眼熟?和上篇文章我们自己写的代码结构是一样的,butterknife-annotations模块下存放的是butterknife的所有注解,butterknife-compiler是用来处理butterknife-annotations注解的。所以们直接看butterknife-compiler模块下的ButterKnifeProcessor类。
1.ButterKnifeProcessor的getSupportedAnnotationTypes()方法
我们知道ButterKnife除了BindView注解之外还有许多其它注解,比如常用的BindColor注解、OnClick注解等。这些注解的名字都会被添加到getSupportedAnnotationTypes方法中的Set集合里。来看getSupportedAnnotationTypes的源码:
1 2 3 4 5 6 7 8
| @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); // 遍历ButterKnife中的所有注解,并将注解名字添加到Set集合 for (Class<? extends Annotation> annotation : getSupportedAnnotations()) { types.add(annotation.getCanonicalName()); } return types; }
|
2.ButterKnifeProcessor的process方法
我们知道process方法是整个注解处理其的核心方法,对注解的处理以及代码的生成都是在这个方法里边实现的。那么接下来,就一步步看ButterKnife是怎么处理注解和生成代码的。
**(1)解析RoundEnviroment**
在process方法的第一行我们看到通过findAndParseTargets方法得到了一个 Map<TypeElement, BindingSet>集合,代码如下:
1 2 3 4 5 6 7 8
| public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); ... return false; }
|
而在findAndParseTargets(env)是对一系列注解的处理,这里我们仅以处理BindView为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>(); Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
for (Element element : env.getElementsAnnotatedWith(BindView.class)) { try { parseBindView(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindView.class, e); } }
...
|
第9行在parseBindView方法中对BindView注解进行了一系列的校验,通过校验后解析BindView的数据,比如view的Id等信息封装到了BindingSet.Builder中,并最终将封装好的BindingSet.Builder放入builderMap 中。由于parseBindView方法的代码比较多,这里就不再贴出了,有兴趣的可以直接下载源码查看。
接下来得到builderMap的集合之后,由于findAndParseTargets方法返回值是Map<TypeElement, BindingSet>,因此接下来又对builderMap集合进行了遍历并最终存储到了 Map<TypeElement, BindingSet>中。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { ... 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(); TypeElement parentType = findParentType(type, erasedTargetNames); if (parentType == null) { bindingMap.put(type, builder.build()); } else { BindingSet parentBinding = bindingMap.get(parentType); if (parentBinding != null) { builder.setParent(parentBinding); bindingMap.put(type, builder.build()); } else { entries.addLast(entry); } } } } }
|
好了,接下来,继续到process方法中,在拿到bindingMap 之后遍历bindingMap ,并调用BindingSet的brewJava来生成Java文件,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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, useAndroidX); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } }
return false; }
|
3.JavaPoet生成代码
BindingSet 的brewJava方法中同样是使用了JavaPoet来生成代码的,无非就是拼接类,构造方法、方法,这些感觉真的没什么说的了。
1 2 3 4 5 6
| JavaFile brewJava(int sdk, boolean debuggable, boolean useAndroidX) { TypeSpec bindingConfiguration = createType(sdk, debuggable, useAndroidX); return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| private TypeSpec createType(int sdk, boolean debuggable, boolean useAndroidX) { TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName()) .addModifiers(PUBLIC); if (isFinal) { result.addModifiers(FINAL); }
if (parentBinding != null) { result.superclass(parentBinding.bindingClassName); } else { result.addSuperinterface(UNBINDER); }
if (hasTargetField()) { result.addField(targetTypeName, "target", PRIVATE); }
if (isView) { result.addMethod(createBindingConstructorForView(useAndroidX)); } else if (isActivity) { result.addMethod(createBindingConstructorForActivity(useAndroidX)); } else if (isDialog) { result.addMethod(createBindingConstructorForDialog(useAndroidX)); } if (!constructorNeedsView()) { result.addMethod(createBindingViewDelegateConstructor(useAndroidX)); } result.addMethod(createBindingConstructor(sdk, debuggable, useAndroidX));
if (hasViewBindings() || parentBinding == null) { result.addMethod(createBindingUnbindMethod(result, useAndroidX)); }
return result.build(); }
|
关于ButterKnife的源码就说这么多吧。总结一下,其实就是根据注解使用APT及JavaPoet在项目编译期间自动生成代码,并达到为注解元素赋值或者添加监听的目的。在阅读Butterknife时候不一定要把每一句代码都搞懂,能做到掌握其核心原理便可。