ButterKnife(8.4.0版本)原理分析
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) //註冊處理器,這樣在編譯時才會呼叫ButterKnifeProcessor public final class ButterKnifeProcessor extends AbstractProcessor { ... //支援的監聽器 private static final List<Class<? extends Annotation>> 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<String> 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集合,集合裡包含了需要處理的註解</span></code>型別 @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); for (Class<? extends Annotation> annotation : getSupportedAnnotations()) { types.add(annotation.getCanonicalName()); } return types; } //支援的所有註解型別 private Set<Class<? extends Annotation>> getSupportedAnnotations() { Set<Class<? extends Annotation>> 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<? extends TypeElement> elements, RoundEnvironment env) { //查詢所有的註解資訊,並形成BindingClass儲存到 map中 Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); //遍歷bindingMap生成類名_ViewBinding的java檔案 for (Map.Entry<TypeElement, BindingSet> 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<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>(); Set<TypeElement> 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<? extends Annotation> 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<Map.Entry<TypeElement, BindingSet.Builder>> entries = new ArrayDeque<>(builderMap.entrySet()); Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>(); while (!entries.isEmpty()) { Map.Entry<TypeElement, BindingSet.Builder> 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("<span style="color:#FF0000;">Generated code from Butter Knife. Do not modify!</span>")
.build(); }
bind:
如何才能生成的Java檔案呢?
答案是: ButterKnife.bind(this);方法。
unbind:
在新版的8.4.0中去除了 unbind方法。
<span style="font-size:18px;">ButterKnife.unbind //已經刪除了</span>
並採用了介面的形式,讓生成的類來實現釋放引用。 例如:
<span style="font-size:18px;"> public final class SimpleAdapter$ViewHolder_ViewBinding implements Unbinder { @UiThread public SimpleAdapter$ViewHolder_ViewBinding(SimpleAdapter.ViewHolder target, View ) { //... } //... @Override public void unbind() { //... } }</span>
那如何unbind呢?ButterKnife.bind(this)返回值是一個Unbinder引用。
所以可以這樣:
<span style="font-size:18px;"> 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 }</span>
綜上, ButterKnife是個好東西,節省開發時間而且不影響效能。 是Android開發居家必備的良品。