本文主要介绍Android之神JakeWharton的一个注解框架,听说现在面试官现在面试都会问知不知道JakeWharton,不知道的直接略过。废话不多说,我们接下来就简单介绍下ButterKnife的实现,希望对大家有所帮助,还有一点,以下故事纯属虚构,如有雷同,纯属巧合。
目录
- ButterKnife的起源和工作流程
- ButterKnife模块
- ButterKnife处理器模块
- 总结
ButterKnife的起源和工作流程
起源
1.我们平常一般都是在xml文件中写好布局,然后在Activity中通过下面代码查找和处理控件
public class MainActivity extends Activity implements View.OnClickListener {
private TextView mTextview;
private Button mButton;
private ImageView mImageview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mTextview = (TextView) findViewById(R.id.textview);
mButton = (Button) findViewById(R.id.button);
mImageview = (ImageView) findViewById(R.id.imageview);
mButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
Toast.makeText(this, "罗马不是一天建成的", Toast.LENGTH_SHORT).show();
break;
}
}
}
2.显然这些findViewById和setOnClickListener很是碍眼,于是有人想是不是可以把findViewById和setOnClickListener这些操作放外边儿呢,这样在MainActivity里边只需要声明变量和回调方法,我们就可以只关注我们需要关注的,于是乎就有了如下代码
public class MainActivity extends Activity {
TextView mTextview;
Button mButton;
ImageView mImageview;
private TestMainActivity_ViewBinding mBind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBind = TestButterKnife.bind(this);
}
public void onClick() {
Toast.makeText(this, "罗马不是一天建成的", Toast.LENGTH_SHORT).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
mBind.unbind();
}
}
public class TestButterKnife {
public static TestMainActivity_ViewBinding bind(Activity target) {
return new TestMainActivity_ViewBinding((MainActivity) target);
}
}
public class TestMainActivity_ViewBinding {
private MainActivity target;
public TestMainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
public TestMainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
target.mTextview = (TextView) source.findViewById(R.id.textview);
target.mImageview = (ImageView) source.findViewById(R.id.imageview);
target.mButton = (Button) source.findViewById(R.id.button);
target.mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
target.onClick();
}
});
}
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.mTextview = null;
target.mImageview = null;
target.mButton.setOnClickListener(null);
target.mButton = null;
}
}
3.看到上面的代码一定有很多人都想吐,我也想说me too,这样岂不是要码很多代码,还要建好多类,有点得不偿失的赶脚。这时候Android之神JakeWharton站出来了,发表了一席讲话,大体内容是说可以写个小框架,让用户在MainActivity里边只需要写点注解,其它的事情交给框架来做,也不需要建那么类,码很多代码,于是就有了如下设计
public class MainActivity extends Activity {
@BindView(R.id.textview)
TextView mTextview;
@BindView(R.id.button)
Button mButton;
@BindView(R.id.imageview)
ImageView mImageview;
private Unbinder mBind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBind = ButterKnife.bind(this);
}
@OnClick(R.id.button)
public void onClick() {
Toast.makeText(this, "罗马不是一天建成的", Toast.LENGTH_SHORT).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
mBind.unbind();
}
}
工作流程
看完上面的设计,肯定有人就会想,这样的话需要使用运行时注解实现吧,在ButterKnife的bind(this)方法执行的时候通过反射获取MainActivity中所有的带有注解的属性并且获得注解中的R.id.xxx值,最后还是通过反射调用Activity.findViewById()方法来获取View,并赋值给Activity中的某个属性。可是这样做不会有问题吗,我们都知道在Activity运行时大量使用反射会影响App的运行性能,造成卡顿等一系列问题。其实这种问题JakeWharton早就想到了,于是乎选用了编译时注解来实现,也就是Java Annotation Processing技术。下面开始简单介绍下ButterKnife的工作流程,该库主要使用编译时注解和javapoet在编译时生成辅助类,在运行时通过反射调用辅助类的构造和方法来实现。
1.编译时ButterKnifeProcessor类的process()方法处理过程如下
- 获取所有的标记了@BindView、@OnClick等注解的元素
- 定义一个key为类级别的元素,value为注解信息的Map集合,根据获取的元素可以拿到当前类级别的元素和注解标记的信息,然后put进去,这样Map里边就保存了所有要生成类和类中的信息
- 根据Map集合通过 javapoet 生成类似MainActivity_ViewBinding这样的很多类,包括包名,导包,类名,字段,构造,方法等信息都会生成,可见这个库的强大,这种库最难的应该就是导包了吧
2.运行时
- ButterKnife.bind(this);这个方法会反射创建并返回MainActivity_ViewBinding这样的实例,MainActivity_ViewBinding的构造就会执行,构造中会进行findViewById和setOnClickListener这些操作,这与起源中第2个模块里边的TestMainActivity_ViewBinding代码很相似
- 当然通过ButterKnife.bind(this);的返回值,你可以在onDestroy()中调用MainActivity_ViewBinding的unbind()方法来解除绑定释放资源
ButterKnife模块
这个模块主要有三个东东,一个是ButterKnife类,一个Utils类,一个是Unbinder接口。ButterKnife主要是用于运行时反射调用辅助类和返回辅助类实例,Utils类会被生成辅助类调用,Unbinder是生成的辅助类需要实现的接口,下面依次来介绍
ButterKnife类
1.mBind = ButterKnife.bind(this),会走如下代码
//UiThread表明这个方法必须在UI线程调用
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
2.接着调用createBinding(target, sourceView)
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;
}
//省去了try-catch,这里创建辅助类的实例并返回,注意这里会调用辅助类两个参数的构造方法
return constructor.newInstance(target, source);
}
3.下面我们看findBindingConstructorForClass(targetClass),这里采用了享元模式的设计,这种设计在很对框架中都会有,这里维护的是一个Map集合,集合中有这个key对应的value直接返回,没有才会构建,构建完后会放入Map中,这样大大减少了内存中对象的数量
//BINDINGS是一个key为Class,value为构造器的Map
static final Map, Constructor extends Unbinder>> BINDINGS = new LinkedHashMap<>();
private static Constructor extends Unbinder> findBindingConstructorForClass(Class> cls) {
//集合中通过key来获取构造器
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();
//类名不能以android.和java.开头
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
//可以看到生成的辅助类的类名是原始类名加上_ViewBinding,比如MainActivity_ViewBinding
Class> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//创建构造器
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);
}
//创建完后放入Map集合中
BINDINGS.put(cls, bindingCtor);
//返回构造器
return bindingCtor;
}
Utils类
这里简单介绍几个方法,后面生成的辅助类会调用这个类中方法
1.findRequiredViewAsType()
public static T findRequiredViewAsType(View source, @IdRes int id,String who,Class cls) {
//这里会调用下面2中的方法拿到一个没有强转的View
View view = findRequiredView(source, id, who);
//会调用3中的方法根据返回一个强制后的T,T表示@BindView所修饰控件类型,比如TextView、Button等
return castView(view, id, who, cls);
}
2.findRequiredView(View source, @IdRes int id, String who)
public static View findRequiredView(View source, @IdRes int id, String who) {
//我们期望看到的findViewById在这里
View view = source.findViewById(id);
if (view != null) {
return view;
}
//省略异常处理
}
3.castView(View view, @IdRes int id, String who, Class
public static T castView(View view, @IdRes int id, String who, Class cls) {
//省略try-catch,这里调用Class类的cast方法进行强转,我们稍微看下这个方法
return cls.cast(view);
}
//Class类中的cast方法
public T cast(Object obj) {
if (obj != null && !isInstance(obj))
throw new ClassCastException(cannotCastMsg(obj));
//这里强转了下然后返回
return (T) obj;
}
Unbinder接口
这个接口中就一个方法和一个空实现的成员变量
public interface Unbinder {
@UiThread void unbind();
//这个成员变量会在构造器为null的异常情况下直接返回,可以在上面ButterKnife类中的createBinding方法中看到
Unbinder EMPTY = new Unbinder() {
@Override public void unbind() {}
};
}
ButterKnife处理器模块
这个模块中我们只关注ButterKnifeProcessor这个类,该类继承了AbstractProcessor抽象类,下面我们看它重写的几个方法
1.首先我们看@AutoService注解,这是一个其他注解处理器中引入的注解。它是Google开发的,用来生成META-INF/services/javax.annotation.processing.Processor文件的,我们直接用即可
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
//省略
}
2.接着我们看init(ProcessingEnvironment env),注解处理器工具APT会调用这个方法并传入ProcessingEnvironment ,我理解的是该方法是提供给我们用来访问APT的环境
private int sdk = 1;
private static final String OPTION_SDK_INT = "butterknife.minSdk";
private static final String OPTION_DEBUGGABLE = "butterknife.debuggable";
private boolean debuggable = true;
private Elements elementUtils;
private Types typeUtils;
private Filer filer;
private Trees trees;
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
//env.getOptions()拿到的是传递给注释处理工具的Map选项
String sdk = env.getOptions().get(OPTION_SDK_INT);
if (sdk != null) {
//省去try-catch
this.sdk = Integer.parseInt(sdk);
}
debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
//获取元素实用工具
elementUtils = env.getElementUtils();
//获取类型实用工具
typeUtils = env.getTypeUtils();
//获取用来创建文件的Filter
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
}
}
3.我们看getSupportedSourceVersion(),这个方法用来指定你使用的Java版本
@Override
public SourceVersion getSupportedSourceVersion() {
//通常这样返回就行了,它会根据你的环境返回对应的版本枚举
return SourceVersion.latestSupported();
}
4.接下来就是getSupportedAnnotationTypes()方法,指定支持的注解
@Override
public Set getSupportedAnnotationTypes() {
Set types = new LinkedHashSet<>();
for (Class extends Annotation> annotation : getSupportedAnnotations()) {
//annotation.getCanonicalName()为获取全类名,即包名+类名
types.add(annotation.getCanonicalName());
}
return types;
}
private Set> getSupportedAnnotations() {
Set> 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;
}
5.最后来详细介绍下process(),这个方法相当于每个处理器的main()函数,在这里可以写你的扫描、处理注解以及生成Java文件的代码,因此这个方法是整个处理器模块的关键
@Override
public boolean process(Set extends TypeElement> elements, RoundEnvironment env) {
//根据env获取一个Map集合,其中key为类级别的元素,value为BindingSet
//BindingSet是一个自定义类,其中含有当前类中所有的注解信息
Map bindingMap = findAndParseTargets(env);
for (Map.Entry entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
//注意下面的两行代码就是生成辅助类啦,由于需要用到JavaPoet库,后面会详细讲解
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(env)是如何构建Map集合的
private Map findAndParseTargets(RoundEnvironment env) {
//key为类级别的元素,value为BindingSet的内部类Builder,看到Builder我们应该都很敏感,这是典型的
//建造者模式,这种模式特别常见,不管是在第三方库OkHttp中,还是在Android提供的对话框API中都可以看见
//它的身影,对于设计模式我们这里不再详细展开
Map builderMap = new LinkedHashMap<>();
//Set集合中保存了所有类级别的元素
Set erasedTargetNames = new LinkedHashSet<>();
scanForRClasses(env);
//由于定义的注解很多,这里省略了其他注解,只关注@BindView
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
try {
//解析@BindView注解到buildMap中
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
//builderMap是key为类级别的元素,value为BindingSet的内部类Builder,这里会转为bindingMap,即
//把value通过builder()方法构建成BindingSet,这就是典型的建造者模式
Deque> entries =
new ArrayDeque<>(builderMap.entrySet());
Map bindingMap = new LinkedHashMap<>();
while (!entries.isEmpty()) {
Map.Entry entry = entries.removeFirst();
TypeElement type = entry.getKey();
BindingSet.Builder builder = entry.getValue();
//注意这是在while循环里边,这个方法中会根据当前类级别的元素查找父元素,如果没有就返回null,如果
//有父元素就返回父元素,从这里可以看出来我们的注解是支持继承的
TypeElement parentType = findParentType(type, erasedTargetNames);
if (parentType == null) {
//父元素为null,直接put当前元素
bindingMap.put(type, builder.build());
} else {
//从bindingMap中先获取父元素的BindingSet
BindingSet parentBinding = bindingMap.get(parentType);
if (parentBinding != null) {
//父元素的BindingSet不为null,就需要为当前元素设置它的父元素
builder.setParent(parentBinding);
//然后put到bindingMap中
bindingMap.put(type, builder.build());
} else {
//父元素的BindingSet是null,就先把当前的Entry放到队尾,继续下一次循环
entries.addLast(entry);
}
}
}
//返回key为类级别的元素,value为BindingSet的集合
return bindingMap;
}
- 接下来我们看上面方法中的parseBindView()和findParentType(),我们先看findParentType()是如何根据当前类级别的元素查找父元素
private TypeElement findParentType(TypeElement typeElement, Set parents) {
TypeMirror type;
while (true) {
//根据当前类级别的元素查找父元素
type = typeElement.getSuperclass();
if (type.getKind() == TypeKind.NONE) {
//父元素为null,直接返回null
return null;
}
//parents集合中保存的是包含有我们自定义注解的类级别的元素,找到
//父元素并且父元素是个有效的即在集合里,就返回父元素
typeElement = (TypeElement) ((DeclaredType) type).asElement();
if (parents.contains(typeElement)) {
return typeElement;
}
}
}
- 再先看parseBindView()是如何解析@BindView注解到buildMap中的
private void parseBindView(Element element, Map builderMap,
Set erasedTargetNames) {
//获取当前元素的类级别的元素
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//生成的代码是不是可以访问,由于我们是通过target.mTextview来访问的,所以这个方法会校验@BindView修饰的
//字段是不是private、static的,@BindView是不是在类里边,如果在类里边那么类是不是private的
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
//下面几行代码主要是校验@BindView修饰的字段类型,必须是一个View或者是一个接口,否则就毫无意义
//获取@BindView修饰的字段类型,比如TextView类型
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的值,即R.id.xxx
int id = element.getAnnotation(BindView.class).value();
//享元模式的设计,根据当前元素的类级别的元素先从Map中获取BindingSet的内部类Builder
BindingSet.Builder builder = builderMap.get(enclosingElement);
//把int类型的id包装成了QualifiedId类
QualifiedId qualifiedId = elementToQualifiedId(element, id);
if (builder != null) {
//getId(qualifiedId)把QualifiedId类中的id又封装成了Id类,校验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为空,说明当前类还没有对应的value,需要new一个出来,并放到builderMap中
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
//@BindView修饰的字段的简单名称,即变量名mTextView
String name = simpleName.toString();
//@BindView修饰的字段的类型,比如TextView
TypeName type = TypeName.get(elementType);
//是否必须是字段
boolean required = isFieldRequired(element);
//调用BindingSet的内部类Builder中的addField方法封装解析的信息
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
//给所有含有自定义注解的类组成的Set集合中添加元素
erasedTargetNames.add(enclosingElement);
}
private BindingSet.Builder getOrCreateBindingBuilder(
Map builderMap, TypeElement enclosingElement) {
BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder == null) {
//builder为null,就构建一个BindingSet.Builder,放入集合中,并返回
builder = BindingSet.newBuilder(enclosingElement);
builderMap.put(enclosingElement, builder);
}
return builder;
}
static Builder newBuilder(TypeElement enclosingElement) {
TypeMirror typeMirror = enclosingElement.asType();
//类级别的元素是不是View的子类
boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
//类级别的元素是不是Activity的子类
boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
//类级别的元素是不是Dialog的子类
boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);
TypeName targetType = TypeName.get(typeMirror);
if (targetType instanceof ParameterizedTypeName) {
targetType = ((ParameterizedTypeName) targetType).rawType;
}
//根据当前类级别的元素获取包名
String packageName = getPackage(enclosingElement).getQualifiedName().toString();
//substring截取后一般是MainActivity这样的字符串,然后replace的话会没有效果,如果有.则用$代替
String className = enclosingElement.getQualifiedName().toString().substring(
packageName.length() + 1).replace('.', '$');
//这里可以看到要生成的包名,要生成的类名,格式如MainActivity_ViewBinding
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
//当前类是不是被final修饰
boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
//直接通过构造new一个Builder,第一个参数表示是不是一个class,第二个参数中含有要生成的类名和包名,其它都是布尔类型的参数
return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}
总结下,在parseBindView()方法中解析出来的信息都放在BindingSet的内部类Builder中,包含的信息有包名、原始类名、要生成的类名、R.id.xxx、是不是字段、变量名mTextView、变量类型TextView等信息。而BindingSet的内部类Builder又会通过调用build()方法构建一个BindSet实例并把这些信息传递过去,这就是典型的建造者模式,在BindSet中有了这些信息,下面我们就可以生成辅助类啦。
最后我们看binding.brewJava(sdk, debuggable)是如何生成辅助类的
1.回到最初的process()方法,我们讲完了如何构建Map集合,接着我们讲解如何生成辅助类
@Override
public boolean process(Set extends TypeElement> elements, RoundEnvironment env) {
//根据env获取一个Map集合,其中key为类级别的元素,value为BindingSet
//BindingSet是一个自定义类,其中含有当前类中所有的注解信息
Map bindingMap = findAndParseTargets(env);
for (Map.Entry entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
//注意下面的两行代码就是生成辅助类啦,由于需要用到JavaPoet库,后面会详细讲解
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;
}
2.需要用到javapoet,因此打开这个库我们稍微看下它是如何使用的
- 比如我们想要生成一个普通的java类,其中有main方法,方法中有syso
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
- 那我们就可以调用下面的方法来实现,可以看到又是建造者builder模式,真是无处不见啊,呼哈
//方法级别的,定义方法名
MethodSpec main = MethodSpec.methodBuilder("main")
//定义修饰符
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
//定义返回值
.returns(void.class)
//定义方法参数
.addParameter(String[].class, "args")
//定义方法中的syso
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
//类级别的,定义类名
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
//添加修饰符
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
//把上面定义的main添加到类中
.addMethod(main)
.build();
//JavaFile中保存了所有的信息,包名和上面定义的类信息,方法信息
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
3.简单看了javapoet的使用,继续看我们的代码,而我们知道生成辅助类的所有信息都在JavaFile中,我们看JavaFile是如何生成的,也就是上面的brewJava()
JavaFile javaFile = binding.brewJava(sdk, debuggable);
JavaFile brewJava(int sdk, boolean debuggable) {
//bindingClassName是ClassName类型,它是对包名和类名的封装,这里可以看到获取包名,类和类里边的信息是
//通过createType()方法获取的,也就是TypeSpec
return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
private TypeSpec createType(int sdk, boolean debuggable) {
//类级别的,定义类名,bindingClassName是ClassName类型,它是对包名和类名的封装
//上面获取的是包名,这里获取的是辅助类的类名
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC);
if (isFinal) {
result.addModifiers(FINAL);
}
//判断有没有父类,即生成的辅助类是不是也要从父类继承
if (parentBinding != null) {
//有父类,那么生成的辅助类也要从父类继承
result.superclass(parentBinding.bindingClassName);
} else {
//没有父类,就让辅助类实现Unbinder
result.addSuperinterface(UNBINDER);
}
//设置类似这样的字段private MainActivity target;
if (hasTargetField()) {
result.addField(targetTypeName, "target", PRIVATE);
}
//判断是不是View、Activity、Dialog的子类,来生成不同构造
if (isView) {
result.addMethod(createBindingConstructorForView());
} else if (isActivity) {
//这里我们只关注这个构造的生成,这个方法用来生成一个参数MainActivity target的构造
result.addMethod(createBindingConstructorForActivity());
} else if (isDialog) {
result.addMethod(createBindingConstructorForDialog());
}
if (!constructorNeedsView()) {
result.addMethod(createBindingViewDelegateConstructor());
}
//前面是只有一个参数MainActivity target构造
//这里是两个参数final MainActivity target, View source的构造
result.addMethod(createBindingConstructor(sdk, debuggable));
if (hasViewBindings() || parentBinding == null) {
//没有父类,那么我们前面实现了Unbinder接口,需要实现它里面的unbind方法
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}
4.上面方法中调用的三个方法我们还没看,其中有一个参数的构造、两个参数的构造、unbind()方法,接下来我们挨着看
- createBindingConstructorForActivity()生成一个参数MainActivity target的构造
private MethodSpec createBindingConstructorForActivity() {
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
//添加@UiThread注解
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC)
//添加构造中的参数,如MainActivity target
.addParameter(targetTypeName, "target");
if (constructorNeedsView()) {
builder.addStatement("this(target, target.getWindow().getDecorView())");
} else {
//这里是一个参数的构造不需要View,构造中添加this(target, target)
builder.addStatement("this(target, target)");
}
return builder.build();
}
- createBindingConstructor(sdk, debuggable)生成两个参数final MainActivity target, View source的构造
private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
//添加@UiThread注解
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC);
//判断是否有方法绑定,即是否有onClick()这样的回调方法
if (hasMethodBindings()) {
//由于是在匿名内部类方法里边调用构造函数的第一个参数,需要加上final修饰
constructor.addParameter(targetTypeName, "target", FINAL);
} else {
//没有方法绑定,只需要添加构造函数中的第一个参数MainActivity target
constructor.addParameter(targetTypeName, "target");
}
if (constructorNeedsView()) {
//构造函数的第二个参数就是View source
constructor.addParameter(VIEW, "source");
} else {
constructor.addParameter(CONTEXT, "context");
}
//有一些警告的话添加java提供的@SuppressWarnings注解
if (hasUnqualifiedResourceBindings()) {
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()) {
constructor.addStatement("super(target, source)");
} else if (constructorNeedsView()) {
constructor.addStatement("super(target, source.getContext())");
} else {
constructor.addStatement("super(target, context)");
}
constructor.addCode("\n");
}
if (hasTargetField()) {
//会执行这里,添加this.target = target,并且换行
constructor.addStatement("this.target = target");
constructor.addCode("\n");
}
//判断是不是有View绑定,肯定有@BindView
if (hasViewBindings()) {
if (hasViewLocal()) {
//在构造函数中定义一个局部变量View view,用来view.setOnClickListener()
constructor.addStatement("$T view", VIEW);
}
for (ViewBinding binding : viewBindings) {
//这是在for循环里面,这个方法会间接调用findViewById和强转的操作,接下来主要看这个方法
addViewBinding(constructor, binding, debuggable);
}
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();
}
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
if (binding.isSingleFieldBinding()) {
FieldViewBinding fieldBinding = binding.getFieldBinding();
//先写target.mTextview = ,这里可以看到是直接通过.调用的,因此我们写的字段不能是private的
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", fieldBinding.getName());
boolean requiresCast = requiresCast(fieldBinding.getType());
if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
builder.add("source.findViewById($L)", binding.getId().code);
} else {
//下面的代码会组合成一行代码,Utils中的findRequiredViewAsType()会进行find和强转操作,上面介绍过了
//Utils.findRequiredViewAsType(source, R.id.textview, "field 'mTextview'", TextView.class);
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;
}
- createBindingUnbindMethod(result)生成unbind()方法,即实现Unbinder接口中的unbind()方法
private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass) {
//定义unbind()方法,并且添加@Override注解
MethodSpec.Builder result = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override.class)
.addModifiers(PUBLIC);
if (!isFinal && parentBinding == null) {
//@CallSuper是android提供的注解,表示调用父类
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.");
//添加this.target = null
result.addStatement("$N = null", hasFieldBindings() ? "this.target" : "target");
//换行
result.addCode("\n");
for (ViewBinding binding : viewBindings) {
if (binding.getFieldBinding() != null) {
//循环添加类似这样的代码来解除绑定,target.mTextview = null;
result.addStatement("target.$L = null", binding.getFieldBinding().getName());
}
}
for (FieldCollectionViewBinding binding : collectionBindings) {
result.addStatement("target.$L = null", binding.name);
}
}
//有绑定方法,如设置监听事件回调方法才会走这个if
if (hasMethodBindings()) {
result.addCode("\n");
for (ViewBinding binding : viewBindings) {
addFieldAndUnbindStatement(bindingClass, result, binding);
}
}
if (parentBinding != null) {
//有父类才会换行,并且添加super.unbind()
result.addCode("\n");
result.addStatement("super.unbind()");
}
return result.build();
}
总结下,至此生成辅助类的代码就简单介绍完了,我们只是简单介绍了下@BindView,其它的注解都没介绍,有兴趣的读者可以自己去扒Butterknife源码,我们可以在\app\build\generated\source\apt\debug\目录下找到编译时生成的辅助类,下面我们列出一个简单的
package com.zeit.butter;
import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Utils;
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view2131427415;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
target.mTextview = Utils.findRequiredViewAsType(source, R.id.textview, "field 'mTextview'", TextView.class);
view = Utils.findRequiredView(source, R.id.button, "field 'mButton' and method 'onClick'");
target.mButton = Utils.castView(view, R.id.button, "field 'mButton'", Button.class);
view2131427415 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onClick();
}
});
target.mImageview = Utils.findRequiredViewAsType(source, R.id.imageview, "field 'mImageview'", ImageView.class);
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.mTextview = null;
target.mButton = null;
target.mImageview = null;
view2131427415.setOnClickListener(null);
view2131427415 = null;
}
}
总结
JakeWharton的Butterknife,俗称"黄油刀",可以帮助我们提高开发效率,尤其是配合AS插件的使用,下面推荐一个插件Android-Butterknife-Zelezny。而阅读源码可以让我们知其然知其所以然,也可以学到很多设计模式。建议阅读第三方库或Android提供的API等源码之前,先从设计模式学起,推荐《Android源码设计模式解析与实战》。