Android注解拜银,這幾篇文章就夠了(三)自己寫個(gè)注解處理器

一 前言

前面兩篇文章殊鞭,注解處理器理解注解尼桶,對(duì)注解有了一個(gè)初步認(rèn)識(shí)操灿,第二篇文章末尾也提到了,注解不是代碼的一部分泵督,當(dāng)開發(fā)者使用了Annotation注解以后牲尺,注解不會(huì)自己起作用,必須提供相應(yīng)的代碼來處理這些信息。
這篇文章谤碳,我們就寫一個(gè)簡單的注解處理器,作用是類似于ButterKnife查找id溢豆。
源碼傳送門

二 項(xiàng)目結(jié)構(gòu)

整個(gè)項(xiàng)目采用如下所示的結(jié)構(gòu):


    1. BindViewAnnotation蜒简,Java Library,存放我們定義的注解漩仙。
    1. bindviewapi搓茬,Android Library,聲明注解框架使用的api队他,本例子中卷仑,我們要實(shí)現(xiàn)的是查找view控件,并將控件和xml中的綁定麸折。
    1. BindViewCompiler锡凝,Java Library,注解處理器垢啼,根據(jù)注解生成處理打代碼窜锯。

新建這幾個(gè)Library的過程不再陳述,特別注意的是芭析,建BindViewCompiler Java Library時(shí)锚扎,在build.gradle下要加入以下代碼:

// 用于生成Java文件的庫
    implementation 'com.squareup:javapoet:1.11.1'
    implementation 'com.google.auto.service:auto-service:1.0-rc6'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'

這些代碼后面會(huì)提及。

三 正式開始吧

(一)在BindViewAnnotation新建注解

前面說過了馁启,我們要實(shí)現(xiàn)的功能是查找xml文件的id功能驾孔,注解歹意如下:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)

public @interface BindView {
    int value();
}

使用注解,在Activity中使用注解惯疙,在xml中定義一個(gè)button翠勉,

<Button
        android:id="@+id/btn_bind"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="BindButton"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

在Activity中使用我們定義的注解。

@BindView(R.id.btn_bind)
 public Button mButton;

前面說了螟碎,注解不會(huì)自動(dòng)起作用眉菱,如果我們直接運(yùn)行代碼,會(huì)直接報(bào)錯(cuò)的掉分,提示Button沒有定義俭缓,所以我們要寫代碼來處理這個(gè)注解的信息。

(二) 聲明注解框架用到的api

① 定義一個(gè)綁定注解的接口
public interface IViewBind<T> {
    void bind(T t);
}
② 向外提供的綁定方法酥郭,這里使用靜態(tài)方法來管理华坦。
public class ViewBinder {

    public static void bind(Activity activity){

        try {

            // 1
            Class clazz=Class.forName(activity.getClass().getCanonicalName()+"$$ViewBinder");
           // 2
            IViewBind<Activity> iViewBinder= (IViewBind<Activity>) clazz.newInstance();
           // 3
            iViewBinder.bind(activity);
        } catch (ClassNotFoundException e) {
            Log.d("hbj--exp",e.getException()+"");
            Log.d("hbj--cause",e.getCause()+"");
            Log.d("hbj--mess",e.getMessage()+"");
            Log.d("hbj--class",e.getClass()+"");
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

    }
}

// 1 處獲取生成的類的名字,生成類的規(guī)則代碼在后面寫不从,生成的規(guī)則是 類名$$ViewBinder惜姐,例如在MainActivity中需要使用,生成的文件名字就是MainActivity$$ViewBinder.java。
// 2 獲取生成的類的實(shí)例歹袁。
// 3 完成綁定坷衍。
可能對(duì)第二條和第三條不是很好理解,現(xiàn)在貼出生成的java文件的源碼条舔,結(jié)合生成的文件枫耳,應(yīng)該就好理解了吧。

public class MainActivity$$ViewBinder< T extends MainActivity> implements IViewBind<T> {
@Override
public void bind(T activity) {
activity.mButton=activity.findViewById(2131165250);
}
}

(三) 根據(jù)注解生成代碼

現(xiàn)在只剩根據(jù)注解生成代碼孟抗。
創(chuàng)建一個(gè)自定義的Annotation Processor迁杨,繼承自AbstractProcessor。

  // 1
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {

   // 2
    @Override
    public synchronized void init(ProcessingEnvironment env){
    }
  
   // 3
    @Override
    public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment roundEnv) { }

    // 4
    @Override
    public Set<String> getSupportedAnnotationTypes() { 
    }

    // 5
    @Override
    public SourceVersion getSupportedSourceVersion() {
    }
}

// 1處 @AutoService(Processor.class)凄硼,向javac注冊(cè)我們自定義的注解處理器铅协, 這樣,在javac編譯時(shí)摊沉,才會(huì)調(diào)用到我們這個(gè)自定義的注解處理器方法狐史。
AutoService主要是生成 META-INF/services/javax.annotation.processing.Processor文件的。如果不加上這個(gè)注解的話坯钦,需要通過以下方法進(jìn)行手動(dòng)配置進(jìn)行手動(dòng)注冊(cè):
1.在main下創(chuàng)建resources目錄预皇,然后創(chuàng)建META-INF/services 目錄,最后在此目錄下創(chuàng)建文件:javax.annotation.processing.Processor婉刀,目錄如下吟温,



文件里的內(nèi)容是一些列的自定義注解處理器完整有效的類名集合,以換行符切割突颊,這里就自定義了一個(gè)注解處理器鲁豪,


注:但是有可能會(huì)出現(xiàn)使用@AutoService()無法動(dòng)態(tài)生成入口文件的,這個(gè)問題可以如下解決:

這個(gè)要從google auto service 和META_INF律秃,谷歌的 auto service 也是一種 Annotation Processor爬橡,它能自動(dòng)生成 META-INF 目錄以及相關(guān)文件,避免手工創(chuàng)建該文件棒动,手工創(chuàng)建的方法糙申,上文有,手工創(chuàng)建有可能失誤船惨。使用 auto service 中的 @AutoService(Processor.class) 注解修飾 Annotation Processor 類就可以在編譯過程中自動(dòng)生成文件柜裸。

如果要進(jìn)入的話,還要注意要引入兩個(gè)配置:

   implementation 'com.google.auto.service:auto-service:1.0-rc6'
   annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'

這兩個(gè)重復(fù)寫的原因是:annotationProcessor這個(gè)是新版 gradle 提供的 java plugin 內(nèi)置的配置項(xiàng)粱锐,在gradle 5.+ 中將 Annotation Processor 從編譯期 classpath 中去除了疙挺,javac 也就無法發(fā)現(xiàn) Annotation Processor。此處如果按照 gradle 4.+ 的寫法怜浅,只寫一個(gè) implementation 是無法使用 auto service 的 Annotation Processor 的铐然。必須要使用 annotationProcessor 來配置 Annotation Processor 使其生效。

// 2處 init(ProcessingEnvironment env): 每一個(gè)注解處理器類都必須有一個(gè)空的構(gòu)造函數(shù)。然而搀暑,這里有一個(gè)特殊的init()方法沥阳,它會(huì)被注解處理工具調(diào)用,并輸入ProcessingEnviroment參數(shù)险掀。ProcessingEnviroment提供很多有用的工具類Elements,Types和Filer沪袭。

// 3處 public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env)這相當(dāng)于每個(gè)處理器的主函數(shù)main()。 在這里寫掃描樟氢、評(píng)估和處理注解的代碼,以及生成Java文件侠鳄。輸入?yún)?shù)RoundEnviroment埠啃,可以讓查詢出包含特定注解的被注解元素。
// 4處 getSupportedAnnotationTypes()伟恶,這里必須指定碴开,這個(gè)注解處理器是注冊(cè)給哪個(gè)注解的。注意博秫,它的返回值是一個(gè)字符串的集合潦牛,包含本處理器想要處理的注解類型的合法全稱。換句話說挡育,在這里定義你的注解處理器注冊(cè)到哪些注解上巴碗。

// 5處 getSupportedSourceVersion();用來指定你使用的Java版本。
下面給出BindViewProcessor的完整代碼:

@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {

  // 文件相關(guān)輔助類
  private Filer mFiler;
  // 日志相關(guān)輔助類
  private Messager mMessager;


  @Override
  public synchronized void init(ProcessingEnvironment processingEnv) {
      super.init(processingEnv);

      mFiler = processingEnv.getFiler();
      mMessager = processingEnv.getMessager();
  }


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

  @Override
  public Set<String> getSupportedAnnotationTypes() {
      Set<String> annotations=new LinkedHashSet<>();
      annotations.add(BindView.class.getCanonicalName());
      return annotations;
  }

  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
      Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
      Map<TypeElement,ArrayList<BindViewInfo>> bindViewMap=new HashMap<>();
      for (Element element : elements) {
          // 因?yàn)锽indView只作用于Filed即寒,判斷注解是否是屬性橡淆,不是的話直接結(jié)束
          if (element.getKind() != ElementKind.FIELD) {
              mMessager.printMessage(Diagnostic.Kind.ERROR, element.getSimpleName().toString() + " is not filed,can not use @BindView");
              return false;
          }
          // 獲取注解元數(shù)據(jù)
          int id = element.getAnnotation(BindView.class).value();
          // 獲取屬性的類
          TypeElement typeElement= (TypeElement) element.getEnclosingElement();

          if (!bindViewMap.containsKey(typeElement)){
              bindViewMap.put(typeElement,new ArrayList<BindViewInfo>());
          }
          ArrayList<BindViewInfo> bindViewInfos=bindViewMap.get(typeElement);
          // 添加list
          bindViewInfos.add(new BindViewInfo(id,element.getSimpleName().toString()));
      }
      produceClass(bindViewMap);
      return true;
  }


  private void produceClass(Map<TypeElement,ArrayList<BindViewInfo>> hasMap){

      if (hasMap == null || hasMap.isEmpty()){
          return;
      }

      Set<TypeElement> typeElements=hasMap.keySet();
      for (TypeElement typeElement:typeElements){
          produceJavaClass(typeElement,hasMap.get(typeElement));
      }

  }

/**
   * 產(chǎn)生Java文件
   * @param typeElement
   * @param bindViewInfos
   */
  private void produceJavaClass(TypeElement typeElement, List<BindViewInfo> bindViewInfos){

      try {
          StringBuffer stringBuffer=new StringBuffer();
          stringBuffer.append("package ");
          stringBuffer.append(getPackageName(typeElement.getQualifiedName().toString())+";\n");
          stringBuffer.append("import com.jackson.bindviewapi.IViewBind;\n");
          stringBuffer.append("public class "+typeElement.getSimpleName()+"$$ViewBinder< T extends "+typeElement.getSimpleName()+"> implements IViewBind<T> {\n");
          stringBuffer.append("@Override\n");
          stringBuffer.append("public void bind(T activity) {\n");

          for (BindViewInfo bindViewInfo:bindViewInfos){
              stringBuffer.append("activity."+bindViewInfo.name+"=activity.findViewById("+bindViewInfo.id+");\n");
          }
          stringBuffer.append("}\n}");
          JavaFileObject javaFileObject=mFiler.createSourceFile(typeElement.getQualifiedName().toString()+"$$ViewBinder");
          Writer writer=javaFileObject.openWriter();
          writer.write(stringBuffer.toString());
          writer.close();
      } catch (IOException e) {
          e.printStackTrace();
      }
  }

  private String getPackageName(String className){
      if (className==null || className.equals("")){
          return "";
      }

      return className.substring(0,className.lastIndexOf("."));
  }
}

重新編譯項(xiàng)目,在app/build/source/apt/debug/com.jackson.annotationdemo下就會(huì)找到生成的文件母赵,如下圖



生成文件的代碼逸爵,前面已經(jīng)給出,可以對(duì)照著生成java文件的代碼凹嘲,來看BindViewProcessor生成java文件的代碼規(guī)則师倔。

(四) 使用

在MainActivity中使用,代碼如下:

 @BindView(R.id.btn_bind)
    public Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewBinder.bind(this);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this,"注解測(cè)試",Toast.LENGTH_SHORT).show();
            }
        });
    }

可以看到周蹭,在MainActivity中趋艘,并沒有mButton的findViewById()來初始化,而是通過注解完成谷醉,代碼運(yùn)行正常致稀。
源碼傳送門

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市俱尼,隨后出現(xiàn)的幾起案子抖单,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矛绘,死亡現(xiàn)場離奇詭異耍休,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)货矮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門羊精,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人囚玫,你說我怎么就攤上這事喧锦。” “怎么了抓督?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵燃少,是天一觀的道長。 經(jīng)常有香客問我铃在,道長阵具,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任定铜,我火速辦了婚禮阳液,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘揣炕。我一直安慰自己帘皿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布祝沸。 她就那樣靜靜地躺著矮烹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪罩锐。 梳的紋絲不亂的頭發(fā)上奉狈,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音涩惑,去河邊找鬼仁期。 笑死,一個(gè)胖子當(dāng)著我的面吹牛竭恬,可吹牛的內(nèi)容都是我干的跛蛋。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼痊硕,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼赊级!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起岔绸,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤理逊,失蹤者是張志新(化名)和其女友劉穎橡伞,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晋被,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡兑徘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了羡洛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挂脑。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖欲侮,靈堂內(nèi)的尸體忽然破棺而出崭闲,到底是詐尸還是另有隱情,我是刑警寧澤威蕉,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布镀脂,位于F島的核電站,受9級(jí)特大地震影響忘伞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沙兰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一氓奈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鼎天,春花似錦舀奶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至罗岖,卻和暖如春涧至,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背桑包。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來泰國打工南蓬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哑了。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓赘方,卻偏偏與公主長得像,于是被迫代替她去往敵國和親弱左。 傳聞我的和親對(duì)象是個(gè)殘疾皇子窄陡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

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