Android进阶系列8-编译时注解框架ButterKnife浅析
来源:互联网 发布:淘宝服装店铺页头图片 编辑:程序博客网 时间:2024/05/22 01:58
本文的诞生离不开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<Object> viewBinder = findViewBinderForClass(targetClass);//根据target类对象获取viewBinder return viewBinder.bind(finder, target, source);//调用viewBinder的bind方法并返回执行结果 } catch (Exception e) { throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e); } }
疑问来了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<Object> findViewBinderForClass(Class<?> cls) throws IllegalAccessException, InstantiationException { ViewBinder<Object> viewBinder = BINDERS.get(cls);//检查缓存的列表里面有没有需要的类对象 if (viewBinder != null) { return viewBinder; } String clsName = cls.getName(); try { Class<?> viewBindingClass = Class.forName(clsName + "$$ViewBinder"); viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();//反射获取viewBindingClass实例 } catch (ClassNotFoundException e) { viewBinder = findViewBinderForClass(cls.getSuperclass()); } BINDERS.put(cls, viewBinder);//做一个viewBinder的缓存 return 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<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(BindView.class.getCanonicalName()); types.add(BindViews.class.getCanonicalName()); return types; }
方法负责指定处理的注解类型。而这些指定的类型最终在process方法中得到处理:
@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扫描、处理我们程序中的注解,然后调用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方法里面的语句就是我们之前需要手动敲的代码。
很惭愧,做了一点微小的贡献!
- Android进阶系列8-编译时注解框架ButterKnife浅析
- Android手动编写ButterKnife编译时注解框架
- Android butterknife注解框架
- Android ButterKnife注解框架
- android注解框架--ButterKnife
- Android 编译时注解-提升-butterknife
- Android 框架学习2:使用编译时注解简单实现类似 ButterKnife 的效果
- Android Butterknife框架 注解攻略
- Android butterknife框架 注解攻略
- Android butterknife框架 注解攻略
- Android Butterknife框架 注解攻略:
- android注解框架ButterKnife学习
- Android ButterKnife注解框架使用
- Android编译时注解框架系列2-Run Demo
- Android编译时注解框架系列2-Run Demo
- ButterKnife编译时注解探秘
- Android主流IOC框架浅析(注解反射,Annotations,ButterKnife的简单使用)
- Android编译时注解框架系列1-什么是编译时注解
- php中判断mysql数据库中某一个数据库是否存在
- OOP五大原则
- windows 下mysql更新用户password异常后导致的问题
- Python基础10 反过头来看看
- Laravel框架分析
- Android进阶系列8-编译时注解框架ButterKnife浅析
- 10039 Railroads
- equals 和== 的区别
- 文件上传和下载——文件上传(一)
- 蓝鸥Unity开发基础——抽象类
- MultiDex分包
- zookeeper的watcher相关
- py学习之路8---循环
- linux目录结构简析