安卓編譯時注解的應用(一)

Java中的注解是一個非常簡潔的東西。使用它可以簡化上層代碼,讓你的業(yè)務邏輯變的清晰明了。
注解分運行時級別的注解,編譯時級別的注解以及源碼級別的注解。運行時注解需要用到反射相關(guān)的知識,有性能的損耗,今天我們來講講編譯時的注解,它的作用可以讓你的程序在編譯過程中通過代碼自動生成一些類然后調(diào)用,如此就沒有反射什么事了,性能也就提升了。Class級別的注解沒使用過這里不做探討了。

使用編譯時注解很簡單,在創(chuàng)建注解的時候只需要指定它的保留策略為Source級別即可(經(jīng)過測試指定為CLASS級別也可以)。
下面這段代碼創(chuàng)建了一個Class級別的注解:

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface AuthorityFail {

    int value();
}

這個注解表面是標注在方法上使用的,保留策略為Class級別。

現(xiàn)在步入本文的正題,如果你連注解是什么都還不清楚,建議先去了解了解注解在來看下文這樣會更好些。通過閱讀本文您將一步一步的明白編譯時注解的到底是怎么一回事,會讓你避免很多坑,之后在回過頭來回想一下可能就會覺得原來Butterknife也就那么一回事。

  • 配置編譯時注解
    在安卓中配置編譯時注解很方便,谷歌已經(jīng)幫我們做好了很多措施了。下面一步一步講解
    首先打開AndroidStudio 創(chuàng)建一個Project,我這里的名字叫做AndZilla,然后在其下創(chuàng)建兩個Module,注意這兩個Module類型是javalibrary的。這里我創(chuàng)建了一個名為ioc-annotation和ioc-compiler的兩個javalibrary。一個是用來存放注解的,我們的下面例子中用的注解就是存放在這個模塊下的,另一個是用來編譯注解庫的,也就是讓我們自定義的注解生效的模塊。其次在創(chuàng)建一個android library,我這里叫做ioc-api.這個模塊是給你的應用調(diào)用使用的。
編譯時注解的結(jié)構(gòu)
  • 萬事起于一個HelloWorld
    學每一項技術(shù)之前都會先去做一個HelloWorld。這里也不例外我們就從一個HelloWorld開始,先讓代碼幫我們自動創(chuàng)建一個HelloWorld類。
1.我們先在ioc-annotation模塊下自定義個注解取名為HelloWorld,里面有一個String類型的屬性。
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface HelloWorld {

    String say();
}
2.在ioc-compiler模塊下創(chuàng)建一個繼承自AbstractProcessor的類,這里取名為HelloWorldProcessor。

這里先來介紹一下這個類,這是一個注解處理器類。在編譯代碼的時候會執(zhí)行這個類的。繼承這個類之后我們需要實現(xiàn)里面的process方法,這個方法就是來處理我們的注解用的。
此類中還是其他幾個方法這里簡單介紹一下:
public void init(ProcessingEnvironment processingEnv)
這個方法一次編譯過程中只會執(zhí)行一次,在這里我們可以初始化或者獲取自己所需要的一些參數(shù)。
public Set<String> getSupportedAnnotationTypes()
實現(xiàn)此方法告訴這個注解處理類,需要處理的注解參數(shù)。
public SourceVersion getSupportedSourceVersion()
這個方法用來指定只是的JDK版本。
public Set<String> getSupportedOptions()
這個方法返回該注解處理類所支持的參數(shù)。其實上面所說的getSupportSourceVersiongetSupportedSourceVersion都可以用注解來代替配置。
該方法的英文介紹:If the processor class is annotated with SupportedOptions, return an unmodifiable set with the same set of strings as the annotation. If the class is not so annotated, an empty set is returned.
如果這個處理類使用注解配置選項,這返回這個配置集合,否則返回空集合。
public abstract boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
這個方法用來處理我們定義的注解。
現(xiàn)在我們在ioc-compiler的gradle文件中引入如下依賴:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.auto.service:auto-service:1.0-rc2'//配置谷歌AutoService注解,使用它可以省掉很多配置
    compile 'com.squareup:javapoet:1.8.0'//這是一個簡單強大的代碼創(chuàng)建工具
    compile project(':ioc-annotation')//引入我們自己的注解模塊
}

同步完成之后在我們的HelloWorldProcessor上加上@AutoService(Processor.class)的標注并加上下面的哦配置代碼:

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

    private Elements elements;//輔助工具類
    private Filer filer;//用來寫入文件用

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elements=processingEnvironment.getElementUtils();
        filer=processingEnvironment.getFiler();
    }

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

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations=new HashSet<>();
        annotations.add(HelloWorld.class.getSimpleName());
        return annotations;
    }

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

現(xiàn)在我們重點來處理pricess中的功能。
我們在process方法中加入這些代碼,我們把一些關(guān)鍵信息打印出來看看:

 Set<TypeElement > typeElements=ElementFilter.typesIn(roundEnvironment.getElementsAnnotatedWith(HelloWorld.class));
 for (TypeElement typeElement:typeElements){
      System.out.println("#######"+typeElement.getSimpleName());
      System.out.println("#######"+typeElement.getEnclosingElement().asType());
      System.out.println("#######"+typeElement.getQualifiedName());
      System.out.println("#######"+typeElement.getSuperclass().toString());
}

之后我們在app模塊中的gradle文件下加入如下依賴:

compile project(path: ':ioc-annotation')
annotationProcessor   project(':ioc-compiler')

并在MainActivity類上加入@HelloWorld注解

@HelloWorld(say="MainActivity ")
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

然后,clean project,build project,如果在Gradle Console中可以看到輸出的信息:

#######MainActivity
#######ggx.com.iocdemo
#######ggx.com.iocdemo.MainActivity
#######android.support.v7.app.AppCompatActivity

表明你已經(jīng)成功了。從輸出信息中我們看到了很多信息。這些信息都是來自于Element對象,Element類的結(jié)構(gòu)如下:

Element類及其子類

ExecutableElement:表示可執(zhí)行的元素如構(gòu)造方法,普通方法。
PackageElement:表示包元素
TypeElement:表示類元素
TypeParameterElement:表示參數(shù)化類元素 也就是泛型。
VariableElement:表示屬性字段元素。
這里我只簡單介紹一下,每種元素都對應一個內(nèi)容,具體的API可以參考這篇Element API文檔
在上面的例子中我用的是TypeElement元素,因為我的注解是標注在類上的。通過TypeElement可以獲取到被標注類的所在包,類名,實現(xiàn)的接口以及繼承的父類等。

3.生成HelloWorld類

這里我們創(chuàng)建的HelloWorld類名規(guī)則是按照注解標注類的類名加$$HelloWorld,并添加say方法,方法體中打印出注解中say的值。
我們將process中的代碼替換成如下:

 Set<TypeElement > typeElements=ElementFilter.typesIn(roundEnvironment.getElementsAnnotatedWith(HelloWorld.class));
for (TypeElement typeElement:typeElements){
      String say=typeElement.getAnnotation(HelloWorld.class).say();
       MethodSpec.Builder sayBuilder=MethodSpec.methodBuilder("say")
               .addModifiers(Modifier.PUBLIC)
               .returns(void.class)
               .addStatement("$T.out.println($S)",System.class,say);
        TypeSpec clazz=TypeSpec.classBuilder("HelloWorld$$"+typeElement.getSimpleName())
                .addModifiers(Modifier.PUBLIC)
                .addMethod(sayBuilder.build()).build();
         String packageName=elements.getPackageOf(typeElement).getQualifiedName().toString();
         try {
              //將filer替換成System.out就可以在控制臺看到輸出信息
              JavaFile.builder(packageName,clazz).build().writeTo(filer);
          } catch (IOException e) {
              e.printStackTrace();
}

對javapoet這個工具陌生的請點擊這里自行腦補官方的readme對使用方法介紹的非常清楚
現(xiàn)在點擊build之后我們可以在build/generated/source/apt/debug或者release文件夾下看到生成的代碼:

package ggx.com.iocdemo;
import java.lang.System;
public class HelloWorld$$MainActivity {
  public void say() {
    System.out.println("MainActivity");
  }
}

至此,整套流程介紹完了,不知道有沒有看明白了,不明白的話可以在評論去交流也可以加群號(下面有)。既然能生成HelloWorld類那么就可以生成你想要的一切類,沒有做不到只有想不到。Butterknife也是通過自動創(chuàng)建類,來代替你手寫findViewById。后面講解基于編譯時注解的實現(xiàn)。

歡迎共同探討更多安卓,java,c/c++相關(guān)技術(shù)QQ群:392154157

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,769評論 18 399
  • 什么是注解注解分類注解作用分類 元注解 Java內(nèi)置注解 自定義注解自定義注解實現(xiàn)及使用編譯時注解注解處理器注解處...
    Mr槑閱讀 1,100評論 0 3
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,523評論 25 708
  • 序 對于android應用來說,發(fā)布release版本的時候,需要有個正式的簽名,這個時候就需要用到jarsign...
    go4it閱讀 1,906評論 0 0
  • 謝謝你 對不起 我怕聽到這兩個詞 真的怕了 也許吧 我沒有那種命 想和一個我愛的又愛我的人在一起 有那么難嗎 我在...
    幸福密碼_173f閱讀 255評論 0 0