Android APT注解處理器

一、編譯時(shí)技術(shù)簡(jiǎn)介
APT ( Annotation Processing Tool ) 注解處理工具 ;

編譯時(shí)技術(shù) , 廣泛應(yīng)用在當(dāng)前主流框架中 , 如 JetPack 中的 DataBinding , Room , Navigatoion , 第三方 ButterKnife , ARouter 等框架 ;

編譯時(shí)技術(shù) 最重要的作用就是在編譯時(shí)可以 生成模板代碼 ;
由于生成代碼操作是在編譯時(shí)進(jìn)行的 , 不會(huì)對(duì)運(yùn)行時(shí)的性能產(chǎn)生影響 ;

程序的周期 :
源碼期 : 開(kāi)發(fā)時(shí) , 剛編寫(xiě)完 " .java " 代碼 , 還未編譯之前 , 就處于源碼期 ;
編譯期 : 程序由 java 源碼編譯成 class 字節(jié)碼文件 ;
運(yùn)行期 : 將字節(jié)碼文件加載到 Java 虛擬機(jī)中運(yùn)行 ;

編譯時(shí)技術(shù) APT 作用于 編譯期 , 在這個(gè)過(guò)程中使用該技術(shù) , 生成代碼 ;
編譯時(shí)技術(shù) 2 22 大核心要素 : 在編譯時(shí) , 執(zhí)行生成代碼的邏輯 , 涉及到兩個(gè)重要概念 ;
① 編譯時(shí)注解 ;
② 注解處理器 ;

舉例說(shuō)明 : 使用 ButterKnife 時(shí)會(huì)依賴(lài)兩個(gè)庫(kù) ,

dependencies {
    implementation 'com.jakewharton:butterknife:10.2.3'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
}

其中
com.jakewharton:butterknife:10.2.3 是 編譯時(shí)的注解 ,
com.jakewharton:butterknife-compiler:10.2.3 是 注解處理器 ;

二畏铆、ButterKnife 原理分析
使用 ButterKnife :

① 添加依賴(lài) :

dependencies {
    implementation 'com.jakewharton:butterknife:10.2.3'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
}

② Activity 中使用 ButterKnife :

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import butterknife.BindView;
import butterknife.ButterKnife;

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv1)
    TextView tv1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        tv1.setText("ButterKnife");
    }
}

BindView 注解分析 : 在 TextView tv1成員變量處添加了 @BindView(R.id.tv1) 注解 ;
@Target(FIELD) 元注解 : 表示其作用與類(lèi)的成員字段 ;
@Retention(RUNTIME) 元注解 : 表示該注解保留到運(yùn)行時(shí)階段 ;
int value() 注解屬性 : 只有一個(gè)注解屬性 , 并且屬性名是 value , 則使用注解時(shí) “value =” 可省略 ;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME) //注解的生命周期雷袋,保留時(shí)間到類(lèi)文件級(jí)別
@Target(ElementType.FIELD) //作用域在字段上
public @interface BindView {
    int value();
}

TextView tv1 需要使用 findViewById 進(jìn)行賦值 , 在上述代碼中沒(méi)有寫(xiě) findViewById 相關(guān)的代碼 ; 肯定是在某個(gè)地方執(zhí)行了 findViewById 的方法 ;
ButterKnife.bind(this) 代碼就是執(zhí)行了 findViewById 方法 ;

ButterKnife 用到了編譯時(shí)技術(shù)會(huì)在項(xiàng)目編譯時(shí) , 會(huì)生成 MainActivity_ViewBinding 類(lèi) , 在該類(lèi)中 , 會(huì)查找添加了 @BindView 直接的成員變量 , 再獲取 注解屬性 value 的值 , 然后調(diào)用 findViewById 方法獲取組件并為成員變量賦值 ;

// Generated code from Butter Knife. Do not modify!

import android.view.View;
import android.widget.TextView;
import androidx.annotation.CallSuper;
import androidx.annotation.UiThread;
import butterknife.Unbinder;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public MainActivity_ViewBinding(MainActivity target, View source) {
    this.target = target;

    target.tv1= Utils.findRequiredViewAsType(source, R.id.tv1, "field 'tv1'", TextView.class);
  }

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

    target.hello = null;
  }
}

三、ButterKnife 生成 Activity_ViewBinding 代碼分析
調(diào)用 ButterKnife 靜態(tài)方法 Unbinder bind(@NonNull Activity target) , 傳入 Activity 對(duì)象 , 在方法中調(diào)用了 ButterKnife 的 bind 方法 ;

在 bind 方法中 , 先獲取了 Activity 的類(lèi)對(duì)象 ,

Class<?> targetClass = target.getClass();

然后將類(lèi)對(duì)象傳入了 findBindingConstructorForClass 方法 ,

Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

在 findBindingConstructorForClass 方法中 , 獲取了某個(gè)構(gòu)造方法 ,

Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);

獲取 Activity 類(lèi)對(duì)象名稱(chēng) , 即 " com.butterknife.demo.MainActivity " ,

String clsName = cls.getName();

得到名稱(chēng)后 , 判斷該類(lèi)對(duì)象是否是系統(tǒng)的 API , 如果是則退出 ; 如果不是 , 則繼續(xù)向下執(zhí)行 ,

if (clsName.startsWith("android.") || clsName.startsWith("java.")
    || clsName.startsWith("androidx.")) {
  if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
  return null;
}

拼裝要生成的類(lèi)名稱(chēng) , “com.butterknife.demo.MainActivity_ViewBinding” , 并自動(dòng)生成該類(lèi) ;

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

ButterKnife 涉及到的源碼 :

public final class ButterKnife {
  /**
   * BindView annotated fields and methods in the specified {@link Activity}. The current content
   * view is used as the view root.
   *
   * @param target Target activity for view binding.
   */
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
  }

  /**
   * 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 (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);
    }
  }

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

了解了butterKnife原理之后辞居,接下來(lái)字節(jié)手寫(xiě)butterknife注解實(shí)現(xiàn)楷怒。

四、創(chuàng)建 編譯時(shí)注解 和 注解處理器

4.1 使用 Android Studio 開(kāi)發(fā) Android 項(xiàng)目時(shí) , 使用到編譯時(shí)技術(shù) , 都要用到 編譯時(shí)注解 和 注解處理器 ;
編譯時(shí)注解 和 注解處理器 一般都創(chuàng)建為 Java or Kotlin Library 類(lèi)型的 Module ;
右鍵點(diǎn)擊工程名 , 選擇 " New / Module " 選項(xiàng) ,
在彈出的 " Create New Module " 對(duì)話(huà)框中 , 這里選擇 Module 的類(lèi)型為 Java or Kotlin Library ;
設(shè)置編譯時(shí)注解 依賴(lài)庫(kù)名稱(chēng) annotation , 注意語(yǔ)言選擇 Java ; 暫時(shí)不涉及 Kotlin 注解 ;

使用上述相同的方式 , 創(chuàng)建 annotation-compiler 注解處理器 依賴(lài)庫(kù) , 這兩個(gè) Module 的類(lèi)型都是 " Java or Kotlin Library " ;

4.2 接下來(lái)為項(xiàng)目app模塊添加annotation和annotation-compiler模塊的依賴(lài)
在主應(yīng)用 " app " 中 , 依賴(lài)上述 annotation 編譯時(shí)注解 依賴(lài)庫(kù) 和 annotation-compiler 注解處理器 依賴(lài)庫(kù) ;
右鍵點(diǎn)擊應(yīng)用 , 選擇 " Open Modules Settings " 選項(xiàng) ,
在 " Project Structure " 對(duì)話(huà)框中選擇 " Dependencies " 選項(xiàng)卡 , 選擇主應(yīng)用 " app " , 點(diǎn)擊 " + " 按鈕 , 選擇添加 " Module Dependency " 依賴(lài)庫(kù) ,
將 annotation 編譯時(shí)注解 依賴(lài)庫(kù) 和 annotation-compiler 注解處理器 依賴(lài)庫(kù) 添加到主應(yīng)用 " app " 的依賴(lài)中 ;
添加依賴(lài)完成 ;

點(diǎn)擊 " OK " 按鈕后 , 在 build.gradle 構(gòu)建腳本中自動(dòng)生成的依賴(lài) :

dependencies {
    implementation project(path: ':annotation-compiler')
    implementation project(path: ':annotation')
}

注意 : 對(duì)于 annotation-compiler 注解處理器 依賴(lài)庫(kù) 不能使用 implementation , 必須使用 annotationProcessor ,

dependencies {
    annotationProcessor project(path: ':annotation-compiler')
    implementation project(path: ':annotation')
}

五瓦灶、開(kāi)發(fā) annotation 編譯時(shí)注解 依賴(lài)庫(kù) ;

創(chuàng)建BindView.java注解類(lèi)

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.CLASS) //注解的生命周期鸠删,保留時(shí)間到類(lèi)文件級(jí)別
@Target(ElementType.FIELD) //作用域在字段上
public @interface BindView {
    int value();
}

六、開(kāi)發(fā) annotation-compiler 注解處理器 依賴(lài)庫(kù) ;

6.1 創(chuàng)建 AnnotationCompiler 注解處理器 , 該類(lèi)主要作用是生成代碼 , 注解處理器類(lèi)必須繼承 javax.annotation.processing.AbstractProcessor 類(lèi) , 這是 Java 的 API , 再 Android 中無(wú)法獲取該 API , 因此 編譯時(shí)注解 和 注解處理器 都必須是 Java 依賴(lài)庫(kù) ;

創(chuàng)建AnnotationCompiler.java

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.TypeElement;

/**
 * 生成代碼的注解處理器
 */
public class AnnotationCompiler extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        return false;
    }
}

6.2 使用注解 @AutoService(Processor.class) 標(biāo)注 注解處理器
上述實(shí)現(xiàn)的 AbstractProcessor 中的 process 方法 , 專(zhuān)門(mén)用于搜索 Android 源碼中使用的 編譯時(shí)注解 的方法 ;

程序構(gòu)建時(shí) , 發(fā)現(xiàn)了 annotationProcessor project(path: ':annotation-compiler') 依賴(lài) , 使用 annotationProcessor 進(jìn)行依賴(lài) , 說(shuō)明這是一個(gè)注解處理器 , 此時(shí)會(huì)到 annotation-compiler 模塊中查找 注解處理器 , 注解處理器 的查找方式也是 通過(guò) 注解 標(biāo)記 進(jìn)行查找 , 該注解是 Google 服務(wù)庫(kù)提供的 ;

如果要使用 注解 標(biāo)記注解處理器 , 首先要依賴(lài) Google 服務(wù)庫(kù) ,
在 annotation-compiler模塊的build.gradle中添加

dependencies {
    //使用google的auto-service依賴(lài)庫(kù) 
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
    implementation 'com.google.auto.service:auto-service:1.0-rc6'
}

然后到 public class Compiler extends AbstractProcessor 注解處理器類(lèi)上使用 @AutoService(Processor.class) 標(biāo)注 , Android 編譯器通過(guò)查找該注解 , 確定哪個(gè)類(lèi)是注解處理器 ;

import java.util.Set;

import com.google.auto.service.AutoService;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.TypeElement;

/**
 * 生成代碼的注解處理器
 */
@AutoService(Processor.class)
public class AnnotationCompiler extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        return false;
    }
}

6.3 注解處理器 init 初始化方法
AbstractProcessor 注解處理器中 , 專(zhuān)門(mén)提供了一個(gè) init 方法 , 該方法專(zhuān)門(mén)用于注解處理器相關(guān)初始化操作 ;
init 方法的 ProcessingEnvironment processingEnv 參數(shù)很重要 , 通過(guò)該參數(shù)可以獲取 注解處理器中的各種重要工具 ;
ProcessingEnvironment 中定義了獲取相關(guān)工具的接口 ;

public interface ProcessingEnvironment {
    Map<String, String> getOptions();

    Messager getMessager();

    Filer getFiler();

    Elements getElementUtils();

    Types getTypeUtils();

    SourceVersion getSourceVersion();

    Locale getLocale();
}

6.4 注解處理器 Filer 代碼生成工具具
(1)通過(guò)注解生成 Java 代碼需要使用 Filer 對(duì)象 , 將該對(duì)象定義到 注解處理器 成員變量中 , 在 init 方法中進(jìn)行初始化操作 ;
通過(guò) ProcessingEnvironment 可以通過(guò)調(diào)用 getFiler 方法 , 獲取 Filer 對(duì)象 ;

(2)注解處理器中不能打斷點(diǎn)進(jìn)行調(diào)試 , 也不能使用 Log , System.out 打印日志 , 在注解處理器中只能通過(guò) Messager 打印日志 ;
通過(guò)調(diào)用 ProcessingEnvironment 的 getMessager 方法 , 可以獲取到 Messager 對(duì)象 ;

    //生成文件的對(duì)象
    private Filer filer;
    //日志打印
    private Messager messager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
        messager = processingEnv.getMessager();
    }

七倚搬、注解處理器annotation-compiler 依賴(lài) 編譯時(shí)注解annotation模塊
注解處理器 需要處理 編譯時(shí)注解 , 因此必須能夠拿到 編譯時(shí)注解 的引用 , 注解處理器 Module 需要依賴(lài) 編譯時(shí)注解 Module ;

在 注解處理器 Module 的 build.gradle 的 dependencies 依賴(lài)中添加 implementation project(path: ':annotation') 依賴(lài) ;

plugins {
    id 'java-library'
}
dependencies {
    //需要依賴(lài)annotation模塊冶共,獲取這個(gè)模塊的注解
    implementation project(path: ':annotation')
    //使用google的auto-service依賴(lài)庫(kù) 
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
    implementation 'com.google.auto.service:auto-service:1.0-rc6'
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7
}

八乾蛤、設(shè)置 注解處理器 支持的注解類(lèi)型
注解處理器 抽象類(lèi) AbstractProcessor 中的 getSupportedAnnotationTypes 方法 , 用于聲明 注解處理器 要處理的注解類(lèi)型 ;
該方法的返回值是 Set<String> , 因此可以設(shè)置多個(gè)處理的 注解類(lèi)型 ;
在 getSupportedAnnotationTypes 方法中構(gòu)造一個(gè) Set<String> 集合 , 向其中放置要解析注解的全類(lèi)名字符串 ;

/**
     * 聲明要處理的注解有哪些
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        types.add(OnClick.class.getCanonicalName());
        return types;
    }

設(shè)置 注解處理器 支持的注解類(lèi)型 , 也可以使用 注解 的方式進(jìn)行聲明 ;
使用 @SupportedAnnotationTypes 注解 , 也可以聲明 注解處理器 支持的注解類(lèi)型 ;

@Documented
@Target(TYPE)
@Retention(RUNTIME)
public @interface SupportedAnnotationTypes {
    /**
     * Returns the names of the supported annotation types.
     * @return the names of the supported annotation types
     */
    String [] value();
}

注意 : 兩種方式二選一 , 不能同時(shí)存在 ;

九每界、設(shè)置 注解處理器 支持的 Java 版本
注解處理器 抽象類(lèi) AbstractProcessor 中的 getSupportedSourceVersion 方法 , 用于聲明 該注解處理器 支持的 Java 版本 ;
一般情況下要支持到最新的 Java 版本 ,
通過(guò)調(diào)用 ProcessingEnvironment 類(lèi)的 getSourceVersion 方法 , 可以獲取最新的 Java 版本 ;

/**
     * 聲明支持的java版本
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return processingEnv.getSourceVersion();
    }

設(shè)置 注解處理器 支持的 Java 語(yǔ)言版本 , 也可以使用 注解 的方式進(jìn)行聲明 ;
使用 @SupportedSourceVersion 注解 , 也可以聲明 注解處理器 支持的 Java 語(yǔ)言版本 ;

@Documented
@Target(TYPE)
@Retention(RUNTIME)
public @interface SupportedSourceVersion {
    /**
     * Returns the latest supported source version.
     * @return the latest supported source version
     */
    SourceVersion value();
}

注意 : 兩種方式二選一 , 不能同時(shí)存在 ;

十捅僵、獲取被 注解 標(biāo)注的節(jié)點(diǎn)
處理注解的核心邏輯在 AbstractProcessor 中的 process 方法中實(shí)現(xiàn) ;

先獲取被注解標(biāo)注的節(jié)點(diǎn) , 搜索 BindView , 調(diào)用 process 方法的 RoundEnvironment roundEnvironment 參數(shù)的 getElementsAnnotatedWith 方法 , 即可搜索到整個(gè) Module 中所有使用了 BindView 注解的元素 ;

Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);

將 BindView 注解放在什么元素上 , 得到的就是相應(yīng)類(lèi)型的元素 , 根據(jù) 注解類(lèi)型 獲取 被該注解類(lèi)型 標(biāo)注的元素 , 元素可能是類(lèi) , 方法 , 字段 ;

在 app 模塊中 , 只有 MainActivity 中的一個(gè) 屬性字段 使用 BindView 注解 , 調(diào)用 roundEnv.getElementsAnnotatedWith(BindView.class) 方法獲取的元素就是 MainActivity 中的所有標(biāo)記@BindView的成員變量 ;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;

import com.aapl.annotation.BindView;

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv1)
    TextView tv1;
    @BindView(R.id.tv2)
    TextView tv2;
    @BindView(R.id.tv3)
    TextView tv3;
    @BindView(R.id.iv1)
    ImageView iv1;
    @BindView(R.id.iv2)
    ImageView iv2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        tv1.setText("bind view success");
    }
}

假設(shè)在 若干 Activity 中 , 若干位置 , 使用了 BindView 注解 , 那么在獲取的所有使用了 BindView 注解的字段 Set<? extends Element> elements 中裝載了所有的使用了該注解的字段 , 這些字段來(lái)自不同的 Activity 中 ;

這就需要將 Set<? extends Element> elements 中的 字段 按照 Activity 上下文進(jìn)行分組 , 以便生成代碼 ;

這樣每個(gè) Activity 界面都對(duì)應(yīng)若干個(gè) Set<? extends Element> elements 中的元素 ;

十一、Element 注解節(jié)點(diǎn)類(lèi)型
使用注解標(biāo)注的 Element 節(jié)點(diǎn)類(lèi)型 :

ExecutableElement : 使用注解的 方法 節(jié)點(diǎn)類(lèi)型 ;

VariableElement : 使用注解的 字段 節(jié)點(diǎn)類(lèi)型 , 類(lèi)的成員變量 ;

TypeElement : 使用注解的 類(lèi) 節(jié)點(diǎn)類(lèi)型 ;

PackageElement : 使用注解的 包 節(jié)點(diǎn)類(lèi)型 ;

上述 4個(gè)類(lèi)都是 javax.lang.model.element.Element 的子類(lèi) ;

app module下完整源碼參考如下:

ButterKnife.java代碼:

import android.app.Activity;
import android.os.IBinder;

import java.lang.reflect.Constructor;

public class ButterKnife {
    /**
     * 獲取到生成的XXXActivity_ViewBinder類(lèi)文件
     * @param obj
     */
    public static void bind(Object obj) {
        Class<?> clazz = obj.getClass();
        String clazzName = clazz.getName()+"_ViewBinder";
        try {
            //獲取到XXXActivity_ViewBinder的class眨层,并創(chuàng)建class實(shí)例對(duì)象
            Class<?> binderClazz = Class.forName(clazzName);
            //確定一個(gè)類(lèi)binderClass是不是繼承(或?qū)崿F(xiàn)自)IButterKnife
            if (IButterKnife.class.isAssignableFrom(binderClazz)) {
                IButterKnife butterKnife = (IButterKnife) binderClazz.newInstance();
                butterKnife.bind(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

IButterKnife.java代碼:

public interface IButterKnife<T> {
    void bind(T target);
}

AnnotationCompiler.java代碼:

import com.aapl.annotation.BindView;
import com.aapl.annotation.OnClick;
import com.google.auto.service.AutoService;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

/**
 * 注解處理器(目的是生成一個(gè)java文件庙楚,生成一個(gè)窗體的findViewById和onClick的代碼)
 * 1.繼承AbstractProcessor
 * 2.注冊(cè)服務(wù),注冊(cè)注解處理器
 */
@AutoService(Processor.class)
public class AnnotationCompiler extends AbstractProcessor {
    //生成文件的對(duì)象
    private Filer filer;
    //日志打印
    private Messager messager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
        messager = processingEnv.getMessager();
    }

    /**
     * 聲明要處理的注解有哪些
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        types.add(OnClick.class.getCanonicalName());
        return types;
    }

    /**
     * 聲明支持的java版本
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return processingEnv.getSourceVersion();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // PackageElement 包節(jié)點(diǎn)
        // TypeElement 類(lèi)節(jié)點(diǎn)
        // ExecutableElement 方法節(jié)點(diǎn)
        // VariableElement 成員變量節(jié)點(diǎn)
        /**
         * 搜索 BindView , 將 BindView 注解放在什么元素上 , 得到的就是相應(yīng)類(lèi)型的元素
         * 根據(jù) 注解類(lèi)型 獲取 被該注解類(lèi)型 標(biāo)注的元素 , 元素可能是類(lèi) , 方法 , 字段 ;
         * 通過(guò) getElementsAnnotatedWith 方法可以搜索到整個(gè) Module 中所有使用了 BindView 注解的元素
         */
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        /**
         * 定義一個(gè)map集合,用來(lái)存儲(chǔ)所有activity中所有的變量注解集合
         * key->activityName activity名字
         * value->List<VariableElement> 成員變量注解集合
         */
        Map<String, List<VariableElement>> map = new HashMap<>();
        for (Element element : elements) {
            //轉(zhuǎn)化為成員變量節(jié)點(diǎn)
            VariableElement variableElement = (VariableElement) element;
            /**
             * 先獲取該注解節(jié)點(diǎn)的上一級(jí)節(jié)點(diǎn) , 注解節(jié)點(diǎn)是 VariableElement 成員字段節(jié)點(diǎn),
             * 上一級(jí)節(jié)點(diǎn)是就是 Activity 類(lèi)節(jié)點(diǎn)對(duì)應(yīng)的 類(lèi)節(jié)點(diǎn) TypeElement
             */
            TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
            String qualifiedName = typeElement.getQualifiedName().toString();
            //獲取到activityName = simpleName
            String simpleName = typeElement.getSimpleName().toString();
            System.out.println("qualifiedName="+qualifiedName+", simpleName="+simpleName);
            messager.printMessage(Diagnostic.Kind.NOTE, "process==>qualifiedName="+qualifiedName+", simpleName="+simpleName);
            //把當(dāng)前activity中獲取到的所有變量注解的節(jié)點(diǎn)都加入到list集合中
            List<VariableElement> variableElements = map.get(simpleName);
            if (variableElements == null) {
                variableElements = new ArrayList<>();
                map.put(simpleName, variableElements);
            }
            variableElements.add(variableElement);
        }

        if (!map.isEmpty()) {
            Writer writer = null;
            Iterator iterator = map.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, List<VariableElement>> entry = (Map.Entry<String, List<VariableElement>>) iterator.next();
                String activityName = entry.getKey();
                List<VariableElement> variableElements = entry.getValue();
                //獲取包名
                String packageName = "";
                TypeElement typeElement = null;
                if (variableElements != null && !variableElements.isEmpty()) {
                    packageName = getPackageName(variableElements.get(0));
                    typeElement = (TypeElement) variableElements.get(0).getEnclosingElement();
                }
                // 生成一個(gè)java文件
                String bindActivityName = activityName+"_ViewBinder";
                try {
                    JavaFileObject javaFileObject = filer.createSourceFile(
                            packageName+"."+bindActivityName, typeElement);
                    writer = javaFileObject.openWriter();
                    StringBuffer sb = new StringBuffer();
                    sb.append("package "+packageName+";\n\n");
                    sb.append("import android.view.View;\n\n");
                    sb.append("public class "+bindActivityName+" implements IButterKnife<"+packageName+"."+activityName+"> {\n\n");
                    sb.append("    public void bind("+packageName+"." + activityName + " target) {\n");
                    for (VariableElement variableElement : variableElements) {
                        //獲取到成員變量的名字
                        String filedName = variableElement.getSimpleName().toString();
                        //獲取到注解中的值,即(R.id.xxx)
                        int resId = variableElement.getAnnotation(BindView.class).value();
                        sb.append("        target."+filedName+" = target.findViewById("+resId+");\n");
                    }
                    sb.append("");
                    sb.append("    }\n");
                    sb.append("}\n");

                    writer.write(sb.toString());

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (writer != null) {
                        try {
                            writer.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return false;
    }

    /**
     * 獲取包名
     * @param typeElement
     * @return
     */
    public String getPackageName(Element typeElement)  {
        PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(typeElement);
        String qualifiedName = packageElement.getQualifiedName().toString();
        return qualifiedName;
    }
}

MainActivity.java代碼:

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;

import com.aapl.annotation.BindView;

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv1)
    TextView tv1;
    @BindView(R.id.tv2)
    TextView tv2;
    @BindView(R.id.tv3)
    TextView tv3;
    @BindView(R.id.iv1)
    ImageView iv1;
    @BindView(R.id.iv2)
    ImageView iv2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        tv1.setText("bind view success");
    }
}

運(yùn)行之后生成的MainActivity_ViewBinder.java

import android.view.View;

public class MainActivity_ViewBinder implements IButterKnife<com.aapl.butterknifedemo.MainActivity> {

    public void bind(com.aapl.butterknifedemo.MainActivity target) {
        target.tv1 = target.findViewById(2131231129);
        target.tv2 = target.findViewById(2131231130);
        target.tv3 = target.findViewById(2131231131);
        target.iv1 = target.findViewById(2131230915);
        target.iv2 = target.findViewById(2131230916);
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末翅敌,一起剝皮案震驚了整個(gè)濱河市蚯涮,隨后出現(xiàn)的幾起案子恋昼,更是在濱河造成了極大的恐慌液肌,老刑警劉巖嗦哆,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異橘券,居然都是意外死亡旁舰,警方通過(guò)查閱死者的電腦和手機(jī)毯焕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)芜辕,“玉大人物遇,你說(shuō)我怎么就攤上這事《鹘Γ” “怎么了腥放?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)族扰。 經(jīng)常有香客問(wèn)我渔呵,道長(zhǎng)扩氢,這世上最難降的妖魔是什么录豺? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任厚骗,我火速辦了婚禮夫嗓,結(jié)果婚禮上舍咖,老公的妹妹穿的比我還像新娘窍株。我一直安慰自己攻柠,他們只是感情好冒滩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著篇恒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蝗茁。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天阻课,我揣著相機(jī)與錄音,去河邊找鬼署驻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的征候。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起甜无,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤眠饮,失蹤者是張志新(化名)和其女友劉穎松蒜,沒(méi)想到半個(gè)月后秸苗,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體秸讹,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡文虏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年丸相,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜘拉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出年局,到底是詐尸還是另有隱情僵朗,我是刑警寧澤社牲,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站搏恤,受9級(jí)特大地震影響违寿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜熟空,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一藤巢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧息罗,春花似錦掂咒、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至挨摸,卻和暖如春录淡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背油坝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工嫉戚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人澈圈。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓彬檀,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親瞬女。 傳聞我的和親對(duì)象是個(gè)殘疾皇子窍帝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容