本文的诞生离不开ButterKnife源码分析和Android编译时注解框架系列1-什么是编译时注解。
在《Think in Java》一书中,作者提到注解解释器的实现方案,除了最常见的利用反射机制构造外,还提到了注解处理工具APT,APT操作java的源文件,而不是编译后的类,APT会在处理完源文件后编译它们。
Android开发过程中,经常要对控件进行初始化以及监听操作等。其中的代码繁琐而又机械,而这正是注解的强项,减轻程序猿的码码负担。相应的注解工具很多,比如一些敏捷开发框架xUtils3中的注解以及ButterKnife等专职注解框架,大部分注解框架采用的反射机制实现,优点是代码量少,缺点是运行时解析比较耗时。一个极简的注解解析代码大概是这个样子
//创建一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ContentView {
int value();
}
//使用注解
@ContentView(R.layout.activity_home)
public class HomeActivity extends BaseActivity {
......
}
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//注解解析
for (Class c = this.getClass(); c != Context.class; c = c.getSuperclass()) {
ContentView annotation = (ContentView) c.getAnnotation(ContentView.class);
if (annotation != null) {
try {
this.setContentView(annotation.value());
} catch (RuntimeException e) {
e.printStackTrace();
}
return;
}
}
}
这个例子并没有什么实用性,很多情况没考虑,但是不妨碍我们理解使用反射去注解。
下面重点看下ButterKnife编译时注解的实现过程,我们从最初的调用一步步地深入。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
......
ButterKnife.bind(this);
......
}
查看ButterKnife的bind方法的实现
public static Unbinder bind(@NonNull Activity target{
return bind(target, target, Finder.ACTIVITY);
}
接着往下看
static Unbinder bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder) {
Class targetClass = target.getClass();//获取Activity的类class(针对展示的执行流程是如此逻辑)
try {
ViewBinder
疑问来了viewBinder指向的类对象是啥呢?在Android Studio的工程结构的build/intermediates/classes文件夹下,存放了一些带有$$ViewBinder后缀的类文件,打开一瞅,第一行就说:
// Generated code from Butter Knife. Do not modify!
public class MainActivity$$ViewBinder<T extends MainActivity>
implements ButterKnife.ViewBinder<T>
{
public void bind(ButterKnife.Finder paramFinder, final T paramT, Object paramObject)
{
View localView = (View)paramFinder.findRequiredView(paramObject, 2131492944, "field 'textView' and method 'onClick'");
paramT.textView = ((TextView)paramFinder.castView(localView, 2131492944, "field 'textView'"));
localView.setOnClickListener(new DebouncingOnClickListener()
{
public void doClick(View paramAnonymousView)
{
paramT.onClick(paramAnonymousView);
}
});
}
public void unbind(T paramT)
{
paramT.textView = null;
}
}
找到这样一个类,上面的疑惑也就可以解开了viewBinder指向的就是$$ViewBinder结尾的类,而调用的bind方法也定义在此类中。看到bind()中的代码,是不是分外亲切呢?和我们手撸的代码很像,也就是说通过自动生成代码的形式代替我们写findViewById这样繁琐的语句,Niubililty!
我们再看下findViewBinderForClass()方法是如何创建ViewBinder对象的。
@NonNull
private static ViewBinder
代码比较简单,都已经注释了,就不再解释。
以上,我们从ButterKnife.bind(this);开始一步步找到了bind()方法,或许大家对bind()方法的参数Finder还存疑,其实它就是个枚举
public enum Finder{
ACTIVITY {
@Override
protected View findView(Object source, int id) {
return ((Activity) source).findViewById(id);
}
@Override
public Context getContext(Object source){
return (Activity) source;
}
}
VIEW {
......
}
......
}
这些配置保证了我们在Activity中,在Fragment或者Adapter等中都可以使用ButterKnife成功找到控件或者其它操作。
接着考虑下bind()方法又是由谁生成的呢?一切都仰仗于ButterKnifeProcessor类,里面有两个重要的方法process ()方法和 getSupportedAnnotationTypes()方法。我们先看下getSupportedAnnotationTypes方法:
@Override public Set getSupportedAnnotationTypes() {
Set types = new LinkedHashSet<>();
types.add(Bind.class.getCanonicalName());
for (Class listener : LISTENERS){
types.add(listener.getCanonicalName());
}
types.add(BindArray.class.getCanonicalName());
types.add(BindBitmap.class.getCanonicalName());
types.add(BindBool.class.getCanonicalName());
types.add(BindColor.class.getCanonicalName());
types.add(BindDimen.class.getCanonicalName());
types.add(BindDrawable.class.getCanonicalName());
types.add(BindInt.class.getCanonicalName());
types.add(BindString.class.getCanonicalName());
types.add(BindView.class.getCanonicalName());
types.add(BindViews.class.getCanonicalName());
return types;
}
方法负责指定处理的注解类型。而这些指定的类型最终在process方法中得到处理:
@Override
public boolean process(Set elements, RoundEnvironment env) {
Map targetClassMap = findAndParseTargets(env);
for (Map.Entry entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
try {
bindingClass.brewJava().writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
e.getMessage());
}
}
return true;
}
可见,process扫描、处理我们程序中的注解,然后调用bindingClass.brewJava().writeTo(filer)产生$$ViewBinder类文件,我们看下brewJava怎么生成类文件法儿:
JavaFile brewJava() {
TypeSpec.Builder result = TypeSpec.classBuilder(className)
.addModifiers(PUBLIC)
.addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));
if (isFinal) {
result.addModifiers(Modifier.FINAL);
}
if (hasParentBinding()) {
result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentBinding.classFqcn),
TypeVariableName.get("T")));
} else {
result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, TypeVariableName.get("T")));
}
result.addMethod(createBindMethod());
if (hasUnbinder() && hasViewBindings()) {
// Create unbinding class.
result.addType(createUnbinderClass());
if (!isFinal) {
// Now we need to provide child classes to access and override unbinder implementations.
createUnbinderCreateUnbinderMethod(result);
}
}
return JavaFile.builder(classPackage, result.build())
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
可以看到类文件是根据上面的信息用字符串一行一行的拼接起来。
这样我们对ButterKnife的流程就有一个大概的认识了,ButterKnifeProcessor中指定所有用到的注解,扫描遍历程序中的注解,生成相应的Java文件,我们在自己的APP中初始相关代码后,调用生成的Java类中的bind方法,而这些bind方法里面的语句就是我们之前需要手动敲的代码。
很惭愧,做了一点微小的贡献!