本文共 9148 字,大约阅读时间需要 30 分钟。
ButterKnife是鼎鼎大名的JakeWharton写的注解框架, 将你从findViewById这样无聊的体力活解脱出来。 github地址: https://github.com/JakeWharton/butterknife , 已超过1万颗星了, 很屌。
JakeWharton是square公司的大咖, 是Piccaso(图片开源框架)、RxJava/RxAndroid(响应式编程)、OkHttp(Http通讯开源框架)的主要开发者。 不夸张的说, 对于一个做互联网app的码农来说, 如果不知道他就是少见识了。
ButterKnife的集成方式和使用方法已在github上描述, 就不多说了。 8.4.0版本做了一些优化:
1、 删除了ButterKnife类的unbind方法。 改为保存ButterKnife.bind函数返回的Unbinder引用, 在onDestroy函数里调用Unbinder的unbind方法。
2、如下图所示,在编译过程会生成*_ViewBinding.java, 如SimpleActivity_ViewBinding.java。
3、ButterKnife对性能没影响, 一些人说用了注解和反射影响性能, 这个锅ButterKnife不背。 增加Java方法数量和apk体积到是真的, 毕竟生成了_ViewBinding.java文件。
基础知识:APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定), APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。
生成*_ViewBinding.java文件的原理:在编译时期,javac会调用java注解处理器(APT)进行处理,通过自定义注解处理器来实现想要的功能, 例如ButterKnife在编译期间生成Java文件。
下面看ButterKnife的核心代码ButterKnifeProcessor.java, 在Android编译期间
@AutoService(Processor.class) //注册处理器,这样在编译时才会调用ButterKnifeProcessorpublic final class ButterKnifeProcessor extends AbstractProcessor { ... //支持的监听器 private static final List下面看Java文件的生成方法:> LISTENERS = Arrays.asList(// OnCheckedChanged.class, // OnClick.class, // OnEditorAction.class, // OnFocusChange.class, // OnItemClick.class, // OnItemLongClick.class, // OnItemSelected.class, // OnLongClick.class, // OnPageChange.class, // OnTextChanged.class, // OnTouch.class // ); //支持注解的资源类型 private static final List SUPPORTED_TYPES = Arrays.asList( "array", "attr", "bool", "color", "dimen", "drawable", "id", "integer", "string" ); ... //指定使用的Java版本 @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); ... } //返回stirng类型的set集合,集合里包含了需要处理的注解类型 @Override public Set getSupportedAnnotationTypes() { Set types = new LinkedHashSet<>(); for (Class annotation : getSupportedAnnotations()) { types.add(annotation.getCanonicalName()); } return types; } //支持的所有注解类型 private Set > getSupportedAnnotations() { Set > annotations = new LinkedHashSet<>(); 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(BindInt.class); annotations.add(BindString.class); annotations.add(BindView.class); annotations.add(BindViews.class); annotations.addAll(LISTENERS); return annotations; } //核心函数, 在这个函数里生成*_ViewBinding.java @Override public boolean process(Set elements, RoundEnvironment env) { //查找所有的注解信息,并形成BindingClass保存到 map中 Map bindingMap = findAndParseTargets(env); //遍历bindingMap生成类名_ViewBinding的java文件 for (Map.Entry entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); JavaFile javaFile = binding.brewJava(sdk); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return true; } //解析所有的注解 private Map findAndParseTargets(RoundEnvironment env) { Map builderMap = new LinkedHashMap<>(); Set erasedTargetNames = new LinkedHashSet<>(); scanForRClasses(env); // Process each @BindArray element. for (Element element : env.getElementsAnnotatedWith(BindArray.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceArray(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindArray.class, e); } } // Process each @BindBitmap element. for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceBitmap(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindBitmap.class, e); } } // Process each @BindBool element. for (Element element : env.getElementsAnnotatedWith(BindBool.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceBool(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindBool.class, e); } } // Process each @BindColor element. for (Element element : env.getElementsAnnotatedWith(BindColor.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceColor(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindColor.class, e); } } // Process each @BindDimen element. for (Element element : env.getElementsAnnotatedWith(BindDimen.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceDimen(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindDimen.class, e); } } // Process each @BindDrawable element. for (Element element : env.getElementsAnnotatedWith(BindDrawable.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceDrawable(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindDrawable.class, e); } } // Process each @BindFloat element. for (Element element : env.getElementsAnnotatedWith(BindFloat.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceFloat(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindFloat.class, e); } } // Process each @BindInt element. for (Element element : env.getElementsAnnotatedWith(BindInt.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceInt(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindInt.class, e); } } // Process each @BindString element. for (Element element : env.getElementsAnnotatedWith(BindString.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceString(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindString.class, e); } } // Process each @BindView element. for (Element element : env.getElementsAnnotatedWith(BindView.class)) { // we don't SuperficialValidation.validateElement(element) // so that an unresolved View type can be generated by later processing rounds try { parseBindView(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindView.class, e); } } // Process each @BindViews element. for (Element element : env.getElementsAnnotatedWith(BindViews.class)) { // we don't SuperficialValidation.validateElement(element) // so that an unresolved View type can be generated by later processing rounds try { parseBindViews(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindViews.class, e); } } // Process each annotation that corresponds to a listener. for (Class listener : LISTENERS) { findAndParseListener(env, listener, builderMap, erasedTargetNames); } // Associate superclass binders with their subclass binders. This is a queue-based tree walk // which starts at the roots (superclasses) and walks to the leafs (subclasses). 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(); TypeElement parentType = findParentType(type, erasedTargetNames); if (parentType == null) { bindingMap.put(type, builder.build()); } else { BindingSet parentBinding = bindingMap.get(parentType); if (parentBinding != null) { builder.setParent(parentBinding); bindingMap.put(type, builder.build()); } else { // Has a superclass binding but we haven't built it yet. Re-enqueue for later. entries.addLast(entry); } } } return bindingMap; } ...}
JavaFile brewJava() { //参数1:包名 //参数2:TypeSpec,这个可以生成class ,interface 等java文件 //注意addFileComment的参数,已经说明是生成的代码了 return JavaFile.builder(bindingClassName.packageName(), createBindingClass()) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); }
bind: 如何才能生成的Java文件呢?
答案是: ButterKnife.bind(this);方法。
unbind:
在新版的8.4.0中去除了 unbind方法。
ButterKnife.unbind //已经删除了
并采用了接口的形式,让生成的类来实现释放引用。 例如:
public final class SimpleAdapter$ViewHolder_ViewBinding implements Unbinder { @UiThread public SimpleAdapter$ViewHolder_ViewBinding(SimpleAdapter.ViewHolder target, View ) { //... } //... @Override public void unbind() { //... } }
那如何unbind呢?ButterKnife.bind(this)返回值是一个Unbinder引用。 所以可以这样:
Unbinder mUnbinder; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); mUnbinder=ButterKnife.bind(this); //保存引用 } @Override protected void onDestroy() { super.onDestroy(); mUnbinder.unbind(); //释放所有绑定的view }
综上, ButterKnife是个好东西,节省开发时间而且不影响性能。 是Android开发居家必备的良品。