ButterKnifeソース読み
14116 ワード
ButterKnifeフレームワーク butterknife:バインドされたエントリ、ビューへの転送、およびtargetターゲット を提供する butterknife-annotations:view,onclickなどのパラメータ、メソッド、メンバー変数などの実行時注釈情報 を定義する butterknife-compiler:注釈プロセッサ(コア) をカプセル化 butterknife-reflect butterknife-runtime
1.バインドプロセス
1.1 butterknifeモジュールはUIオブジェクトをバインドするエントリを実現する
バインドactivityを例に分析を開始 ButterKnife.bind(Activity activity):bind法によりActivityおよびActivityトップレベルのViewをパラメータとして に渡す.でbutterknifeは、注釈オブジェクト(すなわちActivity)に基づいて対応するViewBindingクラスを生成します.この生成プロセスは、注釈プロセッサで説明されています. は、反射によってバインディングクラスのインスタンスオブジェクト、すなわち、Unbinderオブジェクト(バインディングクラスはUnbinderから継承する) を返す. f i n d BindingConstructorForClass:butterknifeによって生成されたActivityバインドクラスのコンストラクタを取得する BINDINGSは、コンストラクタ対応バインディングクラス名のキャッシュセット である. BINDINGSにより、バインディングクラスは、Unbinderクラスから継承された であることがわかる.オブジェクトバインドクラスをクエリーする場合、クエリーがない場合は、androidオリジナルクラスまで親クラスのバインドクラスを再帰的にクエリーし続けます .
1.2バインドクラスの構造バインドクラスはappbuildgeneratedsourceaptdebugcomにあります.haha.updateass.MainActivity_ViewBinding MainActivity_ViewBindingのコンストラクション関数は、上の反射作成バインディングクラスに対応する に対応しています. findRequiredViewでButtonリソースを検索し、castViewでActivity対応のButtonオブジェクトに変換し、クリックリスニング を追加する Unbinderオブジェクトのunbindメソッドを呼び出すと、リソース関連付けはnull に設定されます. findRequiredView:findViewByIdで対応するリソースを検索 メソッドはD:githubbutterknife-masterbutterknife-runtimesrcmainjavabutterknifeinternalUtilsにある.java castView:viewを対応するclassタイプに強く変換 メソッドはD:githubbutterknife-masterbutterknife-runtimesrcmainjavabutterknifeinternalUtilsにある.java
ここで疑問に思うのは、バインドクラスでリソースを検索したり、リソースタイプを強く回したりすることは理解できますが、これらのリソース情報はどのように生成されますか?次に、注釈プロセッサButterknife-Processorを参照してください.
2.butterknife注記プロセッサ
Android注釈プロセッサの学習を通じて、butterknifeがカスタマイズした注釈プロセッサButterKnifeProcessorを見ることができます.彼はAbstractProcessorを継承することによって対応する方法を実現します.Android注釈器の使用についてはlinkを参照してください
2.1注釈の登録@AutoService(Processor.class)はgoogleが提供してくれた自動的にjavacに注釈を登録する方法で、手動で注釈 を構成しないようにしています. ButterKnifeProcessorはAbstractProcessorを継承し、いくつかの重要な方法を書き換えた. init(ProcessingEnvironment env)ではsdkバージョンを判断し、Types、Filer、Treesなどの例を取得しました.ここで、Filerは主にクラスファイル などの新しいファイルの作成に使用されます. getSupportedOptions()注釈でサポートされている構成項目 を取得 getSupportedAnnotationType()サポートされている注釈タイプ を取得 process(Set extends TypeElement>elements,RoundEnvironment env)コアメソッド、注釈のクエリーと処理、javaファイル の生成
2.2 process processメソッドは、すべての注釈付きノードを問合せ、 をカプセル化する. findAndParseTargetsクエリButterKnife bind注記タイプを持つすべての要素のセットを取得します.bindViewを例にとると です. parseBindView解析view注記の情報はBindingSetにカプセル化され、TypeElementに対応してセットに格納される BindingSet:bindクラスの注釈要素情報をカプセル化し、特定のクラス を生成する
3.バインドクラスの生成
JavaPoetテクノロジーの具体的な参考になりますhttps://blog.csdn.net/l540675759/article/details/82931785
上記processでBindingSetを取得した後、そのbrewJava(sdk,debuggable)メソッドによりJavaFileオブジェクトを生成し、JavaFileを生成する方法はJavaPoetオープンソースライブラリで完了する. brewJava
4.末尾
ButterKnife全体のフレームワーク原理は簡単で,まず対応するバインドクラスを生成し,次に反射によりバインドクラスのインスタンス化と登録を完了する.しかし,注釈対応に基づいてバインドクラスを生成し,細部と内容をカプセル化しすぎる.中にはannotationProcessorやJavaPoet技術が使われていますが、もちろん学ぶべきところもたくさんあります.良いオープンソースフレームワークごとにキャッシュメカニズムやモードが少なくありません.参照:https://blog.csdn.net/gdutxiaoxu/article/details/71512754 リファレンス:JavaPoetの使用マニュアルhttps://blog.csdn.net/l540675759/article/details/82931785
1.バインドプロセス
1.1 butterknifeモジュールはUIオブジェクトをバインドするエントリを実現する
バインドactivityを例に分析を開始
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();//1.1 Activity view
return bind(target, sourceView);
}
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
Class> targetClass = target.getClass();//1.2
//1.3 Activity class , clsName + "_ViewBinding"
Constructor extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
...
//1.9
return constructor.newInstance(target, source);//try-catch
}
、 //
static final Map, Constructor extends Unbinder>> BINDINGS = new LinkedHashMap<>();
private static Constructor extends Unbinder> findBindingConstructorForClass(Class> cls) {
//1.4
Constructor extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
return bindingCtor;
}
//1.5 : ,
// android ,
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")|| clsName.startsWith("androidx."))
return null;
try {//1.6
Class> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
bindingCtor = (Constructor extends Unbinder>) bindingClass.getConstructor(cls, View.class);
} catch (ClassNotFoundException e) {
//1.7 ( ),
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
...
}
BINDINGS.put(cls, bindingCtor);// 1.8
return bindingCtor;
}
1.2バインドクラスの構造
// Generated code from Butter Knife. Do not modify!
package com.haha.updateass;
import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.Button;
import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view2131165219;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.bt_load, "field 'btLoad' and method 'onViewClicked'");
target.btLoad = Utils.castView(view, R.id.bt_load, "field 'btLoad'", Button.class);
view2131165219 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onViewClicked(p0);
}
});
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.btLoad = null;
view2131165219.setOnClickListener(null);
view2131165219 = null;
}
}
public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
...
}
public static T castView(View view, @IdRes int id, String who, Class cls) {
return cls.cast(view);//try-catch
...
}
ここで疑問に思うのは、バインドクラスでリソースを検索したり、リソースタイプを強く回したりすることは理解できますが、これらのリソース情報はどのように生成されますか?次に、注釈プロセッサButterknife-Processorを参照してください.
2.butterknife注記プロセッサ
Android注釈プロセッサの学習を通じて、butterknifeがカスタマイズした注釈プロセッサButterKnifeProcessorを見ることができます.彼はAbstractProcessorを継承することによって対応する方法を実現します.Android注釈器の使用についてはlinkを参照してください
2.1注釈の登録
@AutoService(Processor.class)
@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC)
@SuppressWarnings("NullAway") // TODO fix all these...
public final class ButterKnifeProcessor extends AbstractProcessor {
...
}
@Override public Set getSupportedAnnotationTypes() {
Set types = new LinkedHashSet<>();
for (Class extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
private Set> getSupportedAnnotations() {
Set> annotations = new LinkedHashSet<>();
// ButterKnife
annotations.add(BindAnim.class);
annotations.add(BindArray.class);
annotations.add(BindBitmap.class);
...
return annotations;
}
2.2 process
@Override public boolean procesButterKnifes(Set extends TypeElement> elements, RoundEnvironment env) {
//1. ButterKnife
Map bindingMap = findAndParseTargets(env);
//2. bind , java
for (Map.Entry 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;
}
private Map findAndParseTargets(RoundEnvironment env) {
Map builderMap = new LinkedHashMap<>();
Set erasedTargetNames = new LinkedHashSet<>();
... // BindInt.class
// BindView Element
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
try {
// BindView
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
...//
Map classpathBindings =
findAllSupertypeBindings(builderMap, erasedTargetNames);
// builderMap
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, classpathBindings.keySet());
if (parentType == null) {
// TypeElement, BindingSet.Builder
bindingMap.put(type, builder.build());
} else {
BindingInformationProvider parentBinding = bindingMap.get(parentType);
if (parentBinding == null) {
parentBinding = classpathBindings.get(parentType);
}
if (parentBinding != null) {
// , setParent BindingSet
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
} else {
// , 。
entries.addLast(entry);
}
}
}
return bindingMap;
}
private void parseBindView(Element element, Map builderMap,
Set erasedTargetNames) {
//getEnclosingElement ( ) 。
// , view
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// fields andriod api
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
// View 。
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
Name qualifiedName = enclosingElement.getQualifiedName();
Name simpleName = element.getSimpleName();
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
...// View , ,
}
if (hasError) return;
// View , view id
int id = element.getAnnotation(BindView.class).value();
//
BindingSet.Builder builder = builderMap.get(enclosingElement);
Id resourceId = elementToId(element, BindView.class, id);
if (builder != null) {
... // View , id ,
} else {
// BindingSet.Builder, enclosingElement builderMap
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
// builder
builder.addField(resourceId, new FieldViewBinding(name, type, required));
// view erasedTargetNames
erasedTargetNames.add(enclosingElement);
}
private BindingSet(
TypeName targetTypeName, ClassName bindingClassName, TypeElement enclosingElement,
boolean isFinal, boolean isView, boolean isActivity, boolean isDialog,
ImmutableList viewBindings,
ImmutableList collectionBindings,
ImmutableList resourceBindings,
@Nullable BindingInformationProvider parentBinding) {
this.isFinal = isFinal;
this.targetTypeName = targetTypeName;
this.bindingClassName = bindingClassName;
this.enclosingElement = enclosingElement;
this.isView = isView;
this.isActivity = isActivity;
this.isDialog = isDialog;
this.viewBindings = viewBindings;
this.collectionBindings = collectionBindings;
this.resourceBindings = resourceBindings;
this.parentBinding = parentBinding;
}
3.バインドクラスの生成
JavaPoetテクノロジーの具体的な参考になりますhttps://blog.csdn.net/l540675759/article/details/82931785
上記processでBindingSetを取得した後、そのbrewJava(sdk,debuggable)メソッドによりJavaFileオブジェクトを生成し、JavaFileを生成する方法はJavaPoetオープンソースライブラリで完了する.
JavaFile brewJava(int sdk, boolean debuggable) {
//TypeSpec , ,
//createType TypeSpec
TypeSpec bindingConfiguration = createType(sdk, debuggable);
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
4.末尾
ButterKnife全体のフレームワーク原理は簡単で,まず対応するバインドクラスを生成し,次に反射によりバインドクラスのインスタンス化と登録を完了する.しかし,注釈対応に基づいてバインドクラスを生成し,細部と内容をカプセル化しすぎる.中にはannotationProcessorやJavaPoet技術が使われていますが、もちろん学ぶべきところもたくさんあります.良いオープンソースフレームワークごとにキャッシュメカニズムやモードが少なくありません.