一、前言
ButterKnife——通過注解的方式生成View字段巷挥、資源綁定和方法綁定的樣板代碼梧奢,是一款老司機書寫UI布局的必備神器!自從有了ButterKnife,媽媽再也不用擔(dān)心我findViewbyid()穆刻,find到手抽筋。
本文基于最新的8.7.0版本進行分析派敷,不同版本可能實現(xiàn)方式有所差異蛹批,請知悉。
二篮愉、上車
下載Android studio 插件Android ButterKnife Zelezny腐芍,一鍵生成模板代碼。更多姿勢试躏,請參考官方文檔猪勇。作為一個老司機,上車不是我們的重點颠蕴,漂移才是我們的目標(biāo)泣刹!
三、漂移
checkout下來源碼犀被,工程結(jié)構(gòu)如圖椅您,核心實現(xiàn)模塊是butterknife、butterknife-annotations寡键、butterknife-compiler掀泳。
首先從入口開始,以activity的bind為例,代碼如下:
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
先獲取activity的根布局DecorView员舵,然后作為參數(shù)傳遞給createBinding()方法脑沿,該方法就是通過構(gòu)造函數(shù)new出一個“clsName + "_ViewBinding”的class對象÷砥В看一下createBinding()方法的關(guān)鍵代碼:
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
return constructor.newInstance(target, source);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
}
繼續(xù)追蹤 findBindingConstructorForClass()的源碼:
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
} catch (ClassNotFoundException e) {
if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
}
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
該方法首先從BINDINGS這個LinkedHashMap中查找緩存庄拇,如果命中,直接返回bindingCtor韭邓,bindingCtor是實現(xiàn)了Unbinder接口的子類的構(gòu)造函數(shù)措近;如果為null,則通過ClassLoade加載一個"clsName + "_ViewBinding""的類仍秤,然后返回其構(gòu)造函數(shù)熄诡。其中clsName 就是我們上文調(diào)用bind()的activity,最后把它put到BINDINGS這個集合中诗力。
??那么"clsName + "_ViewBinding""的這個類在哪碑韵?里面實現(xiàn)了什么邏輯胜宇?又是怎么生成的?這些才是今天的重點摧冀!接下來我們一個個解答:
3.1 "clsName + "_ViewBinding""的這個類在哪菜拓?:
大家根據(jù)截圖一層一層追進去就可以找到相應(yīng)代碼瓣窄。大致是:app->build->generated->source->apt->"打包渠道"->clsName類在工程中所在目錄
3.2 "clsName + "_ViewBinding""的這個類實現(xiàn)了什么邏輯?
如上所述纳鼎,該類實現(xiàn)了Unbinder接口俺夕,有兩個重載的構(gòu)造函數(shù),和一個unbind()方法贱鄙,unbind()方法很簡單劝贸,顧名思義就是解除綁定,釋放資源逗宁,沒啥好說的映九。
public class BusinessmenListActivity_ViewBinding implements Unbinder {
private BusinessmenListActivity target;
@UiThread
public BusinessmenListActivity_ViewBinding(BusinessmenListActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public BusinessmenListActivity_ViewBinding(BusinessmenListActivity target, View source) {
this.target = target;
target.mRecyclerView = Utils.findRequiredViewAsType(source, R.id.rv_bus, "field 'mRecyclerView'", RecyclerView.class);
target.mRefreshLayout = Utils.findRequiredViewAsType(source, R.id.refresh_layout, "field 'mRefreshLayout'", SwipeRefreshLayout.class);
}
@Override
@CallSuper
public void unbind() {
BusinessmenListActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.mRecyclerView = null;
target.mRefreshLayout = null;
}
}
我們重點看一下Utils.findRequiredViewAsType()的代碼,就是這個方法幫我們實現(xiàn)了綁定View相關(guān)邏輯。先調(diào)用findRequiredView()方法返回Viwe對象,然后再castView()成具體的子View瞎颗。比較簡單件甥,直接看代碼相信都能看懂:
public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
Class<T> cls) {
View view = findRequiredView(source, id, who);
return castView(view, id, who, cls);
}
findRequiredView()代碼:
public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
if (view != null) {
return view;
}
String name = getResourceEntryName(source, id);
throw new IllegalStateException("Required view '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
+ " (methods) annotation.");
}
castView()代碼:
public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
try {
return cls.cast(view);
} catch (ClassCastException e) {
String name = getResourceEntryName(view, id);
throw new IllegalStateException("View '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was of the wrong type. See cause for more info.", e);
}
}
至此,就完成了View綁定的所有邏輯哼拔。
3.3 "clsName + "_ViewBinding"".java文件是如何生成的引有?
該類的生成主要依賴一個叫做APT的工具。APT(Annotation Processing Tool)是一種處理注解的工具,它對源代碼文件進行檢測找出其中的Annotation倦逐,使用Annotation進行額外的處理譬正。
??使用apt需要繼承AbstractProcessor類,同時有幾個核心方法需要實現(xiàn),分別是:
??init()主要做一些初始化操作导帝;
??getSupportedAnnotationTypes()守谓,顧名思義,獲取所有支持的注解類型您单;
??process()處理注解相關(guān)邏輯斋荞,"clsName + "_ViewBinding"".java文件生成的核心邏輯就在這里!
在ButterKnife中虐秦,有一個ButterKnifeProcessor類平酿,該類就是處理ButterKnife注解相關(guān)邏輯的類。
3.3.1 我們先從init()方法開始:初始化悦陋。
@Override public synchronized void init(ProcessingEnvironment env) {
super.init(env);
String sdk = env.getOptions().get(OPTION_SDK_INT);
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));
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
}
}
init()方法中蜈彼,沒有過多邏輯,只是有幾個變量需要說明一下俺驶,這幾個主要是注解處理時用到的工具類幸逆。
private Elements elementUtils;
private Types typeUtils;
private Filer filer;
private Trees trees;
Elements:一個用來處理Element的工具類,源代碼的每一個部分都是一個特定類型的Element暮现。例如还绘,包名、字段栖袋、方法等等拍顷。
Types:一個用來處理TypeMirror的工具類,比如判斷該元素是class還是interface塘幅;
Filer:生成文件昔案;
Trees :樹,遍歷文件用到电媳。
3.3.2 接下來看一下getSupportedAnnotationTypes()方法:獲取所有支持的注解類型踏揣。
@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(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;
}
該方法的大概意思就是,將所有ButterKnife用到的注解全部添加到支持的注解集合中匆背。
我們來瞄一眼最熟悉的BindView.class
@Retention(CLASS) @Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
代碼很簡單呼伸,但是有幾個注解相關(guān)的點需要說明一下,
元注解:元注解的作用就是負責(zé)注解其他注解钝尸。相關(guān)元注解:
1.@Target:
表示注解類型所適用的程序元素的種類括享。
2.@Retention:
表示該注解類型的注解保留的時長。
??SOURCE 僅存在Java源文件珍促,經(jīng)過編譯器后便丟棄相應(yīng)的注解铃辖;
??CLASS 存在Java源文件,以及經(jīng)編譯器后生成的Class字節(jié)碼文件猪叙,但在運行時VM不再保留注釋娇斩;
??RUNTIME 存在源文件仁卷、編譯生成的Class字節(jié)碼文件,以及保留在運行時VM中犬第,可通過反射性地讀取注解锦积。
對應(yīng)到BindView這個注解中,我們可以知道歉嗓,該注解適用的類型為字段丰介,且會打包到Class字節(jié)碼文件中,該注解接收的值類型為@IdRes int類型鉴分。
3.3.3 最后process()方法:遍歷所有注解->根據(jù)注解生成相應(yīng)的代碼->生成java文件哮幢。
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
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;
}
先瞄一眼findAndParseTargets()方法核心代碼:
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
// ...... 此處省略若干行代碼
// 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);
}
}
}
// 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;
還是以@BindView 為例,其他的類似志珍,不再一一贅述橙垢。關(guān)鍵代碼:
// 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);
}
}
}
遍歷所有使用@BindView注解的Element,繼續(xù)看parseBindView()代碼:
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Assemble information on the field.
//獲取綁定的View的id伦糯,即:R.id.xx.
int id = element.getAnnotation(BindView.class).value();
//判斷該元素是否已經(jīng)綁定過柜某,如果綁定過,返回錯誤舔株,否則莺琳,調(diào)用getOrCreateBindingBuilder()方法
BindingSet.Builder builder = builderMap.get(enclosingElement);
QualifiedId qualifiedId = elementToQualifiedId(element, id);
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
if (existingBindingName != null) {
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();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
首先,通過 int id = element.getAnnotation(BindView.class).value();
獲取綁定的View的id载慈,即:R.id.xx.;
再通過elementToQualifiedId()方法,生成一個合格標(biāo)識:
private QualifiedId elementToQualifiedId(Element element, int id) {
return new QualifiedId(elementUtils.getPackageOf(element).getQualifiedName().toString(), id);
}
第三步,從已經(jīng)綁定的元素中查找該元素是否存在珍手,如果存在办铡,返回錯誤,不允許重復(fù)綁定琳要,否則調(diào)用getOrCreateBindingBuilder()方法生成一個綁定對象:
private BindingSet.Builder getOrCreateBindingBuilder(
Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder == null) {
builder = BindingSet.newBuilder(enclosingElement);
builderMap.put(enclosingElement, builder);
}
return builder;
}
進一步看看 BindingSet.newBuilder(enclosingElement)方法
static Builder newBuilder(TypeElement enclosingElement) {
TypeMirror typeMirror = enclosingElement.asType();
boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);
TypeName targetType = TypeName.get(typeMirror);
if (targetType instanceof ParameterizedTypeName) {
targetType = ((ParameterizedTypeName) targetType).rawType;
}
String packageName = getPackage(enclosingElement).getQualifiedName().toString();
String className = enclosingElement.getQualifiedName().toString().substring(
packageName.length() + 1).replace('.', '$');
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}
就是這里將生成的java文件類名定義為“className + "_ViewBinding"”
第四步寡具,回到parseBindView()方法,為綁定對象添加綁定的View字段:
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
第五步稚补,關(guān)聯(lián)父類綁定的資源(view童叠、string、listener等)课幕,并把她們添加到 Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>()這個集合當(dāng)中厦坛,這個集合存儲了所有的BuildSet對象。
BuildSet對象就是單個綁定類型(activity乍惊、fragment杜秸、view、dialog)的所有綁定請求(BindView润绎、BindString等)的集合撬碟。每一個使用ButterKnife的類對應(yīng)一個BuildSet诞挨。
// 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);
}
}
主要看一下關(guān)鍵代碼 BindingSet build()代碼,以及BindingSet的構(gòu)造函數(shù):
BindingSet build() {
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, BindingSet parentBinding) {
this.isFinal = isFinal;
this.targetTypeName = targetTypeName;
this.bindingClassName = bindingClassName;
this.isView = isView;
this.isActivity = isActivity;
this.isDialog = isDialog;
this.viewBindings = viewBindings;
this.collectionBindings = collectionBindings;
this.resourceBindings = resourceBindings;
this.parentBinding = parentBinding;
}
至此呢蛤,所有需要綁定的資源已經(jīng)添加到集合當(dāng)中惶傻,只差生成代碼,可謂萬事俱備只欠東風(fēng)其障!
回到process()代碼:
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
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;
}
通過第一行代碼银室,我們幾經(jīng)周折終于掌握了她的來龍去脈,還剩一個for循環(huán)静秆。for循環(huán)無非就是遍歷生成相應(yīng)的"clsName + "_ViewBinding"".java文件粮揉,具體怎么生成的,我們跟進去瞄一眼:
JavaFile brewJava(int sdk, boolean debuggable) {
return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
這里涉及一個重要的知識點:JavaPoet抚笔。此為何物扶认? 套用官方的簡介就是:“JavaPoet is a Java API for generating .java source files.” 簡直精辟得不能再精辟!
簡單理解——就是用來生成java文件的殊橙,這不正是我們所要的東風(fēng)嗎辐宾?具體的使用姿勢,不是本文的重點膨蛮,可以查看官方文檔叠纹,文檔寫得相當(dāng)詳細,在此不再一一贅述敞葛,本文只對用到的地方做一些解析誉察。
看一下createType()方法:
private TypeSpec createType(int sdk, boolean debuggable) {
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC);
if (isFinal) {
result.addModifiers(FINAL);
}
if (parentBinding != null) {
result.superclass(parentBinding.bindingClassName);
} else {
result.addSuperinterface(UNBINDER);
}
if (hasTargetField()) {
result.addField(targetTypeName, "target", PRIVATE);
}
if (isView) {
result.addMethod(createBindingConstructorForView());
} else if (isActivity) {
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) {
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}
如果你已經(jīng)了解了JavaPoet之后再來看這個代碼,其實可以一目了然惹谐,這里不做過多解釋持偏,僅僅驗證一下,我們的設(shè)想與生成的"clsName + "_ViewBinding"".java文件內(nèi)容是否一致即可氨肌。
主要看一下createBindingConstructor()方法鸿秆,該方法是生成"clsName + "_ViewBinding"".java代碼的核心:
private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC);
if (hasMethodBindings()) {
constructor.addParameter(targetTypeName, "target", FINAL);
} else {
constructor.addParameter(targetTypeName, "target");
}
if (constructorNeedsView()) {
constructor.addParameter(VIEW, "source");
} else {
constructor.addParameter(CONTEXT, "context");
}
if (hasUnqualifiedResourceBindings()) {
// Aapt can change IDs out from underneath us, just suppress since all will work at runtime.
constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", "$S", "ResourceType")
.build());
}
if (hasOnTouchMethodBindings()) {
constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
.addMember("value", "$S", "ClickableViewAccessibility")
.build());
}
if (parentBinding != null) {
if (parentBinding.constructorNeedsView()) {
constructor.addStatement("super(target, source)");
} else if (constructorNeedsView()) {
constructor.addStatement("super(target, source.getContext())");
} else {
constructor.addStatement("super(target, context)");
}
constructor.addCode("\n");
}
if (hasTargetField()) {
constructor.addStatement("this.target = target");
constructor.addCode("\n");
}
if (hasViewBindings()) {
if (hasViewLocal()) {
// Local variable in which all views will be temporarily stored.
constructor.addStatement("$T view", VIEW);
}
for (ViewBinding binding : viewBindings) {
addViewBinding(constructor, binding, debuggable);
}
for (FieldCollectionViewBinding binding : collectionBindings) {
constructor.addStatement("$L", binding.render(debuggable));
}
if (!resourceBindings.isEmpty()) {
constructor.addCode("\n");
}
}
if (!resourceBindings.isEmpty()) {
if (constructorNeedsView()) {
constructor.addStatement("$T context = source.getContext()", CONTEXT);
}
if (hasResourceBindingsNeedingResource(sdk)) {
constructor.addStatement("$T res = context.getResources()", RESOURCES);
}
for (ResourceBinding binding : resourceBindings) {
constructor.addStatement("$L", binding.render(sdk));
}
}
return constructor.build();
}
大功告成!所有的邏輯執(zhí)行完畢之后怎囚,就生成是我們3.2節(jié)對應(yīng)的代碼卿叽。
4、總結(jié)
祝各位司機漂移成功!