ButterKnife源碼拆輪子學習

前提

Version 10.1.0java

地址 github.com/JakeWharton…android

ButterKnife有兩種實現方式git

  1. 定義註解,在運行時利用反射實現。
  2. 定義註解,在編譯時利用APT生成固定格式的源文件。

源碼下載編譯遇到的一個問題github

直接git clone的Project的名稱是butterknife,和裏面的一個Module重名,致使gradle sync失敗,須要clone時修改下名稱。bash

使用

Application Module和Library Module在使用上有點差異。app

  1. Application Module 1.1 build.gradle 添加依賴
android {
  ...
  // Butterknife requires Java 8.
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

dependencies {
  implementation 'com.jakewharton:butterknife:10.1.0'
  annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
}
複製代碼

1.2 使用maven

不能給private或static添加,不然報錯ide

class ExampleActivity extends Activity {
  @BindView(R.id.user) EditText username;
  @BindView(R.id.pass) EditText password;

  @BindString(R.string.login_error) String loginErrorMessage;

  @OnClick(R.id.submit) void submit() {
    // TODO call server...
  }

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
    // TODO Use fields...
  }
}
複製代碼
  1. Library Module 2.1 添加依賴

project.gradle函數

buildscript {
  repositories {
    mavenCentral()
    google()
   }
  dependencies {
    classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0'
  }
}
複製代碼

build.gradle源碼分析

android {
  ...
  // Butterknife requires Java 8.
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

dependencies {
  implementation 'com.jakewharton:butterknife:10.1.0'
  annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
}
複製代碼

添加plugin

apply plugin: 'com.jakewharton.butterknife'
複製代碼

2.2 使用 須要用R2替換掉R,由於註解的值只支持常量,而Library Module中的R變量再也不是常量,ButterKnife生成的R2變量都是常量。

class ExampleActivity extends Activity {
  @BindView(R2.id.user) EditText username;
  @BindView(R2.id.pass) EditText password;
  ...
}
複製代碼

源碼分析 先看下源碼包結構

可見 支持反射實現和APT實現

  1. 首先分析APT的實現方式

(1) 從入口ButterKnife.bind()開始

/**
   * BindView annotated fields and methods in the specified {@code target} using the {@code source}
   * {@link View} as the view root.
   *
   * @param target Target class for view binding.
   * @param source View root on which IDs will be looked up.
   */
  @NonNull @UiThread
  public static Unbinder bind(@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 {
        ......
    }
  }
複製代碼

主要是經過綁定的類,獲取繼承Unbinder的類的構造方法,利用反射建立對象。

@Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    // 若是已經獲取保存過,則直接返回
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    // 若是是系統類,則返回null
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")
        || clsName.startsWith("androidx.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    // 獲取類名+"_ViewBinding"的類的構造函數
    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 () {
        ......
    }
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }
複製代碼

返回類名+"_ViewBinding"的類的構造函數。該類是如何產生的呢?

先別急,這個就是APT在編譯時生成的。咱們先看下這個類的代碼。在Project中build下工程。

public static View findRequiredView(View source, @IdRes int id, String who) {
    View view = source.findViewById(id);
    if (view != null) {
      return view;
    }
    ......
  }
複製代碼

可見,View的獲取,仍是經過Android提供的findViewById,和類型轉換。

view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.show(p0);
      }
    });
複製代碼

點擊事件,封裝了一層DebouncingOnClickListener用來防快速點擊。 這種防快速點擊的方式咱們也能夠直接應用到咱們的工程中。

Context context = source.getContext();
Resources res = context.getResources();
target.appname = res.getString(R.string.app_name);
複製代碼

獲取資源的方式,也是經過Resource。

(2) 下面分析下,如何在編譯時生成類

關於APT技術和如何實現就不細說了,網上都有,很簡單。

因此先從ButterKnifeProcessor proces方法開始講起。

@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;
  }
複製代碼

具體生成代碼的邏輯就不細說了,主要就是經過APT技術,遍歷文件,查找自定義註解,封裝成固定格式的javapoet的類,再生成文件。

  • 第一行,遍歷全部自定義Bind註解,存到Map中
  • 第二行,生成javapoet中的JavaFile對象
  • 第三行,利用JavaFile對象生成具體文件

(3) 說下ButterKnife如何在Module中生成R2文件,將資源定義成常量,編譯在註解中使用,這個咱們開發中也能夠借鑑。 juejin.im/post/5ce3aa…

相關文章
相關標籤/搜索