说道注解,我想大部分android开发的同学都会有所了解。以前我个人也是不喜欢使用注解框架,大多数注解框架都使用到了反射,反射在运行期间要创建代码然后编译,会降低性能。随着现在android设备配置各种提升,大部分手机还是有能力承受这个开销。反而使用注解框架确实能简化代码,提高开发效率。(配合butterknife插件,有如神助!^_^)。言归正传,还是来说说正事。
首先来看一下butterknife的使用
@Bind(R.id.title) TextView title;
@OnClick(R.id.title) void sayHello() {
Toast.makeText(this, "Hello!", LENGTH_SHORT).show();
}
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
ButterKnife.bind(this);
}
先看一下 ButterKnife.bind(this) 这个方法,最终会调用到bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder)
static void bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder) {
Class<?> targetClass = target.getClass();
try {
if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
viewBinder.bind(finder, target, source);
} catch (Exception e) {
throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
}
}
这段代码核心是findViewBinderForClass(targetClass)找到类相对应的viewbinder,然后调用他的bind方法。viewBinder是一个接口,再往下走,无法追踪了。(( ⊙ o ⊙ )该怎么办?全局搜索ViewBinder看哪个类实现它了,然并卵。。。)
骚年别急,县看看findViewBinderForClass(targetClass)这个方法
@NonNull
private static ViewBinder<Object> findViewBinderForClass(Class<?> cls) throws IllegalAccessException, InstantiationException {
ViewBinder<Object> viewBinder = BINDERS.get(cls);
if (viewBinder != null) {
if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
return viewBinder;
}
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return NOP_VIEW_BINDER;
}
try {
Class<?> viewBindingClass = Class.forName(clsName + "$$ViewBinder");
Log.i("butter knife", viewBindingClass.getSimpleName());
//noinspection unchecked
viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
} catch (ClassNotFoundException e) {
if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
viewBinder = findViewBinderForClass(cls.getSuperclass());
}
BINDERS.put(cls, viewBinder);
return viewBinder;
}
核心代码两行:
Class<?> viewBindingClass=Class.forName(clsName+"$$ViewBinder");
viewBinder = (ViewBinder<Object>)viewBindingClass.newInstance();
找到一个叫XXX$$ViewBinder的类,然后newInstance(),缓存到列表中。这里我们会想,我们并没有见过这样的类啊,又一次迷失了/(ㄒoㄒ)/~~。走到这里,如果对java注解问题不太了解的同学,可能就不知道如何走下去了。不太了解的同学推荐先看一篇文章[http://fyales.com/2015/08/21/Annotation/]。
butterknife里面就有一个ButterKnifeProcessor类,在编译时处理注解,生成相应的代码或者文件。
//初始化
@Override public synchronized void init(ProcessingEnvironment env) {
super.init(env);
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
}
//获取注解支持的类型
@Override public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(Bind.class.getCanonicalName());
for (Class<? extends Annotation> 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(Unbinder.class.getCanonicalName());
return types;
}
//核心执行过程
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingClass> 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里面几个方法Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env)
findAndParseTargets里面的代码太长,只贴一小部分看看
// Process each @Bind element.
for (Element element : env.getElementsAnnotatedWith(Bind.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseBind(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, Bind.class, e);
}
}
主要是区分不同的注解标签,做相应的处理。
private void parseBind(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<String> erasedTargetNames) {
// Verify common generated code restrictions.
if (isInaccessibleViaGeneratedCode(Bind.class, "fields", element)
|| isBindingInWrongPackage(Bind.class, element)) {
return;
}
TypeMirror elementType = element.asType();
//如果类型是数组
if (elementType.getKind() == TypeKind.ARRAY) {
parseBindMany(element, targetClassMap, erasedTargetNames);
} else if (LIST_TYPE.equals(doubleErasure(elementType))) {
//如果类型是列表
parseBindMany(element, targetClassMap, erasedTargetNames);
} else if (isSubtypeOfType(elementType, ITERABLE_TYPE)) {
error(element, "@%s must be a List or array. (%s.%s)", Bind.class.getSimpleName(),
((TypeElement) element.getEnclosingElement()).getQualifiedName(),
element.getSimpleName());
} else {
parseBindOne(element, targetClassMap, erasedTargetNames);
}
}
根据元素类型用不同的解析方式来处理,看一下
private void parseBindOne(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<String> erasedTargetNames) {
boolean hasError = false;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Verify that the target type extends from View.
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
Bind.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}
// Assemble information on the field.
int[] ids = element.getAnnotation(Bind.class).value();
if (ids.length != 1) {
error(element, "@%s for a view must only specify one ID. Found: %s. (%s.%s)",
Bind.class.getSimpleName(), Arrays.toString(ids), enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
if (hasError) {
return;
}
int id = ids[0];
BindingClass bindingClass = targetClassMap.get(enclosingElement);
if (bindingClass != null) {
ViewBindings viewBindings = bindingClass.getViewBinding(id);
if (viewBindings != null) {
Iterator<FieldViewBinding> iterator = viewBindings.getFieldBindings().iterator();
if (iterator.hasNext()) {
FieldViewBinding existingBinding = iterator.next();
error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
Bind.class.getSimpleName(), id, existingBinding.getName(),
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
}
} else {
bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
}
String name = element.getSimpleName().toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
FieldViewBinding binding = new FieldViewBinding(name, type, required);
bindingClass.addField(id, binding);
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement.toString());
}
核心代码:
int[] ids = element.getAnnotation(Bind.class).value();
拿到注解元素的值,也就是代码里面@Bind(R.id.title) ,括号里面的值。
然后将对应的信息,跟view进行绑定。最后所有的信息都到了BindingClass 类中
执行的时候 targetClassMap 没有找到对应的值的,就会跳转到getOrCreateTargetClass(targetClassMap, enclosingElement);`
private BindingClass getOrCreateTargetClass(Map<TypeElement, BindingClass> targetClassMap,
TypeElement enclosingElement) {
BindingClass bindingClass = targetClassMap.get(enclosingElement);
if (bindingClass == null) {
String targetType = enclosingElement.getQualifiedName().toString();
String classPackage = getPackageName(enclosingElement);
String className = getClassName(enclosingElement, classPackage) + BINDING_CLASS_SUFFIX;
bindingClass = new BindingClass(classPackage, className, targetType);
targetClassMap.put(enclosingElement, bindingClass);
}
return bindingClass;
}
这个方法的就是创建我们前面说到的XXX$$ViewBinder类,这里就知道这个类是怎么来的了。
解析完成之后 回到process方法里面调用
bindingClass.brewJava().writeTo(filer)
JavaFile brewJava() {
TypeSpec.Builder result = TypeSpec.classBuilder(className)
.addModifiers(PUBLIC)
.addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));
if (parentViewBinder != null) {
result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentViewBinder),
TypeVariableName.get("T")));
} else {
result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, TypeVariableName.get("T")));
}
if (hasUnbinder()) {
result.addType(createUnbinderClass());
}
result.addMethod(createBindMethod());
return JavaFile.builder(classPackage, result.build())
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
将bindingClass里面的信息,生成相应的java文件,同时编译的时候也会生成相应的class文件。这里就是整个java文件生成的过程。
反编译可以看到生成的文件
public class SimpleActivity$$ViewBinder
implements ViewBinder
{
public void bind(Finder paramFinder, SimpleActivity paramSimpleActivity, Object paramObject)
{
e locale = new e(paramSimpleActivity);
paramSimpleActivity.title = ((TextView)paramFinder.castView((View)paramFinder.findRequiredView(paramObject, 2130968576, "field 'title'"), 2130968576, "field 'title'"));
paramSimpleActivity.subtitle = ((TextView)paramFinder.castView((View)paramFinder.findRequiredView(paramObject, 2130968577, "field 'subtitle'"), 2130968577, "field 'subtitle'"));
View localView1 = (View)paramFinder.findRequiredView(paramObject, 2130968578, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
paramSimpleActivity.hello = ((Button)paramFinder.castView(localView1, 2130968578, "field 'hello'"));
locale.a = localView1;
localView1.setOnClickListener(new b(this, paramSimpleActivity));
localView1.setOnLongClickListener(new c(this, paramSimpleActivity));
View localView2 = (View)paramFinder.findRequiredView(paramObject, 2130968579, "field 'listOfThings' and method 'onItemClick'");
paramSimpleActivity.listOfThings = ((ListView)paramFinder.castView(localView2, 2130968579, "field 'listOfThings'"));
locale.b = localView2;
((AdapterView)localView2).setOnItemClickListener(new d(this, paramSimpleActivity));
paramSimpleActivity.footer = ((TextView)paramFinder.castView((View)paramFinder.findRequiredView(paramObject, 2130968580, "field 'footer'"), 2130968580, "field 'footer'"));
View[] arrayOfView = new View[3];
arrayOfView[0] = ((View)paramFinder.findRequiredView(paramObject, 2130968576, "field 'headerViews'"));
arrayOfView[1] = ((View)paramFinder.findRequiredView(paramObject, 2130968577, "field 'headerViews'"));
arrayOfView[2] = ((View)paramFinder.findRequiredView(paramObject, 2130968578, "field 'headerViews'"));
paramSimpleActivity.headerViews = Utils.listOf(arrayOfView);
paramSimpleActivity.unbinder = locale;
}
}
然后就可以回到开始说到的位置,找不到谁实现了ViewBinder,生成的SimpleActivity$$ViewBinder实现了ViewBinder接口。接下来的代码就好理解了,调用Finder中的findView,findRequiredView,castView之类,最后就是我们经常写到的findViewById。
很少写博客,文笔粗糙,也没有刻意去整理一下思路,就按照看代码的流程说了一下,有问题的地方,还望大家赐教。
PS:提到注解框架,估计大家也知道Dagger,效率确实比butterknife高,因为它大多都是生成的代码,反射用的很少,弄起来稍微麻烦一点。有兴趣的同学也可以看看Dagger
本文深入探讨Butterknife的工作原理,从使用示例入手,详细分析其内部机制,包括注解处理过程、Java文件生成及反编译结果,帮助读者理解Butterknife是如何简化UI绑定的。

1252

被折叠的 条评论
为什么被折叠?



