Android 編譯時註解-初認識

背景

編譯時註解愈來愈多的出如今各大開源框架使用中,好比javascript

JakeWharton/butterknife viewhtml

greenrobot/EventBus 事件java

square/dagger 依賴注入android

相似這樣的庫在開發和工做中已經愈來愈多,它們旨在幫助咱們在效率爲前提的狀況下幫助開發者快速開發,節約時間成本。而它們都使用了編譯時註解的思想。git

正由於如此火熱,因此有必要好好學習其中的實現原理,方便解決由於編譯時註解致使的問題,同時可將此技術運用到本身的開源庫中github

思想

編譯時註解框架在編寫時有相對固定的格式,分包爲例api

這裏寫圖片描述

格式相對固定,可是也能夠靈活變更,好比講apiannotations結合在一個moudelapp

moudel中的依賴關係也很是的固定框架

processors依賴包有api- annotationside

app依賴包有 api -annotations-processors

其中除了appandroid moudel之外,其餘所有均是java moudel

annotations註解

在講解annotations註解以前,須要對java和android註解有大體的瞭解,能夠參考我以前的博客

Java-註解詳解

Android-註解詳解

先初始一個HelloWordAtion註解標註Target爲ElementType.TYPE修飾類對象

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface HelloWordAtion {
    String value();
}複製代碼

通常一個註解須要對應一個註解處理器,註解處理器在processors處理

processors 註解處理器

對應註解的處理器須要繼承AbstractProcessor類,須要複寫如下4個方法:

init

init(ProcessingEnvironment processingEnv)會被註解處理工具調用

process

process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)這至關於每一個處理器的主函數main(),你在這裏寫你的掃描、評估和處理註解的代碼,以及生成Java文件。

getSupportedAnnotationTypes

etSupportedAnnotationTypes()這裏必須指定,這個註解處理器是註冊給哪一個註解的。注意,它的返回值是一個字符串的集合,包含本處理器想要處理的註解類型的合法全稱

@return 註解器所支持的註解類型集合,若是沒有這樣的類型,則返回一個空集合

getSupportedSourceVersion

指定使用的Java版本,一般這裏返回SourceVersion.latestSupported(),默認返回SourceVersion.RELEASE_6 `

@return 使用的Java版本

生成註解處理器

AbstractProcessor有了深刻的瞭解,知道核心的初始編譯時編寫代碼的方法及時process,在process中咱們經過獲得傳遞過來的數據,寫入代碼,這裏先採用打印的方式,簡單輸出信息,後續會詳細講解如何本身實現 butterknife功能

public class HelloWordProcessor extends AbstractProcessor {

    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // Filer是個接口,支持經過註解處理器建立新文件
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(HelloWordAtion.class)) {

            if (!(element instanceof TypeElement)) {
                return false;
            }

            TypeElement typeElement = (TypeElement) element;
            String clsNmae = typeElement.getSimpleName().toString();
            String msg = typeElement.getAnnotation(HelloWordAtion.class).value();

            System.out.println("clsName--->"+clsNmae+" msg->"+msg);
        }
        return true;
    }

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

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}複製代碼

到這一步HelloWordAtion對應的註解處理器已經編寫完成,這裏簡單的打印了HelloWordAtion註解的class和註解指定的value信息

準備工做完成之後,app觸發調用

@HelloWordAtion("hello")
public class MainActivity extends AppCompatActivity {

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

這裏註解註釋的類MainActivity而且指定valuehello,到此準備工做就算完成了,這時若是你直接編譯或者運行工程的話,是看不到任何輸出信息的,這裏還要作的一步操做是指定註解處理器的所在,須要作以下操做:

  • 一、在 processors 庫的 main 目錄下新建 resources 資源文件夾;

  • 二、在 resources文件夾下創建 META-INF/services 目錄文件夾;

  • 三、在 META-INF/services 目錄文件夾下建立 javax.annotation.process.Processors 文件;

  • 四、在 javax.annotation.process.Processors 文件寫入註解處理器的全稱,包括包路徑;

經歷了以上步驟之後方可成功運行,可是實在是太複雜了,博主爲了配置這一步也是搞了很久,因此這裏推薦使用開源框架AutoService

AutoService

AutoService

直接在Processors中依賴

compile 'com.google.auto.service:auto-service:1.0-rc2'複製代碼

使用

@AutoService(Processor.class)
public class HelloWordProcessor extends AbstractProcessor {
xxxxxxx
}複製代碼

到這裏運行程序即可以成功看到後臺的輸出信息

這裏寫圖片描述

須要切換到右下角的Gradle Console窗口,若是變異不成功能夠clean工程之後從新運行

獲得須要的數據,下一步固然是將數據寫入到java class中,也就是題目所言的編譯時註解,如何才能寫入,這裏須要藉助Filer

Filer

AbstractProcessorinit方法中初始Filer

private Filer filer;  

    @Override  
    public synchronized void init(ProcessingEnvironment processingEnv) {  
        super.init(processingEnv);    
        filer = processingEnv.getFiler();  
    }複製代碼

到此咱們已經有了寫入的類的幫助類,還差代碼生成邏輯,這裏介紹使用javapoet

javapoet

JavaPoet一個是建立 .java 源文件的輔助庫,它能夠很方便地幫助咱們生成須要的.java 源文件,GitHub上面有很是詳細的用法,建議好好閱讀相關的使用

javapoet

processors依賴:

compile 'com.squareup:javapoet:1.8.0'複製代碼

綜合上述的技術,仿照javapoet的第一個Example生成以下代碼

@Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(HelloWordAtion.class)) {

            if (!(element instanceof TypeElement)) {
                return false;
            }

            TypeElement typeElement = (TypeElement) element;
            String clsNmae = typeElement.getSimpleName().toString();
            String msg = typeElement.getAnnotation(HelloWordAtion.class).value();

            System.out.println("clsName--->"+clsNmae+" msg->"+msg);

            // 建立main方法
            MethodSpec main = MethodSpec.methodBuilder("main")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(void.class)
                    .addParameter(String[].class, "args")
                    .addStatement("$T.out.println($S)", System.class, clsNmae+"-"+msg)
                    .build();

            // 建立HelloWorld類
            TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(main)
                    .build();

            try {
                // 生成 com.wzgiceman.viewinjector.HelloWorld.java
                JavaFile javaFile = JavaFile.builder("com.wzgiceman.viewinjector", helloWorld)
                        .addFileComment(" This codes are generated automatically. Do not modify!")
                        .build();
                // 生成文件
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }複製代碼

這裏重點講解process方法,也就是寫入代碼的方法體,咱們在javapoetExample基礎上將輸出信息改成HelloWordAtion註解獲取的信息,處處便徹底搞定編譯時註解的整個流程,clean之後運行工程,在以下路徑下即可看到自動編譯生成的HelloWorld

這裏寫圖片描述

到此簡單的編譯時註解就搞定了,可是編譯時註解的自動寫入也會致使代碼混亂,可能在屢次build編譯過程當中出現文件衝突的狀況,因此這裏須要引入android-apt

android-apt

android-apt能在編譯時期去依賴註解處理器並進行工做,但在生成 APK 時不會包含任何遺留無用的文件,輔助 Android Studio項目的對應目錄中存放註解處理器在編譯期間生成的文件

android-apt

依賴使用:

根目錄build.gradle

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'複製代碼

app

apply plugin: 'com.neenbedankt.android-apt'

apt project(':processors')複製代碼

這裏是apt替換compile依賴processors

總結

到此簡單的編譯時註解就搞定了,可是api模塊尚未涉及,彆着急接下來的博客中繼續擴展,運用掌握的編譯時註解和時下主流的butterknife框架,實現一套本身的自定義注入框架中會詳細講解api模塊的使用,你會發現原來butterknife很簡單,固然能夠自由發散,擴展回到本身的任何開源項目中,替換掉反射提升效率。火燒眉毛的小夥伴能夠去GitHub下載源碼先自行研究。


專欄

註解-編譯運行時註解


源碼

下載源碼


建議

若是你有任何的問題和建議歡迎加入QQ羣告訴我!

相關文章
相關標籤/搜索