編輯:關於Android編程
話說在android這座大山裡,有一座廟(方塊公司-square),廟裡住著一個神-jake(我是這麼叫的嘻嘻)。
不要小看這個小jake,這個神可是為android應用開發們提供了強有力的幫助。比如流行的開源庫okhttp,leakcanary ,retrofit,butterknife 等等都是出於他之手。小弟佩服的不要不要的…,可以說是為android的應用開發效率和耦合性提高了一個台階啊。
其它的大神我也是佩服的不要不要的…嘻嘻
這一系列的文章是對ButterKnife的源碼進行分析的,涉及的細節比較多,但也比較廣。一次看不懂no 問題,慢慢來嘛,嘻嘻
如有不對的地方,還望指導。
最後會給出自己實現的一個demo,主要是原理和思想哦!!!
我們這裡對ButterKnife的最新版本8.4.0進行分析。
我們先down下來看下代碼的結構,可以看到代碼結構分的還是很好的。
可以看到大神的代碼很風騷的,很清晰啊。
這裡重點分析butterknife-compiler及butterknife<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMyBpZD0="原理圖">原理圖
你可以看完了再回來看這個圖,會更明白。
這裡不再給出。不過會在原代碼分析的時候給出一些注意的地方。
我們拿官方的demo-SimpleActivity
編譯完後最後生成的文件為:SimpleActivity_ViewBinding
路徑在:
內容:
// Generated code from Butter Knife. Do not modify! package com.example.butterknife.library; import android.support.annotation.CallSuper; import android.support.annotation.UiThread; import android.view.View; import android.widget.AdapterView; import android.widget.Button; import android.widget.ListView; import android.widget.TextView; import butterknife.Unbinder; import butterknife.internal.DebouncingOnClickListener; import butterknife.internal.Utils; import java.lang.IllegalStateException; import java.lang.Override; public class SimpleActivity_ViewBindingimplements Unbinder { protected T target; private View view2130968578; private View view2130968579; @UiThread public SimpleActivity_ViewBinding(final T target, View source) { this.target = target; View view; target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class); target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class); view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'"); target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class); view2130968578 = view; view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.sayHello(); } }); view.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View p0) { return target.sayGetOffMe(); } }); view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'"); target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class); view2130968579 = view; ((AdapterView) view).setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView p0, View p1, int p2, long p3) { target.onItemClick(p2); } }); target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class); target.headerViews = Utils.listOf( Utils.findRequiredView(source, R.id.title, "field 'headerViews'"), Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"), Utils.findRequiredView(source, R.id.hello, "field 'headerViews'")); } @Override @CallSuper public void unbind() { T target = this.target; if (target == null) throw new IllegalStateException("Bindings already cleared."); target.title = null; target.subtitle = null; target.hello = null; target.listOfThings = null; target.footer = null; target.headerViews = null; view2130968578.setOnClickListener(null); view2130968578.setOnLongClickListener(null); view2130968578 = null; ((AdapterView) view2130968579).setOnItemClickListener(null); view2130968579 = null; this.target = null; } }
我們看到了我們熟悉的代碼,雖然比較亂(因為是生成的),
可以看出 在構造中findview 在unbind中進行置null處理,讓告訴gc在合適的機會回收占用的內存 ;但是這是後面真正生成代碼我們看不到的,沒關系 嘻嘻。
在java代碼的編譯時期,javac 會調用java注解處理器來進行處理。因此我們可以定義自己的注解處理器來干一些事情。一個特定注解的處理器以 java 源代碼(或者已編譯的字節碼)作為輸入,然後生成一些文件(通常是.java文件)作為輸出。因此我們可以在用戶已有的代碼上添加一些方法,來幫我們做一些有用的事情。這些生成的 java 文件跟其他手動編寫的 java 源代碼一樣,將會被 javac 編譯。(個人參考及個人理解)
在java中定義自己的處理器都是繼承自AbstractProcessor
前3個方法都試固定寫法,主要是process方法。
public class MyProcessor extends AbstractProcessor { //用來指定你使用的 java 版本。通常你應該返回 SourceVersion.latestSupported() @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } //會被處理器調用,可以在這裡獲取Filer,Elements,Messager等輔助類,後面會解釋 @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); } //這個方法返回stirng類型的set集合,集合裡包含了你需要處理的注解 @Override public SetgetSupportedAnnotationTypes() { Set annotataions = new LinkedHashSet (); annotataions.add("com.example.MyAnnotation"); return annotataions; } //核心方法,這個一般的流程就是先掃描查找注解,再生成 java 文件 //這2個步驟設計的知識點細節很多。 @Override public boolean process(Set annoations, RoundEnvironment env) { return false; } }
要像jvm調用你寫的處理器,你必須先注冊,讓他知道。怎麼讓它知道呢,其實很簡單,google 為我們提供了一個庫,簡單的一個注解就可以。
首先是依賴
compile 'com.google.auto.service:auto-service:1.0-rc2'
@AutoService(Processor.class) public class BindViewProcessor extends AbstractProcessor { //...省略非關鍵代碼 }
分析之前呢先要有寫基本的概念
可以看到AutoService注解
@AutoService(Processor.class) public final class ButterKnifeProcessor extends AbstractProcessor { //... }
private Filer mFiler; //文件相關的輔助類 private Elements mElementUtils; //元素相關的輔助類 private Messager mMessager; //日志相關的輔助類 @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); elementUtils = env.getElementUtils(); typeUtils = env.getTypeUtils(); filer = env.getFiler(); try { trees = Trees.instance(processingEnv); } catch (IllegalArgumentException ignored) { } }
@Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); }
@Override public boolean process(Set elements, RoundEnvironment env) { //1.查找所有的注解信息,並形成BindingClass(是什麼 後面會講) 保存到 map中 MaptargetClassMap = findAndParseTargets(env); //2.遍歷步驟1的map 的生成.java文件也就是上文的 類名_ViewBinding 的java文件 for (Map.Entry entry : targetClassMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingClass bindingClass = entry.getValue(); JavaFile javaFile = bindingClass.brewJava(); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return true; }
咦,很明顯,2步走啊。是的沒錯, 滿滿套路啊!!大家都回農村吧,嘻嘻
下面我們仔細走一下該方法流程。
private MapfindAndParseTargets(RoundEnvironment env) { Map targetClassMap = 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, targetClassMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindArray.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, targetClassMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindView.class, e); } } //.... // Process each annotation that corresponds to a listener. for (Class listener : LISTENERS) { findAndParseListener(env, listener, targetClassMap, erasedTargetNames); } // Try to find a parent binder for each. for (Map.Entry entry : targetClassMap.entrySet()) { TypeElement parentType = findParentType(entry.getKey(), erasedTargetNames); if (parentType != null) { BindingClass bindingClass = entry.getValue(); BindingClass parentBindingClass = targetClassMap.get(parentType); bindingClass.setParent(parentBindingClass); } } return targetClassMap; }
首先我們先看一下參數 RoundEnvironment 這個是什麼呢?個人理解是注解框架裡的一個工具什麼工具呢?一個可以在處理器處理該處理器 用來查詢注解信息的工具,當然包含你在getSupportedAnnotationTypes注冊的注解。
接下來呢?
創建了一個LinkedHashMap保證了先後的順序就是先注解的先生成java文件,其實也沒有什麼先後無所謂。最後將其返回。
我們這裡只分析一個具有代表性的BindView注解,其它的都是一樣的,連代碼都一毛一樣。
在這之前先看一下Element這個類我們看一下官方注釋
* Represents a program element such as a package, class, or method. * Each element represents a static, language-level construct * (and not, for example, a runtime construct of the virtual machine).
在注解處理器中,我們掃描 java 源文件,源代碼中的每一部分都是Element的一個特定類型。換句話說:Element代表程序中的元素,比如說 包,類,方法。每一個元素代表一個靜態的,語言級別的結構.
比如:
public class ClassA { // TypeElement private int var_0; // VariableElement public ClassA() {} // ExecuteableElement public void setA( // ExecuteableElement int newA // TypeElement ) { } }
可以看到類為TypeElement,變量為VariableElement,方法為ExecuteableElement
這些都是Element的子類,自己可以看下源碼,的確如此的。
TypeElement aClass ; for (Element e : aClass.getEnclosedElements()){ //獲取所有的子節點 Element parent = e.getEnclosingElement(); // 獲取父節點 }
Elements代表源代碼,TypeElement代表源代碼中的元素類型,例如類。然後,TypeElement並不包含類的相關信息。你可以從TypeElement獲取類的名稱,但你不能獲取類的信息,比如說父類。這些信息可以通過TypeMirror獲取。你可以通過調用element.asType()來獲取一個Element的TypeMirror。
這個是對於理解源碼的基礎。
繼續,我們看到了for循環查找所有包含BindView的注解。
parseBindView(element, targetClassMap, erasedTargetNames);
把element和targetClassMap傳入
private void parseBindView(Element element, MaptargetClassMap, Set erasedTargetNames) { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); //1.檢查用戶使用的合法性 // Start by verifying common generated code restrictions. boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element); // 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)) { if (elementType.getKind() == TypeKind.ERROR) { note(element, "@%s field with unresolved type (%s) " + "must elsewhere be generated as a View or interface. (%s.%s)", BindView.class.getSimpleName(), elementType, enclosingElement.getQualifiedName(), element.getSimpleName()); } else { error(element, "@%s fields must extend from View or be an interface. (%s.%s)", BindView.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } } // 不合法的直接返回 if (hasError) { return; } //2.獲取id 值 // Assemble information on the field. int id = element.getAnnotation(BindView.class).value(); // 3.獲取 BindingClass,有緩存機制, 沒有則創建,下文會仔細分析 BindingClass bindingClass = targetClassMap.get(enclosingElement); if (bindingClass != null) { ViewBindings viewBindings = bindingClass.getViewBinding(getId(id)); if (viewBindings != null && viewBindings.getFieldBinding() != null) { FieldViewBinding existingBinding = viewBindings.getFieldBinding(); error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)", BindView.class.getSimpleName(), id, existingBinding.getName(), enclosingElement.getQualifiedName(), element.getSimpleName()); return; } } else { bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement); } //4 生成FieldViewBinding 實體 String name = element.getSimpleName().toString(); TypeName type = TypeName.get(elementType); boolean required = isFieldRequired(element); FieldViewBinding binding = new FieldViewBinding(name, type, required); //5。加入到 bindingClass 成員變量的集合中 bindingClass.addField(getId(id), binding); // Add the type-erased version to the valid binding targets set. erasedTargetNames.add(enclosingElement); }
element.getEnclosingElement();是什麼呢?是父節點。就是上面我們說的。
基本的步驟就是上面的5步
1.檢查用戶使用的合法性
// Start by verifying common generated code restrictions. boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element);
private boolean isInaccessibleViaGeneratedCode(Class annotationClass, String targetThing, Element element) { boolean hasError = false; // 得到父節點 TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); //判斷修飾符,如果包含private or static 就會拋出異常。 // Verify method modifiers. Setmodifiers = element.getModifiers(); if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) { error(element, "@%s %s must not be private or static. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } //判斷父節點是否是類類型的,不是的話就會拋出異常 //也就是說BindView 的使用必須在一個類裡 // Verify containing type. if (enclosingElement.getKind() != CLASS) { error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } //判斷父節點如果是private 類,則拋出異常 // Verify containing class visibility is not private. if (enclosingElement.getModifiers().contains(PRIVATE)) { error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } return hasError; }
上面的代碼裡遇見注釋了,這裡說一下也就是我們在使用bindview注解的時候不能用使用
類不能是private修飾 ,可以是默認的或者public //in adapter private static final class ViewHolder { //.... } //成員變量不能是private修飾 ,可以是默認的或者public @BindView(R.id.word) private TextView word;
接下來還有一個方法isBindingInWrongPackage。
這個看名字也才出來個大概 就是不能在android ,java這種源碼的sdk中使用。如果你的包名是以android或者java開頭就會拋出異常。
private boolean isBindingInWrongPackage(Class annotationClass, Element element) { //得到父節點 TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); String qualifiedName = enclosingElement.getQualifiedName().toString(); if (qualifiedName.startsWith("android.")) { error(element, "@%s-annotated class incorrectly in Android framework package. (%s)", annotationClass.getSimpleName(), qualifiedName); return true; } if (qualifiedName.startsWith("java.")) { error(element, "@%s-annotated class incorrectly in Java framework package. (%s)", annotationClass.getSimpleName(), qualifiedName); return true; } return false; }
到這裡合法性檢查就完了,如果你使用不當,就會拋出異常。
// 。。。 異常拋出了 // 不合法的直接返回 if (hasError) { return; }
這裡說明一點,在處理器中拋出異常,你不能直接像平常寫java代碼一樣new thow xxx 一樣,這樣拋出去的異常不太好看。所以java處理器幫我們提供了一個輔助類Messager,這個可以幫助我們
/** * Returns the messager used to report errors, warnings, and other * notices. * * @return the messager */ Messager getMessager();
比如檢查使用合法性拋出的異常信息-error方法最後都會調用
private void error(Element element, String message, Object... args) { //Kind.ERROR 級別,就像你使用android的log一樣 printMessage(Kind.ERROR, element, message, args); } private void note(Element element, String message, Object... args) { printMessage(Kind.NOTE, element, message, args); } private void printMessage(Kind kind, Element element, String message, Object[] args) { if (args.length > 0) { message = String.format(message, args); } processingEnv.getMessager().printMessage(kind, message, element); }
ok,到這裡說了這麼多才完成了檢查。我們接著parseBindView的步驟2 獲取值 ,這裡就不多說了。好了,先休息下吧…
下一篇我們接著看。
指紋識別已經成為智能手機新一代必備功能,指紋識別讓你的手指有了更多用途。除了免輸密碼解鎖,它還有更多便捷功能,例如:指紋支付購物,查看私密文件等
-- RecyclerView簡單入門一、加入JAR包第一感覺這個東東,好復雜,沒ListView來的快,方便在項目中加入android-support-v7-recyc
昨天,華為榮耀V8已經在華為商城和京東上正式開賣了,榮耀V8是華為榮耀推出的全新V系列手機,它采用了雙卡雙待的模式,擁有5.7英寸大屏,配備了2K分辨率顯示
Android Window、PhoneWindow、Activity學習心得第二彈Window 分析這裡先給出部分源碼 目錄(Android 4.4/framework