安卓使用注解處理器自動(dòng)生成代碼操作詳解(AutoService,JavaPoet,AbstractProcessor)

關(guān)聯(lián)文章:Android自定義注解

新手村

先來(lái)說(shuō)說(shuō)注解處理器(AbstractProcessor)是干嘛的,它主要是用來(lái)處理注解的一些內(nèi)部邏輯桐腌,拿butterknife舉例拄显,我聲明了一個(gè)bindView注解,那肯定是要寫一些邏輯才能找到控件的id對(duì)吧案站,AbstractProcessor就是注解處理的邏輯入口躬审,出于性能考慮,肯定是不能使用反射來(lái)處理找id這個(gè)邏輯的,這時(shí)承边,JavaPoet就派上用場(chǎng)了遭殉,它的作用是根據(jù)特定的規(guī)則生成java代碼文件,這樣博助,我通過(guò)注解來(lái)拿到需要的參數(shù)险污,通過(guò)JavaPoet來(lái)生成模板代碼,對(duì)性能沒(méi)有任何的影響富岳,由于ServiceLoader加載Processor需要手動(dòng)注冊(cè)配置蛔糯,框架AutoService就是用來(lái)自動(dòng)注冊(cè)ServiceLoader的,省去了AbstractProcessor繁瑣的配置。理解了這三者的關(guān)系城瞎,下面開始真正的學(xué)習(xí)吧

副本之JavaPoet的使用

項(xiàng)目地址:https://github.com/square/javapoet
javapoet的api非常的通俗易懂渤闷,我用主頁(yè)的使用示例來(lái)說(shuō)明一下
例如我們要生成一個(gè)這樣的代碼:

package com.example.helloworld;

public final class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, JavaPoet!");
  }
}

對(duì)應(yīng)的代碼為:

MethodSpec main = MethodSpec.methodBuilder("main") //方法名
    .addModifiers(Modifier.PUBLIC, Modifier.STATIC) //修飾符
    .returns(void.class)//返回值
    .addParameter(String[].class, "args")//參數(shù)
    .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")//內(nèi)容
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") //類名
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL) //修飾符
    .addMethod(main) //方法
    .build();

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
    .build();

javaFile.writeTo(System.out);

MethodSpec類是用來(lái)配置方法的疾瓮,一個(gè)方法包括方法名脖镀,修飾符,返回值狼电,參數(shù)蜒灰,內(nèi)容,配置對(duì)應(yīng)的方法已在注釋中標(biāo)出肩碟。
TypeSpec為類的配置强窖,類包括類名,修飾符削祈,方法,字段等
JavaFile用于指定輸出位置翅溺,生成類,我們傳入包名髓抑,和類咙崎,最后通過(guò)writeTo指定輸出到控制臺(tái)。
可以看出吨拍,復(fù)雜的地方就是在MethodSpec的配置褪猛,下面著重介紹MethodSpec的一些常用用法

基本用法

MethodSpec main = MethodSpec.methodBuilder("main")
    .addCode(""
        + "int total = 0;\n"
        + "for (int i = 0; i < 10; i++) {\n"
        + "  total += i;\n"
        + "}\n")
    .build();

效果:

void main() {
  int total = 0;
  for (int i = 0; i < 10; i++) {
    total += i;
  }
}

可以看到里面的分號(hào)和換行符混在一起看起來(lái)眼花繚亂,丟失一個(gè)還不會(huì)報(bào)錯(cuò)羹饰,讓人很抓狂伊滋,因此JavaPoet很貼心的準(zhǔn)備了換行符分號(hào)和起始結(jié)束括號(hào)的api:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addStatement("int total = 0") //這段代碼之后會(huì)添加一個(gè)分號(hào)和換行符
    .beginControlFlow("for (int i = 0; i < 10; i++)")//這段代碼之后會(huì)添加一個(gè)起始的括號(hào)
    .addStatement("total += i")
    .endControlFlow()//括號(hào)結(jié)束
    .build();

此外還有一個(gè)nextControlFlow是前后都加括號(hào),通常用于if else的邏輯判斷中队秩,示例:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addStatement("long now = $T.currentTimeMillis()", System.class)
    .beginControlFlow("if ($T.currentTimeMillis() < now)", System.class)
    .addStatement("$T.out.println($S)", System.class, "Time travelling, woo hoo!")
    .nextControlFlow("else if ($T.currentTimeMillis() == now)", System.class)
    .addStatement("$T.out.println($S)", System.class, "Time stood still!")
    .nextControlFlow("else")
    .addStatement("$T.out.println($S)", System.class, "Ok, time still moving forward")
    .endControlFlow()
    .build();

輸出:

void main() {
  long now = System.currentTimeMillis();
  if (System.currentTimeMillis() < now)  {
    System.out.println("Time travelling, woo hoo!");
  } else if (System.currentTimeMillis() == now) {
    System.out.println("Time stood still!");
  } else {
    System.out.println("Ok, time still moving forward");
  }
}

可以看到上面有幾個(gè)不明覺(jué)厲的符號(hào)笑旺,我們稱之為占位符,占位符常用的有以下幾種:

  • $T 類占位符馍资,用于替換代碼中的類
  • $L 姑且叫它變量占位符吧燥撞,用法和String.format中的%s差不多,按照順序依次替換里面的變量值
  • $S 字符串占位符,當(dāng)我們需要在代碼中使用字符串時(shí)物舒,用這個(gè)替換
  • $N 名稱占位符色洞,比方說(shuō)需要在一個(gè)方法里使用另一個(gè)方法,可以用這個(gè)替換

$L演示示例,后面的變量按照順序?qū)μ?hào)入座:

private MethodSpec computeRange(String name, int from, int to, String op) {
  return MethodSpec.methodBuilder(name)
      .returns(int.class)
      .addStatement("int result = 0")
      .beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
      .addStatement("result = result $L i", op)
      .endControlFlow()
      .addStatement("return result")
      .build();
}

$N

MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
    .addParameter(int.class, "i")
    .returns(char.class)
    .addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
    .build();

MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
    .addParameter(int.class, "b")
    .returns(String.class)
    .addStatement("char[] result = new char[2]")
    .addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
    .addStatement("result[1] = $N(b & 0xf)", hexDigit)
    .addStatement("return new String(result)")
    .build();

輸出:
public String byteToHex(int b) {
  char[] result = new char[2];
  result[0] = hexDigit((b >>> 4) & 0xf);
  result[1] = hexDigit(b & 0xf);
  return new String(result);
}

public char hexDigit(int i) {
  return (char) (i < 10 ? i + '0' : i - 10 + 'a');
}

其余兩個(gè)前面的示例中已經(jīng)使用過(guò)了,T傳入類冠胯,S傳入字符串火诸,注意順序:

 addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")

類的獲取與使用
java中的源碼類我們?cè)谑褂玫臅r(shí)候都會(huì)自動(dòng)導(dǎo)入,但是我們自定義的類是不會(huì)的荠察,所以我們需要使用ClassName來(lái)獲取我們想要的類

ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
MethodSpec today = MethodSpec.methodBuilder("tomorrow")
    .returns(hoverboard)
    .addStatement("return new $T()", hoverboard)
    .build();

輸出:
package com.example.helloworld;
import com.mattel.Hoverboard;

public final class HelloWorld {
  Hoverboard tomorrow() {
    return new Hoverboard();
  }
}

傳入包名前半段和后半段的類名就能獲取到我們想要的類了置蜀,但是參數(shù)化類型我們要怎么定義呢,比如List<Hoverboard>,這時(shí)TypeName就上場(chǎng)了悉盆,TypeName有多個(gè)子類盯荤,包括上面的ClassName也是它的子類,每個(gè)子類都承擔(dān)著不同的職責(zé):

  • ArrayTypeName 用于生成數(shù)組類焕盟,例如Hoverboard []
  • ClassName 獲取普通的類,例如Hoverboard
  • Parameterized 獲取參數(shù)化類秋秤,例如List<Hoverboard>
  • TypeVariableName 獲取類型變量,例如泛型T
  • WildcardTypeName 獲取通配符脚翘,例如? extends Hoverboard
    它們的用法差不多灼卢,以Parameterized舉例:
ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
ClassName list = ClassName.get("java.util", "List");
ClassName arrayList = ClassName.get("java.util", "ArrayList");
TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);

MethodSpec beyond = MethodSpec.methodBuilder("beyond")
    .returns(listOfHoverboards)
    .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
    .addStatement("result.add(new $T())", hoverboard)
    .addStatement("result.add(new $T())", hoverboard)
    .addStatement("result.add(new $T())", hoverboard)
    .addStatement("return result")
    .build();

輸出:
package com.example.helloworld;

import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;

public final class HelloWorld {
  List<Hoverboard> beyond() {
    List<Hoverboard> result = new ArrayList<>();
    result.add(new Hoverboard());
    result.add(new Hoverboard());
    result.add(new Hoverboard());
    return result;
  }
}

其他的例如字段(FieldSpec),注解(AnnotationSpec)来农,參數(shù)(ParameterSpec)等api用起來(lái)都大同小異鞋真,由于篇幅有限,javaPoet的介紹就講到這里沃于,如果還有不太明白的地方或有想進(jìn)一步了解的可以參考這個(gè)比較全面的介紹:JavaPoet使用詳解

副本之AutoService的使用

項(xiàng)目地址:https://github.com/google/auto
使用非常的簡(jiǎn)單:

package foo.bar;

import javax.annotation.processing.Processor;

@AutoService(Processor.class)
final class MyProcessor implements Processor {
  // …
}

編譯后涩咖,則會(huì)在META-INF文件夾下生成Processor配置信息文件,而當(dāng)外部程序裝配這個(gè)模塊的時(shí)候,
就能通過(guò)該jar包META-INF/services/里的配置文件找到具體的實(shí)現(xiàn)類名繁莹,并裝載實(shí)例化檩互,完成模塊的注入。

副本之AbstractProcessor的使用

AbstractProcessor繼承自Processor蒋困,是一個(gè)抽象處理器盾似,它的作用是在編譯時(shí)掃描注解并處理一些邏輯,例如生成代碼等雪标,一般我們繼承它需要實(shí)現(xiàn)4個(gè)方法:

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

    //文件相關(guān)輔助類
    private Filer mFiler;
    //元素
    private Elements elements;
    //日志信息
    private Messager messager;

    /**
     * 入口零院,相當(dāng)于java的main入口
     *
     * @param processingEnvironment
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFiler = processingEnvironment.getFiler();
        elements = processingEnvironment.getElementUtils();
        messager = processingEnvironment.getMessager();
    }

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


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

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

注解類:

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
     int value();
}

init方法是一個(gè)入口,ProcessingEnvironment類主要提供了一些工具類給我們使用村刨,我們可以在init方法中獲取我們需要的工具類告抄。
getSupportedAnnotationTypes用于獲取我們自定義的注解,寫法可以固定
getSupportedSourceVersion用于獲取java版本嵌牺,寫法可以固定
process方法是我們處理邏輯的核心方法打洼,返回true,代表注解已申明龄糊,并要求Processor后期不用再處理了它們

參數(shù)Set<? extends TypeElement> set是請(qǐng)求處理的類型的集合,RoundEnvironment 是當(dāng)前或之前的請(qǐng)求處理類型的環(huán)境募疮,可以通過(guò)它獲取當(dāng)前需要處理請(qǐng)求的元素炫惩,例如我需要獲取BindView注解的元素的類并獲取其中的內(nèi)容可以這樣寫:

//先拿到所有使用了BindView注解的元素集合
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);
 for (Element element:elementsAnnotatedWith){
          //從元素中拿到這個(gè)注解實(shí)例
            BindView annotation = element.getAnnotation(BindView.class);
          //從這個(gè)注解實(shí)例中獲取到注解中包含的值
            int value = annotation.value();
  }

這樣我們就獲取到了注解中的值,思考下butterknife中bindView注解中的那個(gè)id的獲取,是不是有點(diǎn)豁然開朗了呢阿浓。
我們獲取信息都是基于Element這個(gè)類展開來(lái)他嚷,所以了解下這個(gè)類很有必要,Element表示一個(gè)程序元素芭毙,比如包筋蓖、類或者方法,主要包括以下幾種方法:

  public interface Element extends AnnotatedConstruct {
    TypeMirror asType();

    ElementKind getKind();

    Set<Modifier> getModifiers();

    Name getSimpleName();

    Element getEnclosingElement();

    List<? extends Element> getEnclosedElements();

    boolean equals(Object var1);

    int hashCode();

    List<? extends AnnotationMirror> getAnnotationMirrors();

    <A extends Annotation> A getAnnotation(Class<A> var1);

    <R, P> R accept(ElementVisitor<R, P> var1, P var2);
}
public interface AnnotatedConstruct {
    List<? extends AnnotationMirror> getAnnotationMirrors();

    <A extends Annotation> A getAnnotation(Class<A> var1);

    <A extends Annotation> A[] getAnnotationsByType(Class<A> var1);
}
  • asType
    獲取元素的類型信息,包括包名退敦,類名等粘咖,配合javapoet的ClassName可以直接獲取到該TypeName
    TypeName typeName = ClassName.get(element.asType());
  • getKind 用于判斷是哪種element
  • getModifiers 用于獲取元素的關(guān)鍵字public static等
  • getEnclosingElement 返回包含該element的父element
  • getAnnotation 獲取元素上的注解
  • accept是一個(gè)判斷方法,用于判斷如果是某一個(gè)元素就執(zhí)行某一個(gè)方法侈百,用的很少瓮下,不細(xì)講了

可能會(huì)遇到的問(wèn)題

加入AutoService發(fā)現(xiàn)配置都正確,但就是不能生成代碼设哗,原因可能是你的Gradle過(guò)高唱捣,把版本降到4.10.1或以下就可以了两蟀,原因不詳网梢,如果有知道原因的朋友可以在留言區(qū)說(shuō)一下
另外一個(gè)就是你的注解器的lib包需要使用annotationProcessor來(lái)使用,而不是implementation

文章到這就要到我們的實(shí)戰(zhàn)環(huán)節(jié)了赂毯,下一篇我將帶領(lǐng)大家仿butterknife簡(jiǎn)單實(shí)現(xiàn)一個(gè)findviewbyid的功能战虏,幫助大家更好的運(yùn)用和消化這些知識(shí),加油党涕。關(guān)注我不迷茫烦感,支持我的就點(diǎn)個(gè)贊唄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市膛堤,隨后出現(xiàn)的幾起案子手趣,更是在濱河造成了極大的恐慌,老刑警劉巖肥荔,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绿渣,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡燕耿,警方通過(guò)查閱死者的電腦和手機(jī)中符,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)誉帅,“玉大人淀散,你說(shuō)我怎么就攤上這事右莱。” “怎么了档插?”我有些...
    開封第一講書人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵慢蜓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我郭膛,道長(zhǎng)胀瞪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任饲鄙,我火速辦了婚禮凄诞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘忍级。我一直安慰自己帆谍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開白布轴咱。 她就那樣靜靜地躺著汛蝙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上欲低,一...
    開封第一講書人閱讀 49,842評(píng)論 1 290
  • 那天煤惩,我揣著相機(jī)與錄音,去河邊找鬼西土。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鞍盗,可吹牛的內(nèi)容都是我干的需了。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼般甲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼肋乍!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起敷存,我...
    開封第一講書人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤墓造,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后锚烦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體觅闽,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年挽牢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谱煤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡禽拔,死狀恐怖刘离,靈堂內(nèi)的尸體忽然破棺而出室叉,到底是詐尸還是另有隱情,我是刑警寧澤硫惕,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布茧痕,位于F島的核電站,受9級(jí)特大地震影響恼除,放射性物質(zhì)發(fā)生泄漏踪旷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一豁辉、第九天 我趴在偏房一處隱蔽的房頂上張望令野。 院中可真熱鬧,春花似錦徽级、人聲如沸气破。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)现使。三九已至,卻和暖如春旷痕,著一層夾襖步出監(jiān)牢的瞬間碳锈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工欺抗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留售碳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓佩迟,卻偏偏與公主長(zhǎng)得像团滥,于是被迫代替她去往敵國(guó)和親竿屹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子报强,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

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