ButterKnife源碼研究一宏觀

背景資料:
源碼版本: ButterKnife 8.5.1
編譯工具: Android Studio 2.2.1
java版本: 1.8.0_101_b13

在這篇文章的前面可能有些混亂,那是因爲一直在找思路,不會去特意整理,這樣才能體現我的思考過程。
Java Annotation processing 是javac中用於編譯時掃描和解析Java註解的工具
自定義註解,並且自己定義解析器來處理它們。Annotation processing是在編譯階段執行的,它的原理就是讀入Java源代碼,解析註解,然後生成新的Java代碼。新生成的Java代碼最後被編譯成Java字節碼,註解解析器(Annotation Processor)不能改變讀入的Java 類,比如不能加入或刪除Java方法

ButterKnife 主要是實例化 View 或者給某個 View 添加各種事件 Listenters,那爲什麼在編譯的時候會跑ButterKnifeProcessor這個類呢? 在哪裏定義?
Activity啓動時,ButterKnife.bind(this) 是如何加載對應的ViewBinder類中的方法的?
對編譯原理 加載原理 運行原理 不大懂

手動打包 生成R文件 編譯 打包 簽名 Zipalign Upload Run,這些手動做了一下,發現AS中可以Build生成這些文件
在...\build\intermediates\classes\debug\com\example\butterknife\library下發現class文件
在...\build\generated\source\apt\debug\com\example\butterknife\library發現生成的_ViewBinding.java文件。

註解就是一個繼承自`java.lang.annotation.Annotation`的接口。
簡單來說就是java通過動態代理的方式爲你生成了一個實現了"接口"`TestAnnotation`的實例(對於當前的實體來說,例如類、方法、屬性域等,這個代理對象是單例的),然後對該代理實例的屬性賦值,這樣就可以在程序運行時(如果將註解設置爲運行時可見的話)通過反射獲取到註解的配置信息。

ButterKnife 工作流程
當你編譯你的Android工程時,ButterKnife工程中 ButterKnifeProcessor 類的 process() 方法會執行以下操作:
  • 開始它會掃描Java代碼中所有的ButterKnife註解 @Bind 、 @OnClick 、 @OnItemClicked 等
  • 當它發現一個類中含有任何一個註解時, ButterKnifeProcessor 會幫你生成一個Java類,名字類似 <className>$$ViewBinder ,這個新生成的類實現了 ViewBinder<T> 接口
  • 這個 ViewBinder 類中包含了所有對應的代碼,比如 @Bind 註解對應 findViewById() , @OnClick 對應了 view.setOnClickListener() 等等
  • 最後當Activity啓動 ButterKnife.bind(this) 執行時,ButterKnife會去加載對應的 ViewBinder 類調用它們的 bind() 方法

資料看的差不多了,開始分析, 在Activity中使用代碼
@BindView(R.id.title)
TextView title;
@BindView(R.id.subtitle)
TextView subtitle;
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
點擊bind()方法進入ButterKnife類
@NonNull
@UiThread
public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    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);
    }
}
這裏的BINDINGS是一個Map<Class<?>, Constructor<? extends Unbinder>> 集合
@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;
}
這個集合會將APT在編譯的時候生成的 \build\generated\source\apt\debug\…SimpleActivity_ViewBinding.class, 源代碼如下
public class SimpleActivity_ViewBinding implements Unbinder {
  private SimpleActivity target;

  private View view2130968578;

  private View view2130968579;

  @UiThread
  public SimpleActivity_ViewBinding(SimpleActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public SimpleActivity_ViewBinding(final SimpleActivity target, View source) {
    this.target = target;

    View view;
    target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
    target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
    view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
    target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
    view2130968578 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.sayHello();
      }
    });
    view.setOnLongClickListener(new View.OnLongClickListener() {
      @Override
      public boolean onLongClick(View p0) {
        return target.sayGetOffMe();
      }
    });
    view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
    target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);
    view2130968579 = view;
    ((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
        target.onItemClick(p2);
      }
    });
    target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
    target.headerViews = Utils.listOf(
        Utils.findRequiredView(source, R.id.title, "field 'headerViews'"),
        Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"),
        Utils.findRequiredView(source, R.id.hello, "field 'headerViews'"));

    Context context = source.getContext();
    Resources res = context.getResources();
    target.butterKnife = res.getString(R.string.app_name);
    target.fieldMethod = res.getString(R.string.field_method);
    target.byJakeWharton = res.getString(R.string.by_jake_wharton);
    target.sayHello = res.getString(R.string.say_hello);
  }

  @Override
  @CallSuper
  public void unbind() {
    SimpleActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;

    target.title = null;
    target.subtitle = null;
    target.hello = null;
    target.listOfThings = null;
    target.footer = null;
    target.headerViews = null;

    view2130968578.setOnClickListener(null);
    view2130968578.setOnLongClickListener(null);
    view2130968578 = null;
    ((AdapterView<?>) view2130968579).setOnItemClickListener(null);
    view2130968579 = null;
  }
}
這裏讓我鬱悶的是這東西是怎麼生成的,註解只是一個接口,@BindView雖然這樣寫但是如果開發人員不使用APT工具來提取和處理Annotation信息,《瘋狂java講義》上說可以使用反射獲取該類的AnnotatedElement接口來接受註解的信息,因爲它是Class,Method,Constructor的父接口,但是這只是獲取,屬於APT(Annotation Processing Tool)的一部分,APT是一種註解處理工具,在前面說過,要使用工具來提取和處理Annotation信息,否則註解毫無意義,APT對源碼進行檢測,找出Annotation信息,生成額外的源文件和其他文件(由APT編寫者決定),APT會編譯生成的源文件和原來的源文件一起合成一個新的class文件,簡單理解是APT可以在編譯期間做一些其他維護工作,那問題是APT如何編寫呢? 

Java提供的javac.exe工具有一個-processor選項,可指定一個Annotation處理器,該處理器需要實現javax.annotation.processing包下的Processor接口,爲了方便一般繼承AbstractProcessor來實現處理器。可以使用命令java -processorpath ‘XXXAnnotationProcessor’ XXX.java    
現在大致知道了一些東西了,但是有些混亂,重新來看一下,@BindView會通過APT生成額外的class文件放在 \build\generated\source\apt\debug\下,然後通過ButterKnife.bind(this)來將代碼合併在一起。 那麼現在問題是如何以及何時調用APT呢
如何: 在源碼中找到了一個ButterKnifeProcessor類繼承自AbstractProcessor 
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
何時: 在註解處理器類中有個註解@AutoService(Processor.class) 這個是google開發的用來解決APT更加方便使用問題的
在github原話是這樣的
    “AutoService will generate the file META-INF/services/javax.annotation.processing.Processor in the output classes folder. In the case of javax.annotation.processing.Processor, if this metadata file is included in a jar, and that jar is on javac's classpath, then javac willautomatically load it, and include it in its normal annotation processing environment.”

至此原理大致懂了,但是很多細節並不懂,不過能站在宏觀的角度看問題感覺不錯。

I'm fish,I'm on.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章