ButterKnifeソース読み


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から継承する)
    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
    }  
    
  • を返す.
  • f i n d BindingConstructorForClass:butterknifeによって生成されたActivityバインドクラスのコンストラクタを取得する
  • BINDINGSは、コンストラクタ対応バインディングクラス名のキャッシュセット
  • である.
  • BINDINGSにより、バインディングクラスは、Unbinderクラスから継承された
  • であることがわかる.
  • オブジェクトバインドクラスをクエリーする場合、クエリーがない場合は、androidオリジナルクラスまで親クラスのバインドクラスを再帰的にクエリーし続けます
  • .
      //     
      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バインドクラスの構造
  • バインドクラスはappbuildgeneratedsourceaptdebugcomにあります.haha.updateass.MainActivity_ViewBinding
  • MainActivity_ViewBindingのコンストラクション関数は、上の反射作成バインディングクラスに対応する
  • に対応しています.
  • findRequiredViewでButtonリソースを検索し、castViewでActivity対応のButtonオブジェクトに変換し、クリックリスニング
  • を追加する
  • Unbinderオブジェクトのunbindメソッドを呼び出すと、リソース関連付けはnull
  • に設定されます.
    // 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;
      }
    }
    
    
  • findRequiredView:findViewByIdで対応するリソースを検索
  • メソッドはD:githubbutterknife-masterbutterknife-runtimesrcmainjavabutterknifeinternalUtilsにある.java
  •   public static View findRequiredView(View source, @IdRes int id, String who) {
        View view = source.findViewById(id);
        ...
      }
    
  • castView:viewを対応するclassタイプに強く変換
  • メソッドはD:githubbutterknife-masterbutterknife-runtimesrcmainjavabutterknifeinternalUtilsにある.java
  •   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)はgoogleが提供してくれた自動的にjavacに注釈を登録する方法で、手動で注釈
    @AutoService(Processor.class)
    @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC)
    @SuppressWarnings("NullAway") // TODO fix all these...
    public final class ButterKnifeProcessor extends AbstractProcessor {
        ...
    }
    
  • を構成しないようにしています.
  • ButterKnifeProcessorはAbstractProcessorを継承し、いくつかの重要な方法を書き換えた.
  • init(ProcessingEnvironment env)ではsdkバージョンを判断し、Types、Filer、Treesなどの例を取得しました.ここで、Filerは主にクラスファイル
  • などの新しいファイルの作成に使用されます.
  • getSupportedOptions()注釈でサポートされている構成項目
  • を取得
  • getSupportedAnnotationType()サポートされている注釈タイプ
    @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;
    }
      
    
  • を取得
  • process(Set extends TypeElement>elements,RoundEnvironment env)コアメソッド、注釈のクエリーと処理、javaファイル
  • の生成

    2.2 process
  • 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;
      }
    
  • をカプセル化する.
  • findAndParseTargetsクエリButterKnife bind注記タイプを持つすべての要素のセットを取得します.bindViewを例にとると
      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;
      }
    
  • です.
  • parseBindView解析view注記の情報はBindingSetにカプセル化され、TypeElementに対応してセットに格納される
      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);
      }
    
    
  • BindingSet:bindクラスの注釈要素情報をカプセル化し、特定のクラス
    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オープンソースライブラリで完了する.
  • brewJava
  •   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技術が使われていますが、もちろん学ぶべきところもたくさんあります.良いオープンソースフレームワークごとにキャッシュメカニズムやモードが少なくありません.
  • 参照:https://blog.csdn.net/gdutxiaoxu/article/details/71512754
  • リファレンス:JavaPoetの使用マニュアルhttps://blog.csdn.net/l540675759/article/details/82931785