ButterKnife原始碼深度解析(探索Activity_ViewBinding的生成)
本文基於ButterKnife 8.8.1,主要分析@BindView註解的相關部分。
檢視ButterKnife的原始碼,獲知這是通過使用APT(Annotation Processing Tool)即註解處理器,在程式編譯期掃描和處理註解Activity_ViewBinding的java檔案。 (有關注解處理器,請檢視這邊文章,講解比較詳細)
ButterKnife的compiler包,編譯註解處理器就在這裡。

ButterKnifeProcessor .png
package butterknife.compiler; public final class ButterKnifeProcessor extends AbstractProcessor { }
ButterKnifeProcessor繼承了AbstractProcessor,是生成Activity_ViewBinding的核心類,接下來分析該類和與其相關聯的類(BindingSet,ViewBinding)的原始碼, 主要分析最常用的控制元件@BindView註解和控制元件繫結 。
1、init(ProcessingEnvironment env)
private Types typeUtils;//處理TypeMirror的工具類 private Filer filer;//用於ViewBinding建立檔案 private @Nullable Trees trees; private int sdk = 1; private boolean debuggable = true;//判斷是否開啟debug模式 @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); String sdk = env.getOptions().get(OPTION_SDK_INT);//獲取app的sdk版本 if (sdk != null) { try { this.sdk = Integer.parseInt(sdk); } catch (NumberFormatException e) { env.getMessager() .printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '" + sdk + "'. Falling back to API 1 support."); } } debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE)); typeUtils = env.getTypeUtils(); filer = env.getFiler(); try { trees = Trees.instance(processingEnv); } catch (IllegalArgumentException ignored) { } }
注:init()方法主要執行獲取typeUtils和filer工具,判斷app當前版本,判斷是否開啟debug模式。
2、getSupportedAnnotationTypes()
//註冊自定義的註解 @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); for (Class<? extends Annotation> annotation : getSupportedAnnotations()) { types.add(annotation.getCanonicalName()); } return types; } //新增自定義的註解,BindView、BindViews等等 private Set<Class<? extends Annotation>> getSupportedAnnotations() { Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>(); annotations.add(BindAnim.class); 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(BindFont.class); annotations.add(BindInt.class); annotations.add(BindString.class); annotations.add(BindView.class); annotations.add(BindViews.class); annotations.addAll(LISTENERS); return annotations; }
注:getSupportedAnnotationTypes()定義註解處理器註冊到哪些註解上
3、process(Set<? extends TypeElement> annotations, RoundEnvironment env)
process()相當於每個處理器的主函式main(), 在這裡寫你的掃描、評估和處理註解的程式碼,以及生成Java檔案
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { //TypeElement(註解元素所在的類的類元素),BindingSet(註解所在類對應的繫結配置) Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); JavaFile javaFile = binding.brewJava(sdk, debuggable); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return false; }
注:
1、生成TypeElement(註解所在類的類元素)和BindingSet(Activity_ViewBinding生成配置)的map;
2、遍歷這個map,拿出typeElement和BindingSet,通過BindingSet生成JavaFile(BindingSet後面再分析);
3、執行javaFile.writeTo(filer)即可生成Activity_ViewBinding。
(3.1) findAndParseTargets()
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { //註解元素所在類元素TypeElement和對應的BindingSet.Builder的map Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>(); //使用集合Set記錄每個註解所在類的類元素,Set保證了enclosingElement不會重複 Set<TypeElement> erasedTargetNames = new LinkedHashSet<>(); ... // 遍歷所有被註解了@BindView的元素 //env.getElementsAnnotatedWith(BindView.class)返回所有被註解了@BindView的元素的集合 for (Element element : env.getElementsAnnotatedWith(BindView.class)) { try { parseBindView(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindView.class, e); } } ... /** * 下面的程式碼是把子類的BindingSet和其父類的BindingSet關聯起來 */ Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries = new ArrayDeque<>(builderMap.entrySet()); //對映類元素TypeElement和其對應BindingSet的map,作為結果返回出去 Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>(); while (!entries.isEmpty()) { Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst(); TypeElement type = entry.getKey();//key: 類元素 BindingSet.Builder builder = entry.getValue();//value: BindingSet.Builder //查詢erasedTargetNames中是否有type的父類class TypeElement parentType = findParentType(type, erasedTargetNames); if (parentType == null) { //type沒有父類,直接記錄type和對應的BindingSet bindingMap.put(type, builder.build());//執行builder.build(),詳細檢視一下 } else { BindingSet parentBinding = bindingMap.get(parentType); if (parentBinding != null) { builder.setParent(parentBinding);//設定builder的父BindingSet,關聯起來 bindingMap.put(type, builder.build());//記錄type和對應的BindingSet } else { //找到type有父類class,但是父類的BindingSet還沒執行build(),把這個map放到後面再操作 entries.addLast(entry); } } } }
注:
1、遍歷所有被註解了@BindView的元素, 執行parseBindView();* (parseBindView()的執行請看3.2)
2、新建一個Map<TypeElement, BindingSet> bindingMap,用於記錄typeElement和BindingSet;
3、遍歷builderMap和erasedTargetNames,查詢每個typeElement在builderMap中是否有其父類,如果typeElement沒有父typeElement,執行BindingSet.build()生成BindingSet,把typeElement和生成BindingSet新增入bindingMap;如果typeElement有父typeElement,關聯子typeElement和父typeElement的BindingSet,最後把bindingMap返回出去。 (BindingSet.build()的執行請看3.5)
(3.2)parseBindView()
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) { //獲取註解了@BindView控制元件變數所在的類所對應的TypeElement元素 TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); ... // Verify that the target type extends from View. TypeMirror elementType = element.asType();//獲取控制元件的TypeKind, 如:android.widget.TextView if (elementType.getKind() == TypeKind.TYPEVAR) {//elementType.getKind():TypeKind.DECLARED TypeVariable typeVariable = (TypeVariable) elementType; elementType = typeVariable.getUpperBound(); } //獲取控制元件view所在類的全限定名,如:com.example.administrator.myannotationprocessor.MainActivity Name qualifiedName = enclosingElement.getQualifiedName(); //獲取註解控制元件的變數命名,例如image,btn Name simpleName = element.getSimpleName(); ... // Assemble information on the field. int id = element.getAnnotation(BindView.class).value();//獲取@BindView(ID)的id值 //Builder物件定義了viewBinding的檔名,建構函式,unBind()方法、view所屬物件的型別等 BindingSet.Builder builder = builderMap.get(enclosingElement);//獲取控制元件view所在類Element對應的 BindingSet.Builder Id resourceId = elementToId(element, BindView.class, id); if (builder != null) { String existingBindingName = builder.findExistingBindingName(resourceId); if (existingBindingName != null) {//一個類中不同的控制元件,id不能相同 error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)", BindView.class.getSimpleName(), id, existingBindingName, enclosingElement.getQualifiedName(), element.getSimpleName()); return; } } else { builder = getOrCreateBindingBuilder(builderMap, enclosingElement); } String name = simpleName.toString();//例:image,btn TypeName type = TypeName.get(elementType);//例:android.widget.TextView boolean required = isFieldRequired(element);//判斷元素是否包含註解@Nullable(通常情況下不包含,required 為true) //builder記錄resourceId和對應的FieldViewBinding,用於生成viewbinding檔案時,建立該view變數 builder.addField(resourceId, new FieldViewBinding(name, type, required)); //記錄每個註解所在類的類元素enclosingElement erasedTargetNames.add(enclosingElement); }
注:
1、獲取註解了@BindView 元素所在類的類元素enclosingElement(TypeElement),
檢視builderMap中enclosingElement有沒有對應的BindingSet.Builder,如果沒有則新建BindingSet.Builder (getOrCreateBindingBuilder()檢視3.3) ,用builderMap記錄下這個enclosingElement和這個新建的Builder;
2、獲取註解了@BindView 元素的控制元件id,元素的變數名simpleName和元素型別TypeName,新建一個FieldViewBinding,用Builder記錄下id和對應的FieldViewBinding; ( builder.addField()請看3.4)
3、erasedTargetNames記錄下enclosingElement;
(3.3)getOrCreateBindingBuilder() (建立BindingSet.Builder)
private BindingSet.Builder getOrCreateBindingBuilder( Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) { BindingSet.Builder builder = builderMap.get(enclosingElement); if (builder == null) { builder = BindingSet.newBuilder(enclosingElement); //建立好builder後,記錄enclosingElement和對應的BindingSet.Builder builderMap.put(enclosingElement, builder); } return builder; } --> static Builder newBuilder(TypeElement enclosingElement) { TypeMirror typeMirror = enclosingElement.asType();//獲取類元素enclosingElement的真實型別 //判斷是否為對應型別的子類,這三種類型內部都可能用到控制元件@BindView繫結 boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE); boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE); boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE); //獲取enclosingElement的型別名字,Activity/Dialog/View TypeName targetType = TypeName.get(typeMirror); if (targetType instanceof ParameterizedTypeName) { targetType = ((ParameterizedTypeName) targetType).rawType; } String packageName = getPackage(enclosingElement).getQualifiedName().toString();//enclosingElement的包名 String className = enclosingElement.getQualifiedName().toString().substring(//enclosingElement的類名,如MainActivity packageName.length() + 1).replace('.', '$'); //ClassName包括包名和ViewBinding的類名,類名,: MainActivity_ViewBinding ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding"); boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL); return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog); }
注:傳入類元素enclosingElement,判斷enclosingElement型別, 獲取enclosingElement的型別名TypeName ,所在的包名,拼接新的ViewBinding的類名,把這些引數傳入Builder(),建立一個新的BindingSet.Builder。
(3.4)builder.addField(resourceId, new FieldViewBinding(name, type, required))
private final Map<Id, ViewBinding.Builder> viewIdMap = new LinkedHashMap<>(); void addField(Id id, FieldViewBinding binding) { getOrCreateViewBindings(id).setFieldBinding(binding); } --> private ViewBinding.Builder getOrCreateViewBindings(Id id) { ViewBinding.Builder viewId = viewIdMap.get(id); if (viewId == null) { viewId = new ViewBinding.Builder(id);//new一個ViewBinding.Builder viewIdMap.put(id, viewId); } return viewId; } --> public void setFieldBinding(FieldViewBinding fieldBinding) { ... //設定ViewBinding的fieldBinding this.fieldBinding = fieldBinding; }
注:BindingSet.Build中用viewIdMap記錄id和對應的FieldViewBinding,viewIdMap在3.4中 會用到;FieldViewBinding記錄有控制元件的變數名,控制元件的型別名稱
(3.4)BindingSet.Build.build() (執行BindingSet的建立)
BindingSet build() { //ImmutableList是不可變集合,viewBindings.build()執行後,ImmutableList不再可變 ImmutableList.Builder<ViewBinding> viewBindings = ImmutableList.builder(); for (ViewBinding.Builder builder : viewIdMap.values()) { viewBindings.add(builder.build()); } return new BindingSet(targetTypeName, bindingClassName, isFinal, isView, isActivity, isDialog, viewBindings.build(), collectionBindings.build(), resourceBindings.build(), parentBinding); } --> private BindingSet(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal, boolean isView, boolean isActivity, boolean isDialog, ImmutableList<ViewBinding> viewBindings, ImmutableList<FieldCollectionViewBinding> collectionBindings, ImmutableList<ResourceBinding> resourceBindings, @Nullable BindingSet parentBinding) { this.isFinal = isFinal; this.targetTypeName = targetTypeName;//型別名稱 this.bindingClassName = bindingClassName;//生成ViewBinding的名稱 this.isView = isView;//是否為View this.isActivity = isActivity;//是否為Activity this.isDialog = isDialog;//是否為Dialog this.viewBindings = viewBindings;//記錄 this.collectionBindings = collectionBindings; this.resourceBindings = resourceBindings; this.parentBinding = parentBinding;//記錄父類的ViewBinding,生成的檔案要繼承它 }
注:這裡就只是執行BindingSet的構建,返回一個BindingSet。
小結:以上的程式碼主要是掃描註解了@BindView的元素列表並遍歷這個列表,獲取註解元素所在類的類元素和對應的BindingSet的map;再重新檢視第3點的process()的方法,可以看到這句程式碼JavaFile javaFile = binding.brewJava(sdk, debuggable),得知生成JavaFile的關鍵在BindingSet類中。
後續的分析我們需要配合一個 ButterKnifeDemo 來進行,編譯後生成的MainActivity_ViewBinding 如下;
public class MainActivity_ViewBinding implements Unbinder { private MainActivity target; private View view2131165321; private View view2131165218; private View view2131165238; private TextWatcher view2131165238TextWatcher; @UiThread public MainActivity_ViewBinding(MainActivity target) { this(target, target.getWindow().getDecorView()); } @UiThread @SuppressLint("ClickableViewAccessibility") public MainActivity_ViewBinding(final MainActivity target, View source) { this.target = target; View view; view = Utils.findRequiredView(source, R.id.textView, "field 'textView', method 'onViewClicked', method 'onViewClicked', method 'onViewClicked', method 'onViewLongClicked', and method 'onViewTouched'"); target.textView = Utils.castView(view, R.id.textView, "field 'textView'", TextView.class); view2131165321 = view; view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.onViewClicked(); target.onViewClicked(Utils.castParam(p0, "doClick", 0, "onViewClicked", 0, TextView.class)); target.onViewClicked(p0); } }); view.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View p0) { return target.onViewLongClicked(); } }); view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View p0, MotionEvent p1) { return target.onViewTouched(); } }); view = Utils.findRequiredView(source, R.id.btn, "method 'onViewClicked'"); view2131165218 = view; view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.onViewClicked(p0); } }); view = Utils.findRequiredView(source, R.id.editText, "method 'beforeTextChanged', method 'afterTextChanged', and method 'onTextChanged'"); view2131165238 = view; view2131165238TextWatcher = new TextWatcher() { @Override public void onTextChanged(CharSequence p0, int p1, int p2, int p3) { target.onTextChanged(p0, p1, p2, p3); } @Override public void beforeTextChanged(CharSequence p0, int p1, int p2, int p3) { target.beforeTextChanged(p0, p1, p2, p3); } @Override public void afterTextChanged(Editable p0) { target.afterTextChanged(p0); } }; ((TextView) view).addTextChangedListener(view2131165238TextWatcher); } @Override @CallSuper public void unbind() { MainActivity target = this.target; if (target == null) throw new IllegalStateException("Bindings already cleared."); this.target = null; target.textView = null; view2131165321.setOnClickListener(null); view2131165321.setOnLongClickListener(null); view2131165321.setOnTouchListener(null); view2131165321 = null; view2131165218.setOnClickListener(null); view2131165218 = null; ((TextView) view2131165238).removeTextChangedListener(view2131165238TextWatcher); view2131165238TextWatcher = null; view2131165238 = null; } }
4、brewJava()
final class BindingSet{ JavaFile brewJava(int sdk, boolean debuggable) { //TypeSpec 用於配置javaFile的變數和方法 TypeSpec bindingConfiguration = createType(sdk, debuggable); //bindingClassName.packageName()是包名,設定建立javaFile到對應的包中 return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); } --> private TypeSpec createType(int sdk, boolean debuggable) { TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName()) .addModifiers(PUBLIC);//新增ViewBinding新增屬性public if (isFinal) { result.addModifiers(FINAL);//final屬性 } if (parentBinding != null) { result.superclass(parentBinding.bindingClassName);//設定父類ViewBinding } else { result.addSuperinterface(UNBINDER);//繼承Unbinder介面 } //建立全域性私有變數, 如:private MainActivity target; if (hasTargetField()) { result.addField(targetTypeName, "target", PRIVATE); } //根據型別,建立對應的構造方法 if (isView) { result.addMethod(createBindingConstructorForView()); } else if (isActivity) { //例:public MainActivity_ViewBinding(MainActivity target) result.addMethod(createBindingConstructorForActivity()); } else if (isDialog) { result.addMethod(createBindingConstructorForDialog()); } if (!constructorNeedsView()) { // Add a delegating constructor with a target type + view signature for reflective use. result.addMethod(createBindingViewDelegateConstructor()); } result.addMethod(createBindingConstructor(sdk, debuggable)); if (hasViewBindings() || parentBinding == null) { //建立unbind方法, 解析在(4.5) result.addMethod(createBindingUnbindMethod(result)); } return result.build(); } }
注:呼叫createType()生成TypeSpec,這是生成JavaFile的必要引數; createType()方法可以檢視createBindingConstructorForActivity()和createBindingConstructor(),其他基本比較好理解。
(4.1)createBindingConstructorForActivity()
private MethodSpec createBindingConstructorForActivity() { MethodSpec.Builder builder = MethodSpec.constructorBuilder() .addAnnotation(UI_THREAD)//給構造方法加上@UiThread的註解 .addModifiers(PUBLIC)//給構造方法加上publc屬性 .addParameter(targetTypeName, "target");//加上引數,例如:MainActivity target if (constructorNeedsView()) {//判斷構造方法是否需要傳入檢視 //傳入包括最頂層decorView的檢視 builder.addStatement("this(target, target.getWindow().getDecorView())"); } else { builder.addStatement("this(target, target)"); } return builder.build(); } --> private boolean constructorNeedsView() { //判斷頁面或者父類頁面是否有設定@BindView或@BindViews註解變數 return hasViewBindings() || (parentBinding != null && parentBinding.constructorNeedsView()); } --> //當我們的activity或者dialog等頁面中有設定註解@BindView的控制元件變數,viewBindings就不為空 //有設定註解@BindViews,collectionBindings就不為空 private boolean hasViewBindings() { return !viewBindings.isEmpty() || !collectionBindings.isEmpty(); }
注:判斷viewBindings和collectionBindings是否為空,如果有用到@BindView或者@BindViews,hasViewBindings就為true,那麼MainActivity_ViewBinding的建構函式就需要view的引數。
(4.2)createBindingConstructorForActivity()
private MethodSpec createBindingConstructor(int sdk, boolean debuggable) { MethodSpec.Builder constructor = MethodSpec.constructorBuilder() .addAnnotation(UI_THREAD) .addModifiers(PUBLIC); if (hasMethodBindings()) { //有使用註解@OnClick這些,需要用target回到頁面的onclick方法,所以target要加上final constructor.addParameter(targetTypeName, "target", FINAL); } else { constructor.addParameter(targetTypeName, "target"); } if (constructorNeedsView()) { //用使用@BindView,需要為頁面繫結控制元件,需要View的引數 constructor.addParameter(VIEW, "source"); } else { constructor.addParameter(CONTEXT, "context"); } if (hasUnqualifiedResourceBindings()) { //如果資源id的id值是一個普通的int值而不是R.id.xxx,給構造方法添加註解@SuppressWarnings constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType") .build()); } if (hasOnTouchMethodBindings()) { //如果有用到@Touch註解,構造方法需要加上@SuppressLint("ClickableViewAccessibility")的註解 constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT) .addMember("value", "$S", "ClickableViewAccessibility") .build()); } if (parentBinding != null) {//有父類的ViewBinding時 if (parentBinding.constructorNeedsView()) {//父類ViewBinding的建構函式有View型別的引數 constructor.addStatement("super(target, source)");//新增super(target, source)的程式碼 } else if (constructorNeedsView()) {//父類ViewBinding的建構函式沒有View型別的引數,本建構函式需要View型別的引數 constructor.addStatement("super(target, source.getContext())"); } else {//父類和自己的ViewBinding的建構函式都不需要View型別的引數 constructor.addStatement("super(target, context)"); } constructor.addCode("\n");// } if (hasTargetField()) { //有target變數的時候給它賦值 constructor.addStatement("this.target = target"); constructor.addCode("\n"); } if (hasViewBindings()) { if (hasViewLocal()) { // 加上程式碼View view,用於暫存控制元件 constructor.addStatement("$T view", VIEW); } for (ViewBinding binding : viewBindings) {//遍歷viewBindings集合 addViewBinding(constructor, binding, debuggable);//初始化控制元件並繫結 } for (FieldCollectionViewBinding binding : collectionBindings) { constructor.addStatement("$L", binding.render(debuggable)); } if (!resourceBindings.isEmpty()) { constructor.addCode("\n"); } } ... return constructor.build(); }
上面這段主要還是配置Activity_ViewBinding構造方法的屬性和引數等配置,程式碼註釋也非常清楚,而控制元件的繫結在addViewBinding()中,繼續往下看。
(4.3)addViewBinding()
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) { if (binding.isSingleFieldBinding()) {//只繫結控制元件,不需要新增click或者touch監聽 // Optimize the common case where there's a single binding directly to a field. FieldViewBinding fieldBinding = requireNonNull(binding.getFieldBinding()); CodeBlock.Builder builder = CodeBlock.builder() .add("target.$L = ", fieldBinding.getName());//新增程式碼,例如:target.textView boolean requiresCast = requiresCast(fieldBinding.getType());//如果控制元件變數的型別不是View,就需要轉型 if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) { if (requiresCast) { builder.add("($T) ", fieldBinding.getType());//拼接程式碼:(TextView) } //例子:(TextView)source.findViewById(R.id.textView) builder.add("source.findViewById($L)", binding.getId().code); } else { //生成類似Utils.findRequiredViewAsType(source, R.id.textView, "field 'textView'", TextView.class); builder.add("$T.find", UTILS);//拼接程式碼:Utils.find builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");//拼接程式碼:RequiredView if (requiresCast) { builder.add("AsType");//拼接程式碼:AsType } builder.add("(source, $L", binding.getId().code);//拼接程式碼:(source, R.id.xxx if (fieldBinding.isRequired() || requiresCast) { //拼接程式碼,例如:,"field ' text' " builder.add(", $S", asHumanDescription(singletonList(fieldBinding))); } if (requiresCast) { builder.add(", $T.class", fieldBinding.getRawType());//拼接程式碼,例如:,TextView.class } builder.add(")");//拼接括號結尾: ) } //完成一句程式碼的構建, 例:target.textView = (TextView)source.findViewById(R.id.textView) //或者:target.textView = Utils.findRequiredViewAsType(source, R.id.textView, "field 'textView'", TextView.class); result.addStatement("$L", builder.build()); return; } /* * 當需要給控制元件新增點選監聽時,才執行以下程式碼 * */ List<MemberViewBinding> requiredBindings = binding.getRequiredBindings(); if (!debuggable || requiredBindings.isEmpty()) { //view = source.findViewById(R.id.xxx) result.addStatement("view = source.findViewById($L)", binding.getId().code); } else if (!binding.isBoundToRoot()) { //view = Utils.findRequiredView(source, R.id.xxx, "field 'textView' and method 'onViewClicked'") result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS, binding.getId().code, asHumanDescription(requiredBindings)); } addFieldBinding(result, binding, debuggable); addMethodBindings(result, binding, debuggable); }
注:上面首先判斷控制元件是否只需要繫結id實現初始化,不需要給控制元件新增監聽;然後使用CodeBlock執行程式碼拼接,拼接結果如:target.textView = Utils.findRequiredViewAsType(source, R.id.textView, "field 'textView'", TextView.class);如果控制元件不僅僅需要繫結id,還需要實現監聽,則需要繼續看以下的程式碼 。
(4.4)addFieldBinding()和addMethodBindings()
//控制元件需要設定點選監聽時,設定控制元件的繫結 private void addFieldBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) { FieldViewBinding fieldBinding = binding.getFieldBinding(); if (fieldBinding != null) { if (requiresCast(fieldBinding.getType())) {//需要轉型 if (debuggable) {//debug模式 //target.text = Utils.castView(view, R.id.text, "field 'text'", TextView.class); result.addStatement("target.$L = $T.castView(view, $L, $S, $T.class)", fieldBinding.getName(), UTILS, binding.getId().code, asHumanDescription(singletonList(fieldBinding)), fieldBinding.getRawType()); } else { //target.text = (TextView)view result.addStatement("target.$L = ($T) view", fieldBinding.getName(), fieldBinding.getType()); } } else { result.addStatement("target.$L = view", fieldBinding.getName());//不需要轉型,target.fieldName= view } } } //設定控制元件的繫結 private void addMethodBindings(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) { //ListenerClass-->ListenerMethod-->Set<MethodViewBinding> Map<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>> classMethodBindings = binding.getMethodBindings(); if (classMethodBindings.isEmpty()) {//需要設定監聽的方法為空 return; } // We only need to emit the null check if there are zero required bindings. boolean needsNullChecked = binding.getRequiredBindings().isEmpty(); if (needsNullChecked) { result.beginControlFlow("if (view != null)"); } // Add the view reference to the binding. String fieldName = "viewSource"; String bindName = "source"; if (!binding.isBoundToRoot()) {//判斷id是否為空 fieldName = "view" + Integer.toHexString(binding.getId().value);//例子:view2131165315 bindName = "view"; } result.addStatement("$L = $N", fieldName, bindName);//新增程式碼,例子:view2131165315 = view; for (Map.Entry<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>> e : classMethodBindings.entrySet()) { ListenerClass listener = e.getKey();//例如:@OnClick Map<ListenerMethod, Set<MethodViewBinding>> methodBindings = e.getValue(); //建立一個匿名內部類,父類為listener中的type, 例如:DebouncingOnClickListener TypeSpec.Builder callback = TypeSpec.anonymousClassBuilder("") .superclass(ClassName.bestGuess(listener.type())); //獲取ListenerClass的對應的ListenerMethod for (ListenerMethod method : getListenerMethods(listener)) { MethodSpec.Builder callbackMethod = MethodSpec.methodBuilder(method.name()) .addAnnotation(Override.class) .addModifiers(PUBLIC) .returns(bestGuess(method.returnType()));//函式返回型別,default "void" String[] parameterTypes = method.parameters(); for (int i = 0, count = parameterTypes.length; i < count; i++) { callbackMethod.addParameter(bestGuess(parameterTypes[i]), "p" + i);//方法的引數, 例:View p0 } boolean hasReturnValue = false;//預設方法不需要返回值 CodeBlock.Builder builder = CodeBlock.builder(); Set<MethodViewBinding> methodViewBindings = methodBindings.get(method);//獲取ListenerMethod對應的MethodViewBinding集合 if (methodViewBindings != null) { for (MethodViewBinding methodBinding : methodViewBindings) {//遍歷MethodViewBinding集合 if (methodBinding.hasReturnValue()) {//如果方法需要返回值, hasReturnValue = true; //拼接程式碼: return builder.add("return "); // TODO what about multiple methods? } builder.add("target.$L(", methodBinding.getName());//拼接程式碼,例如:target.onViewClicked(); List<Parameter> parameters = methodBinding.getParameters(); String[] listenerParameters = method.parameters(); for (int i = 0, count = parameters.size(); i < count; i++) { if (i > 0) { builder.add(", ");//例如,如果target.onViewClicked()中需要多個引數,引數中間加逗號 } Parameter parameter = parameters.get(i); int listenerPosition = parameter.getListenerPosition(); if (parameter.requiresCast(listenerParameters[listenerPosition])) { if (debuggable) { //拼接程式碼,例如Utils.castParam(p0, "doClick", 0, "onViewClicked", 0, TextView.class) builder.add("$T.castParam(p$L, $S, $L, $S, $L, $T.class)", UTILS, listenerPosition, method.name(), listenerPosition, methodBinding.getName(), i, parameter.getType()); } else { builder.add("($T) p$L", parameter.getType(), listenerPosition); } } else { builder.add("p$L", listenerPosition);//拼接程式碼,例如,p0 } } builder.add(");\n");//拼接程式碼,); } } if (!"void".equals(method.returnType()) && !hasReturnValue) { builder.add("return $L;\n", method.defaultReturn());//拼接程式碼:return null; } callbackMethod.addCode(builder.build()); callback.addMethod(callbackMethod.build()); } //只有@OnTextChanged和@OnPageChange兩個註解有設定remover boolean requiresRemoval = listener.remover().length() != 0; String listenerField = null; if (requiresRemoval) { //對於@OnTextChanged, listenerClassName = android.text.TextWatcher TypeName listenerClassName = bestGuess(listener.type()); listenerField = fieldName + ((ClassName) listenerClassName).simpleName();//例子:view2131165238TextWatcher result.addStatement("$L = $L", listenerField, callback.build());//view2131165238TextWatcher=new TextWatcher(){...} } String targetType = listener.targetType(); if (!VIEW_TYPE.equals(targetType)) {//判斷listener的targetType是否為android.view.View; //對於@OnTextChanged, 拼接完的程式碼為:((TextView) view).addTextChangedListener(view2131165238TextWatcher); result.addStatement("(($T) $N).$L($L)", bestGuess(targetType), bindName, listener.setter(), requiresRemoval ? listenerField : callback.build()); } else { result.addStatement("$N.$L($L)", bindName, listener.setter(), requiresRemoval ? listenerField : callback.build()); //最後結果 //view.setOnClickListener(new DebouncingOnClickListener() { //@Override //public void doClick(View p0) { //target.onViewClicked(p0); //} //}); } } if (needsNullChecked) { result.endControlFlow(); } }
注:上面兩個方法就是繫結控制元件以及新增控制元件監聽時如何拼接程式碼的過程。
(4.5)createBindingUnbindMethod()
private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass) { MethodSpec.Builder result = MethodSpec.methodBuilder("unbind") .addAnnotation(Override.class) .addModifiers(PUBLIC); if (!isFinal && parentBinding == null) { result.addAnnotation(CALL_SUPER);//新增@CallSuper註解 } if (hasTargetField()) { if (hasFieldBindings()) { result.addStatement("$T target = this.target", targetTypeName);//例如:MainActivity target = this.target } //if (target == null) throw new IllegalStateException("Bindings already cleared."); result.addStatement("if (target == null) throw new $T($S)", IllegalStateException.class, "Bindings already cleared."); result.addStatement("$N = null", hasFieldBindings() ? "this.target" : "target");//this.target = null; result.addCode("\n"); for (ViewBinding binding : viewBindings) { if (binding.getFieldBinding() != null) { result.addStatement("target.$L = null", binding.getFieldBinding().getName());//例如:target.text = null; } } for (FieldCollectionViewBinding binding : collectionBindings) { result.addStatement("target.$L = null", binding.name);//例如:target.text = null; } } if (hasMethodBindings()) { result.addCode("\n"); for (ViewBinding binding : viewBindings) { addFieldAndUnbindStatement(bindingClass, result, binding);-->在下一部分 } } if (parentBinding != null) { result.addCode("\n"); result.addStatement("super.unbind()");//super.unbind() } return result.build(); } -->生成程式碼:給控制元件設定listener為null, 設定控制元件為空 private void addFieldAndUnbindStatement(TypeSpec.Builder result, MethodSpec.Builder unbindMethod, ViewBinding bindings) { // Only add fields to the binding if there are method bindings. Map<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>> classMethodBindings = bindings.getMethodBindings(); if (classMethodBindings.isEmpty()) { return; } //當空間的id不為NO_RES_ID, 新增全域性變數,例如:private View view2131165315; String fieldName = bindings.isBoundToRoot() ? "viewSource" : "view" + Integer.toHexString(bindings.getId().value);//fieldName:view2131165315; result.addField(VIEW, fieldName, PRIVATE); // We only need to emit the null check if there are zero required bindings. boolean needsNullChecked = bindings.getRequiredBindings().isEmpty(); if (needsNullChecked) { unbindMethod.beginControlFlow("if ($N != null)", fieldName); } for (ListenerClass listenerClass : classMethodBindings.keySet()) { // We need to keep a reference to the listener // in case we need to unbind it via a remove method. boolean requiresRemoval = !"".equals(listenerClass.remover()); String listenerField = "null"; if (requiresRemoval) { //對於@OnTextChanged, listenerClassName = android.text.TextWatcher TypeName listenerClassName = bestGuess(listenerClass.type()); listenerField = fieldName + ((ClassName) listenerClassName).simpleName();//view2131165238TextWatcher result.addField(listenerClassName, listenerField, PRIVATE);//private TextWatcher view2131165238TextWatcher; } String targetType = listenerClass.targetType(); if (!VIEW_TYPE.equals(targetType)) {//判斷listener的targetType是否為android.view.View; //例如((TextView) view2131165238).removeTextChangedListener(view2131165238TextWatcher); unbindMethod.addStatement("(($T) $N).$N($N)", bestGuess(targetType), fieldName, removerOrSetter(listenerClass, requiresRemoval), listenerField); } else { //拼接程式碼,例如view2131165320.setOnClickListener(null); unbindMethod.addStatement("$N.$N($N)", fieldName, removerOrSetter(listenerClass, requiresRemoval), listenerField); } if (requiresRemoval) { unbindMethod.addStatement("$N = null", listenerField); } } unbindMethod.addStatement("$N = null", fieldName);//拼接程式碼,例如view2131165320 = null; if (needsNullChecked) { unbindMethod.endControlFlow(); } }
注:上面這段程式碼就是unbind方法生成的過程,給控制元件設定listener為null獲取呼叫removeListener去掉監聽, 設定控制元件為null,具體可以看程式碼以及註釋。
總結:
1、ButterKnife通過註冊自定義註解,註解處理器掃描程式中的所有註解,獲取到@BindView註解的元素集合以及元素的資訊;
2、對所有@BindView註解元素的資訊進行分析,獲取@BindView註解元素所在類的類元素enclosingElement,給每個enclosingElement配對一個BindingSet.Build(用map儲存),Build記錄下enclosingElement類中所有@BindView元素的相關資訊(包括id,控制元件型別,變數名)。
3、執行BindingSet.Build()生成BindingSet(儲存有類元素class的實際型別,Activity或View或Dialog,類元素的包名,類變數名_ViewBinding(如MainActivity_ViewBinding),@BindView註解元素的資訊等);
4、執行BindingSet.brewJava,根據BindingSet儲存的資訊,生成對應的JavaFile,最後執行javaFile.writeTo(filer)就能生成Activity_ViewBinding的檔案。