主目录见:Android高级进阶知识(这是总目录索引)
昨天我们已经分析完butterknife的注解处理器的收集信息部分,如果还没有看的话可以先看[butterknife源码分析(一)],今天我们就要来解析我们的生成代码部分了,相信今天讲解完大家应该能有一个整体的认识,同时因为今天会用到javapoet,如果不熟悉的可以参考这篇[JavaPoet的基本使用]。
一.目标
今天的目标就是通过学习自动生成代码的源码,来让我们对自动生成代码有个认识,然后能以后我们自己写的时候能顺手点,所以目标就是:
1.对javapoet自动生成代码有个认识;
2.对butterknife自动生成代码的过程有个了解;
3.自己能编写和生成自己想要的代码。
二.源码分析
昨天我们已经讲过一部分代码了,今天呢我们就接我们昨天的开始讲我们首先再看下我们的process方法:
1.生成代码部分
@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;
}
我们看到我们findAndParseTargets()方法已经讲解完成,这一步完成之后我们已经把我们的信息都放进bindingMap了,而且我们的信息都已经在BindingSet.Builder对象里面了。我们现在直接来看BindingSet里面的brewJava()方法吧:
JavaFile brewJava(int sdk, boolean debuggable) {
return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
我们看到我们最后builder方法传进去的bindingClassName.packageName()返回的就是要生成的源码类的包名,然后createType()方法返回的TypeSpec就是要创建的类。那么我们现在就看具体的createType()方法做了些什么:
private TypeSpec createType(int sdk, boolean debuggable) {
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());
} else if (isActivity) {
result.addMethod(createBindingConstructorForActivity());
} else if (isDialog) {
result.addMethod(createBindingConstructorForDialog());
}
if (!constructorNeedsView()) {
// Add a delegating constructor with a target type + view signature for reflective use.
result.addMethod(createBindingViewDelegateConstructor());
}
result.addMethod(createBindingConstructor(sdk, debuggable));
if (hasViewBindings() || parentBinding == null) {
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}
上面这段代码看感觉很长,其实逻辑是很顺很简单的,我们现在一步一步来讲解,首先我们看到以下部分:
//创建一个public的类名为bindingClassName.simpleName的类
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC);
if (isFinal) {
//判断类是不是final,如果是则在类前面添加final
result.addModifiers(FINAL);
}
//如果要生成的类有继承关系,则添加上父类
if (parentBinding != null) {
result.superclass(parentBinding.bindingClassName);
} else {
//没有的话就让类实现Unbinder接口
result.addSuperinterface(UNBINDER);
}
//如果类中有绑定变量和方法,则需要添加上target变量
if (hasTargetField()) {
result.addField(targetTypeName, "target", PRIVATE);
}
我们上面代码流程已经都注释了,我们看下最终生成的代码会长什么样:
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
}
我们看到我们上面方法只能生成这些信息。那么我们继续看代码:
if (isView) {
result.addMethod(createBindingConstructorForView());
} else if (isActivity) {
result.addMethod(createBindingConstructorForActivity());
} else if (isDialog) {
result.addMethod(createBindingConstructorForDialog());
}
我们看到这里代码有三个判断,但是里面添加的方法其实都是构造方法,我们挑一个来看,比如我们是要生成的源码是个Activity,我们就看方法createBindingConstructorForActivity:
private MethodSpec createBindingConstructorForActivity() {
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC)
.addParameter(targetTypeName, "target");
if (constructorNeedsView()) {
builder.addStatement("this(target, target.getWindow().getDecorView())");
} else {
builder.addStatement("this(target, target)");
}
return builder.build();
}
这段代码很简单,是个生成构造方法的代码,我们来看对应的生成完的代码:
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
是不是很明了,addAnnotation方法就是添加上UiThread注解,然后addModifiers就是添加这个方法为public的,addParameter就是添加上方法参数,最后添加上面一段代码。接着我们来看下面这句代码:
result.addMethod(createBindingConstructor(sdk, debuggable));
这段代码也是创建的构造函数,但是不同的是这个构造函数里面会初始化很多东西,我们就来看createBindingConstructor方法干了些啥:
private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
//添加public和注解@UiThread
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC);
//有使用注解@OnClick这些,说明要在click事件用target调用方法,所以target要加上final
if (hasMethodBindings()) {
constructor.addParameter(targetTypeName, "target", FINAL);
} else {
constructor.addParameter(targetTypeName, "target");
}
//如果有使用@BindView等说明要查找视图view.findViewById,所以要传进去一个view,不然直接传进去上下文即可
if (constructorNeedsView()) {
constructor.addParameter(VIEW, "source");
} else {
constructor.addParameter(CONTEXT, "context");
}
//添加SuppressWarnings注解,当我们id直接使用的是int值则需要添加这个来告诉程序,我们这里是一个忽略这个警告
if (hasUnqualifiedResourceBindings()) {
// Aapt can change IDs out from underneath us, just suppress since all will work at runtime.
constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", "$S", "ResourceType")
.build());
}
//如果是点击事件则需要添加上这个注解来说明有可能与点击事件冲突
if (hasOnTouchMethodBindings()) {
constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
.addMember("value", "$S", "ClickableViewAccessibility")
.build());
}
if (parentBinding != null) {
//如果有父类的情况下则执行这里面代码
if (parentBinding.constructorNeedsView()) {
//父类构造函数需要有view的情况下则添加下面代码
constructor.addStatement("super(target, source)");
} else if (constructorNeedsView()) {
//如果有父类,然后又需要有绑定控件则添加这个
constructor.addStatement("super(target, source.getContext())");
} else {
//否则直接就添加这句
constructor.addStatement("super(target, context)");
}
constructor.addCode("\n");
}
//有target的情况下则赋值一下target
if (hasTargetField()) {
constructor.addStatement("this.target = target");
constructor.addCode("\n");
}
if (hasViewBindings()) {
if (hasViewLocal()) {
//添加这个View主要是为了暂时存储所有的控件的
// Local variable in which all views will be temporarily stored.
constructor.addStatement("$T view", VIEW);
}
//遍历添加查找控件的代码,这里等会会详细说
for (ViewBinding binding : viewBindings) {
addViewBinding(constructor, binding, debuggable);
}
//如果是@BindingView里面的id有多个则需要走这个例如:@BindViews({ R.id.title, R.id.subtitle, R.id.hello })这个等会也会详细说
for (FieldCollectionViewBinding binding : collectionBindings) {
constructor.addStatement("$L", binding.render(debuggable));
}
if (!resourceBindings.isEmpty()) {
constructor.addCode("\n");
}
}
//如果要获取资源的绑定则需要走以下代码获取
if (!resourceBindings.isEmpty()) {
if (constructorNeedsView()) {
constructor.addStatement("$T context = source.getContext()", CONTEXT);
}
if (hasResourceBindingsNeedingResource(sdk)) {
constructor.addStatement("$T res = context.getResources()", RESOURCES);
}
//同样如果是多个也需要走这个代码
for (ResourceBinding binding : resourceBindings) {
constructor.addStatement("$L", binding.render(sdk));
}
}
return constructor.build();
}
这里面的代码我们注释写的已经非常清楚了,但是我们还是要来说说addViewBinding方法干了什么:
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
if (binding.isSingleFieldBinding()) {
// Optimize the common case where there's a single binding directly to a field.
//这句代码是添加类似 target.title 的句子
FieldViewBinding fieldBinding = binding.getFieldBinding();
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", fieldBinding.getName());
//判断是否需要类型转化
boolean requiresCast = requiresCast(fieldBinding.getType());
if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
if (requiresCast) {
//如果需要类型转化则添加
builder.add("($T) ", fieldBinding.getType());
}
builder.add("source.findViewById($L)", binding.getId().code);
} else {
//下面这段添加的主要如 Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class)或者没有AsType
builder.add("$T.find", UTILS);
builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
if (requiresCast) {
builder.add("AsType");
}
builder.add("(source, $L", binding.getId().code);
if (fieldBinding.isRequired() || requiresCast) {
builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
}
if (requiresCast) {
builder.add(", $T.class", fieldBinding.getRawType());
}
builder.add(")");
}
result.addStatement("$L", builder.build());
return;
}
List<MemberViewBinding> requiredBindings = binding.getRequiredBindings();
//我们这里都会判断是不是debug模式,如果不是debug模式就直接findViewById了,如果是且id存在则调用
//另外一个例如:Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'")
if (!debuggable || requiredBindings.isEmpty()) {
result.addStatement("view = source.findViewById($L)", binding.getId().code);
} else if (!binding.isBoundToRoot()) {
result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS,
binding.getId().code, asHumanDescription(requiredBindings));
}
addFieldBinding(result, binding, debuggable);
addMethodBindings(result, binding, debuggable);
}
上面的代码如果直接看我们肯定会晕的,我们必须先来看看我们最终生成的有可能是什么:
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);
那么我们再看上面的话就简单非常多了,同时代码有两个方法我们没有说明清楚,addFieldBinding和addMethodBindings,我们来看看这两个是干嘛的吧,我们先来看addFieldBinding方法:
private void addFieldBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
FieldViewBinding fieldBinding = binding.getFieldBinding();
if (fieldBinding != null) {
if (requiresCast(fieldBinding.getType())) {
if (debuggable) {
result.addStatement("target.$L = $T.castView(view, $L, $S, $T.class)",
fieldBinding.getName(), UTILS, binding.getId().code,
asHumanDescription(singletonList(fieldBinding)), fieldBinding.getRawType());
} else {
result.addStatement("target.$L = ($T) view", fieldBinding.getName(),
fieldBinding.getType());
}
} else {
result.addStatement("target.$L = view", fieldBinding.getName());
}
}
}
这段代码如果对着生成的源码来看其实也是很简单的....不会吧,什么都说很简单!!!不过确实不难呀,javapoet就是为了简化编程写的嘛,我们来看生成的源码(因为我们现在debug模式,所以生成的代码如下):
target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
可以看到我们如果需要类型转化的时候有时debug模式的情况下就会生成这段代码,是不是很简单。然后我们看下我们最后一个比较重要的方法addMethodBindings,这个方法会比较复杂,因为这个方法是添加事件监听的,首先我们来复习下方法的注解@OnClick:
看完这个注解,有个大致的印象,我们就继续我们代码:
private void addMethodBindings(MethodSpec.Builder result, ViewBinding binding,
boolean debuggable) {
//我们先是获取到ListenerClass为key,以及对应value为Map(key为ListenerMethod,value为对应的绑定方法)的Map集合
Map<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>> classMethodBindings =
binding.getMethodBindings();
if (classMethodBindings.isEmpty()) {
return;
}
// We only need to emit the null check if there are zero required bindings.
//是否需要判断null
boolean needsNullChecked = binding.getRequiredBindings().isEmpty();
if (needsNullChecked) {
result.beginControlFlow("if (view != null)");
}
// Add the view reference to the binding.
String fieldName = "viewSource";
String bindName = "source";
//判断如果id不为空则执行,例如: view2131427446 = view;
if (!binding.isBoundToRoot()) {
fieldName = "view" + binding.getId().value;
bindName = "view";
}
result.addStatement("$L = $N", fieldName, bindName);
for (Map.Entry<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>> e
: classMethodBindings.entrySet()) {
//遍历map,然后获取出key和value
ListenerClass listener = e.getKey();
Map<ListenerMethod, Set<MethodViewBinding>> methodBindings = e.getValue();
//创建一个匿名内部类,父类为listener 中的type例如:DebouncingOnClickListener
TypeSpec.Builder callback = TypeSpec.anonymousClassBuilder("")
.superclass(ClassName.bestGuess(listener.type()));
//遍历所有的需要重写的方法,例如:
// @Override
// public void doClick(View p0) {
//
// }
for (ListenerMethod method : getListenerMethods(listener)) {
MethodSpec.Builder callbackMethod = MethodSpec.methodBuilder(method.name())
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.returns(bestGuess(method.returnType()));
String[] parameterTypes = method.parameters();
for (int i = 0, count = parameterTypes.length; i < count; i++) {
callbackMethod.addParameter(bestGuess(parameterTypes[i]), "p" + i);
}
boolean hasReturnType = !"void".equals(method.returnType());
CodeBlock.Builder builder = CodeBlock.builder();
if (hasReturnType) {
builder.add("return ");
}
if (methodBindings.containsKey(method)) {
//调用我们判定方法:例如 target.sayHello();或者 target.onItemClick(p2);或者return target.sayGetOffMe();
for (MethodViewBinding methodBinding : methodBindings.get(method)) {
builder.add("target.$L(", methodBinding.getName());
List<Parameter> parameters = methodBinding.getParameters();
String[] listenerParameters = method.parameters();
for (int i = 0, count = parameters.size(); i < count; i++) {
if (i > 0) {
builder.add(", ");
}
Parameter parameter = parameters.get(i);
int listenerPosition = parameter.getListenerPosition();
if (parameter.requiresCast(listenerParameters[listenerPosition])) {
if (debuggable) {
builder.add("$T.castParam(p$L, $S, $L, $S, $L, $T.class)", UTILS,
listenerPosition, method.name(), listenerPosition, methodBinding.getName(), i,
parameter.getType());
} else {
builder.add("($T) p$L", parameter.getType(), listenerPosition);
}
} else {
builder.add("p$L", listenerPosition);
}
}
builder.add(");\n");
}
} else if (hasReturnType) {
builder.add("$L;\n", method.defaultReturn());
}
callbackMethod.addCode(builder.build());
callback.addMethod(callbackMethod.build());
}
//有的监听可能需要删除,就是去掉这个监听,则需要返回这个监听给一个变量
boolean requiresRemoval = listener.remover().length() != 0;
String listenerField = null;
if (requiresRemoval) {
TypeName listenerClassName = bestGuess(listener.type());
listenerField = fieldName + ((ClassName) listenerClassName).simpleName();
result.addStatement("$L = $L", listenerField, callback.build());
}
//如果类型是view的类型的话,则执行下面例如:
// ((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
// @Override
// public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
// target.onItemClick(p2);
// }
// });
if (!VIEW_TYPE.equals(listener.targetType())) {
result.addStatement("(($T) $N).$L($L)", bestGuess(listener.targetType()), bindName,
listener.setter(), requiresRemoval ? listenerField : callback.build());
} else {
result.addStatement("$N.$L($L)", bindName, listener.setter(),
requiresRemoval ? listenerField : callback.build());
}
}
if (needsNullChecked) {
//如果上面有判断null的话则这边要加上接受}符号
result.endControlFlow();
}
}
好啦,我们这段代码的注释也写的非常的详细了,我们如果还不知道的话,我们就举出生成代码的几个例子如下:
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View p0) {
return target.sayGetOffMe();
}
});
((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
target.onItemClick(p2);
}
});
到这里我们构造方法的内容已经讲完,但是ButterKnife后面又增加了unbind这个方法,我们这里也来看看unbind生成的方法createBindingUnbindMethod做了什么呢?
private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass) {
//创建unbind方法
MethodSpec.Builder result = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override.class)
.addModifiers(PUBLIC);
if (!isFinal && parentBinding == null) {
result.addAnnotation(CALL_SUPER);
}
if (hasTargetField()) {
if (hasFieldBindings()) {
//添加如下: MainActivity target = this.target;
result.addStatement("$T target = this.target", targetTypeName);
}
//if (target == null) throw new IllegalStateException("Bindings already cleared.");
result.addStatement("if (target == null) throw new $T($S)", IllegalStateException.class,
"Bindings already cleared.");
//接着把target置为空 this.target = null;
result.addStatement("$N = null", hasFieldBindings() ? "this.target" : "target");
result.addCode("\n");
//将所有的变量置为空例如target.title = null;
for (ViewBinding binding : viewBindings) {
if (binding.getFieldBinding() != null) {
result.addStatement("target.$L = null", binding.getFieldBinding().getName());
}
}
//这句跟上面类似
for (FieldCollectionViewBinding binding : collectionBindings) {
result.addStatement("target.$L = null", binding.name);
}
}
//这里的方法解绑就是如下:view2131427446.setOnClickListener(null);或者 ((AdapterView<?>) view2131427447).setOnItemClickListener(null);我就不详细说明了
if (hasMethodBindings()) {
result.addCode("\n");
for (ViewBinding binding : viewBindings) {
addFieldAndUnbindStatement(bindingClass, result, binding);
}
}
//如果父类不为空则调用super.unbind()
if (parentBinding != null) {
result.addCode("\n");
result.addStatement("super.unbind()");
}
return result.build();
}
到这里我们已经生成代码的部分都说明完毕了,其实就是利用javapoet来生成,然后判断之前收集的信息部分符不符合条件来生成相应的代码,逻辑还是比较清晰的。
2.代码的调用
上面我们已经生成了我们想要的代码了,现在当然是要使用我们生成的代码呀,不然还有啥用,我们知道我们使用Butterknife的时候,我们会调用bind()方法和unbind()方法,那么我们知道,我们的方法调用肯定是在这两个方法里面,首先我们来看下bind()方法:
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
@NonNull @UiThread
public static Unbinder bind(@NonNull View target) {
return createBinding(target, target);
}
@NonNull @UiThread
public static Unbinder bind(@NonNull Dialog target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
.........等等
这里面的bind方法有好几个,在不同情况下会调用不同的方法,那么我们直接用第一个方法bind(@NonNull Activity target)来看看,我们看到这个方法里面调用了createBinding()方法:
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
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方法里面:
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null) {
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");
//noinspection unchecked
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
} 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;
}
我们看到这里面的代码很简单,就是得到对应的绑定类中的构造方法,然后返回回去。这样我们就可以利用反射调用这个构造方法了。然后最后我们再调用unbind即可。unbind的代码跟这个类似,我们就不赘述了,已经讲的非常清楚了,我们就在这里介绍这个框架的解析吧。
总结:今天这个代码主要是利用的javapoet来生成源码,需要的信息主要是上一篇收集信息里面的代码,其实也不是非常难,主要是要有耐心看下去,butterknife的代码确实也不少,还有其他的lint优化和R2的生成源码部分如果有需要我们会拿出来说说,祝大家有享受到源码的魅力。