ButterKnife 原理解析


一、使用方法

  1、添加依賴。

  

implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

  2、使用。

public class MainActivity extends AppCompatActivity {
    // 1、控件id綁定
    @BindView(R.id.myBtn)
    Button myBtn;

    Unbinder unbinder = null;
    // 2、點擊事件綁定
    @OnClick(R.id.myBtn)
    public void click() {
        Toast.makeText(this,"btn click",Toast.LENGTH_SHORT).show();
        myBtn.setText("hello world");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //3、activity注冊
        unbinder = ButterKnife.bind(this);
    }

    @Override
    protected void onDestroy() {
        //4、activity 取消注冊
        unbinder.unbind();
        super.onDestroy();
    }
}

 

  3、編譯運行。

二、原理解析

  很明顯的我們可以看出,ButterKnife.bind(this)   是 activity和ButterKnife建立關系的地方,我們從這里入手分析。

----->>點擊進入    bind

public static Unbinder bind(@NonNull Activity target) {
    //獲取decorView 就是頁面的跟布局View 本質 是FrameLayout
  View sourceView = target.getWindow().getDecorView();
// 獲取unbinder    
  return createBinding(target, sourceView);
}
 

---->> 點擊進入  createBinding

  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);
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      }
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      throw new RuntimeException("Unable to create binding instance.", cause);
    }
  }

 

 主要的過程就是

生成constructor 先findBindingConstructorForClass

  如果找不到就 返回  Unbinder.EMPTY,這里的Unbinder.EMPTY就是new Unbinder() 然后直接結束函數 ,不做處理,

  如果得到constructor就 調用constructor.newInstance得到一個unbinder返回,我們稍微看一下newInstance 返回的是一個泛型,至於是在何時傳入的泛型,我們先保留下來。

 

 public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (serializationClass == null) {
            return newInstance0(initargs);
        } else {
            return (T) newInstanceFromSerialization(serializationCtor, serializationClass);
        }
    }

 

我們繼續探查他是如何得到construtor的

---->>點擊進入 findBindingConstructorForClass

 

  @Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
  // 1、先從BINDINGS 里邊獲取construtor
 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 {
 // 2、如果沒有就,利用反射生成construtor
      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);
    }
// 3、將生成的construtor 放入BINDINGS做備份
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }

我們重點看,生成construtor這一段:

String clsName = cls.getName();

Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");

bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);

 

  1、這里向Contrutor傳入Unbinder 泛型,這就能夠解釋newInstance的返回值是Unbinder。

  2、這里泛型使用的類名(clsName + "_ViewBinding")中,竟然包含了我們傳進來的類名,這里完整的類名就是MainActivity_ViewBinding   ,能夠反射獲取實例,說明這個類是確實存在的,二我們並沒有編寫相關的類,而在app運行過程更不可能產生類,那就只能是,app運行之前 由ButterKnife 生成的

我們不妨看看這個類里有什么:

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;
  private View view2131165258;
  @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.myBtn, "field 'myBtn' and method 'click'");
    target.myBtn = Utils.castView(view, R.id.myBtn, "field 'myBtn'", Button.class);
    view2131165258 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.click();
      }
    });
  }
  @Override
  @CallSuper
  public void unbind() {
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;
    target.myBtn = null;
    view2131165258.setOnClickListener(null);
    view2131165258 = null;
  }
}

 

 

 從代碼中我們看到了 我們view的id以及activity中的變量名,可以聯想到,是我們添加Bind注解時傳進來的。

---->>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 '"
        .......
  }

 

    我們終於看到了  findViewById。

---->>  castView  傳入 view  傳入 class  ,很明顯是轉型使用的。

 

 

  還有一個問題就是ButterKnife 如何能夠在運行前根據我們的代碼 ,生成相應的  _ViewBinding  文件的,請繼續看。

 

 

 

三、注解處理器

  在添加依賴時我們還添加了一個,annotationProcessor,就是完成呢些文件生成的。

 
        
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
   這其中涉及到了annotationProcessor技術,和 APT(Annotation Processing Tool)技術,他是一種注解處理器,
在項目編譯期可以對源代碼進行掃描,找出存活時間為RetentionPolicy.CLASS的指定注解,然后對注解進行解析處理。
  至於后邊java類的生成,涉及到了JavaPoet技術



  這里我們先看用注解處理器收集類信息的過程,之前我們已經在app的 build.gradle引入了 ButterKnife 的注解處理器: butterknife-compiler,其中有一個ButterKnifeProcessor 類完成了注解處理器的核心邏輯。

@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
  
    @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
        String sdk = env.getOptions().get(OPTION_SDK_INT);
        ......
        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) {
        }
    }

    @Override
    public Set<String> getSupportedOptions() {
        return ImmutableSet.of(OPTION_SDK_INT, OPTION_DEBUGGABLE);
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
            types.add(annotation.getCanonicalName());
        }
        return types;
    }

    @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;
    }

   @Override
   public SourceVersion getSupportedSourceVersion() {
       return SourceVersion.latestSupported();
   }
}

 

注意,ButterKnifeProcessor類上使用了@AutoService(Processor.class)注解,來實現注解處理器的注冊,注冊到 javac 后,在項目編譯時就能執行注解處理器了。

ButterKnifeProcessor繼承了AbstractProcessor抽象類,並重寫以上五個方法,如果我們自定義解處理器也是類似的,看下這幾個方法:

1、init()

首先 init() 方法完成sdk版本的判斷以及相關幫助類的初始化,幫助類主要有以下幾個:

  • Elements elementUtils,注解處理器運行掃描源文件時,以獲取元素(Element)相關的信息。Element 有以下幾個子類:
    包(PackageElement)、類(TypeElement)、成員變量(VariableElement)、方法(ExecutableElement)
  • Types typeUtils,
  • Filer filer,用來生成 java 類文件。
  • Trees trees,
2、getSupportedAnnotationTypes()

該方法返回一個Set<String>,代表ButterKnifeProcessor要處理的注解類的名稱集合,即 ButterKnife 支持的注解:butterknife-annotations

3、getSupportedSourceVersion()

返回當前系統支持的 java 版本。

4、getSupportedOptions()

返回注解處理器可處理的注解操作。

5、process()

最后,process() 方法是我們要重點分析的,在這里完成了目標類信息的收集並生成對應 java 類。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM