注解處理器使用

本文同步至:http://blog.edreamoon.com/

注解的生命周期

注解生命周期即表明注解在代碼的什么階段生效,通過@Retention來指定,其值可以為以下三種:

  • SOURCE,源碼注解, 注解只在源碼中存在,javac在編譯成class時會把Java源程序上的源碼注解給去掉編譯成.class文件就將相應的注解去掉。
  • CLASS,編譯時注解,注解在源碼、.class文件里面存在。
  • RUNTIME,運行時注解,在運行階段還起作用

三個階段簡單表示為:java源文件–>class文件–>內存中的字節碼

注解處理器(Annotation Processor)

注解處理器用來在編譯時掃描和處理注解,是javac的一個工具。一般使用注解處理器在編譯時生成新的java文件,新生成的java文件會在編譯期同其它java文件一樣被編譯。
目前比較流行的一些庫都用到了注解處理器,如dagger2、butterknife、Android提供的data binding,相對于使用反射極大的提高了效率。

案例

Talk is cheap, show me the code! 下面使用一個demo來說明如何使用注解處理器。這個Demo比較簡單,只是在編譯項目時,打印注解信息。

工程目錄如下:

752773433.png

mj-annotate、mj-processor是java library,分別用于自定義注解、自定義注解處理器。

  1. 首先定義注解,這個很簡單
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface BindTest {
    int value();
}

這里指定為源碼注解,可以用到字段、方法、類上

  1. 在項目的Activity中使用此注解,在類、方法、字段中均使用了下。
@BindTest(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
 
    @BindTest(R.id.bt_activity_main)
    private Button button;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
 
    @BindTest(R.id.bt_activity_main)
    public void onClickButton() {
    }
}

3.注解已經使用了,但這些注解并沒有起到什么作用。如何讓項目編譯時處理這些注解并打印注解相關信息哪?接下來就是注解處理器顯神威的時候了。
定義一個處理器需要繼承AbstractProcessor,并覆蓋其中的一些方法的實現。

public class MProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        /**
         * 處理注解的地方,注解的上面說到的生成Java文件就是在此處理的,這里可以獲取注解相關的內容
         */
 
        Messager messager = processingEnv.getMessager();
        //遍歷用BindView注解的元素
        for (Element element : roundEnvironment.getElementsAnnotatedWith(BindTest.class)) {
            BindView annotation = element.getAnnotation(BindView.class);
 
            int value = annotation.value(); //注解的值
            ElementKind kind = element.getKind();//注解作用的位置
 
            if (kind == ElementKind.FIELD) {//如果這個元素是一個方法
                VariableElement variableElement = (VariableElement) element;//轉換成方法對應的element
                messager.printMessage(Diagnostic.Kind.NOTE, "field");
            } else if (kind == ElementKind.METHOD) {//如果這個元素是一個方法
                ExecutableElement executableElement = (ExecutableElement) element;//轉成方法對應的element
                messager.printMessage(Diagnostic.Kind.NOTE, "method return type : " + executableElement.getReturnType().toString());
                List<? extends VariableElement> params = executableElement.getParameters();//獲取方法所有的參數
            } else if (kind == ElementKind.CLASS) {
                TypeElement typeElement = (TypeElement) element;
                messager.printMessage(Diagnostic.Kind.NOTE, "class " + typeElement.getQualifiedName());
            }
 
            //打印注解相關的信息
            messager.printMessage(Diagnostic.Kind.NOTE, value + " : " + kind.name());
        }
 
        /**
         * 返回true表示該注解已經被處理, 后續不會再有其他處理器處理; 返回false表示仍可被其他處理器處理.
         */
        return false;
    }
 
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        /**
         * 返回支持的注解類型,限定注解處理器用到哪些注解上
         */
        Set<String> types = new LinkedHashSet<>();
        types.add(BindTest.class.getCanonicalName());
        return types;
    }
 
    @Override
    public SourceVersion getSupportedSourceVersion() {
        /**
         *指定使用的Java版本,為了更好的兼容使用SourceVersion.latestSupported()
         */
        return SourceVersion.latestSupported();
    }
}

主要實現就是getSupportedAnnotationTypes、getSupportedSourceVersion、process,代碼中已經解釋很清楚了。其中針對AbstractProcessor,也可以使用@SupportedAnnotationTypes、@SupportedSourceVersion注解來代替getSupportedAnnotationTypes() 、getSupportedSourceVersion()。也就是代碼簡化為如下:

@SupportedAnnotationTypes({"com.mj.annotate.BindView"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //..............省略..........
    }
}

但考慮到兼容性和可維護性,最好還是用覆蓋對應方法的形式實現比較好。

處理器已經寫好了,然后在 app module 中添加對 mj-processor 的依賴。

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:25.1.0'
    compile project(path: ':mj-annotate')
    compile project(path: ':mj-processor')
}

此時Rebuild下項目,并不會打印process中的log信息,還需要創建處理器說明文件: main->resources->META-INF/services->javax.annotation.processing.Processor,然后在文件中輸入定義的處理器:com.mj.processor.MProcessor。如果有多個Processor,以換行切換。這個步驟要創建多級目錄比較麻煩,我們可以使用AutoService注解簡化,在mj-processor 中添加相應依賴即可使用:

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile project(path: ':mj-annotate')
    compile 'com.google.auto.service:auto-service:1.0-rc2'
}

在 MProcessor 類使用@AutoService(Processor.class),編譯成jar包時即會自動生成上述文件。

此時再回過頭看文章開頭的目錄結構,就和各步驟對應了。
此時再Rebuild下項目就會打印log:

519130143.png

注意,此處是log是在gradle console中,為了能看到log,最好是Rebuild,或者是clean下項目。

Processor運行環境

既然Processor是編譯時處理各種注解的,而Processor也是java文件,那它是如何運行工作的哪?
Processor也是運行在虛擬機JVM中的,只不過是一個獨立的JVM,javac會啟動一個完整Java虛擬機來保證Processor的工作,所以在這里保證了java的運行環境。

APT

Processor是在編譯期工作的,而我們的apk運行時就不再需要了,但反編譯上面生成的apk會發現mj-processor的代碼。

APT(Annotation Processing Tool)就很好解決了這個問題,它能在編譯時期去依賴注解處理器并進行工作,但在生成 APK 時不會包含任何遺留的東西
修改工程目錄下的 build.gradle,添加maven倉庫、apt依賴

 repositories {
    jcenter()
    mavenCentral() //for apt
}
dependencies {
    classpath 'com.android.tools.build:gradle:2.2.3'
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' //for apt
}

然后在 app module 修改依賴:

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:25.1.0'
    compile project(':mj-annotate')
//    compile project(':mj-processor')
    apt project(':mj-processor') // 使用apt依賴
}

這樣就可以去除apk中processor相關內容

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容