博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ButterKnife(8.4.0版本)原理分析
阅读量:2350 次
发布时间:2019-05-10

本文共 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
> 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; } ...}
 
  下面看Java文件的生成方法:

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开发居家必备的良品。

你可能感兴趣的文章
Go语言基础入门--数组,切片,map
查看>>
Go语言基础入门--if,for,range,switch
查看>>
Go语言基础入门--函数,错误处理
查看>>
VIM 学习系列之基本命令,常用命令
查看>>
轻松搭建安全、轻量、极速、简约的博客Eiblog
查看>>
Golang包管理工具Glide,你值得拥有
查看>>
Glide命令,如何使用glide,glide.lock
查看>>
耗时整整一天,整理出的超详细清晰的vim,vimrc配置
查看>>
Git 学习笔记、相关命令、问答
查看>>
HTTPS 免费证书,免费 ssl 证书,FreeSSL.cn 申请多种免费证书
查看>>
SSH Config 那些你所知道和不知道的事
查看>>
10 分钟理解什么是 OAuth 2.0 协议
查看>>
docker volume 容器卷的那些事(一)
查看>>
docker volume 容器卷的那些事(二)
查看>>
首日报名爆满超300 向C++大师Lippman提问征集
查看>>
如何降低白噪声对网站用户体验的影响?
查看>>
TUP Masters第七期:C++大师Lippman论编程新范式Hugo
查看>>
[TUP第30期]直击移动应用开发难点 探讨跨平台最佳解决方案
查看>>
Avangate SaaS模式开启全球软件新营销之门
查看>>
如何降低白噪声对网站用户体验的影响?
查看>>