架構(gòu)師(五)——組件化APT(注解處理器)

image.png

先在項(xiàng)目中創(chuàng)建一個(gè)java Library穴豫,命名為annotation
創(chuàng)建注解文件ARouter.java


/**
 * <strong>Activity使用的布局文件注解</strong>
 * <ul>
 * <li>@Target(ElementType.TYPE)   // 接口律歼、類、枚舉谬盐、注解</li>
 * <li>@Target(ElementType.FIELD) // 屬性、枚舉的常量</li>
 * <li>@Target(ElementType.METHOD) // 方法</li>
 * <li>@Target(ElementType.PARAMETER) // 方法參數(shù)</li>
 * <li>@Target(ElementType.CONSTRUCTOR)  // 構(gòu)造函數(shù)</li>
 * <li>@Target(ElementType.LOCAL_VARIABLE)// 局部變量</li>
 * <li>@Target(ElementType.ANNOTATION_TYPE)// 該注解使用在另一個(gè)注解上</li>
 * <li>@Target(ElementType.PACKAGE) // 包</li>
 * <li>@Retention(RetentionPolicy.RUNTIME) <br>注解會(huì)在class字節(jié)碼文件中存在窝趣,jvm加載時(shí)可以通過(guò)反射獲取到該注解的內(nèi)容</li>
 * </ul>
 *
 * 生命周期:SOURCE < CLASS < RUNTIME
 * 1誉裆、一般如果需要在運(yùn)行時(shí)去動(dòng)態(tài)獲取注解信息推正,用RUNTIME注解
 * 2恍涂、要在編譯時(shí)進(jìn)行一些預(yù)處理操作,如ButterKnife舔稀,用CLASS注解乳丰。注解會(huì)在class文件中存在掌测,但是在運(yùn)行時(shí)會(huì)被丟棄
 * 3内贮、做一些檢查性的操作,如@Override汞斧,用SOURCE源碼注解夜郁。注解僅存在源碼級(jí)別,在編譯的時(shí)候丟棄該注解
 */
@Target(ElementType.TYPE) // 該注解作用在類之上
@Retention(RetentionPolicy.CLASS) // 要在編譯時(shí)進(jìn)行一些預(yù)處理操作粘勒。注解會(huì)在class文件中存在
public @interface ARouter {

    // 詳細(xì)路由路徑(必填)竞端,如:"/app/MainActivity"
    String path();

    // 路由組名(選填,如果開(kāi)發(fā)者不填寫庙睡,可以從path中截取出來(lái))
    String group() default "";
}

在app的build.gradle中添加依賴

// 依賴注解
implementation project(':annotation')

在app的activity中加入注解

@ARouter(path = "/app/MainActivity")
public class MainActivity extends AppCompatActivity {

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

        // 不需要了
        // RecordPathManager.joinGroup("app", "MainActivity", MainActivity.class);
    }

    public void jump(View view) {
        Intent intent = new Intent(this, OrderActivity$$ARouter.findTargetClass("/app/OrderActivity"));
        startActivity(intent);
    }
}

接下來(lái)創(chuàng)建注解處理器文件
先在根目錄的build.gradle中引入第三方庫(kù)

buildscript {
    repositories {
        // 超級(jí)實(shí)用:某某影響事富,很多被墻,強(qiáng)烈推薦阿里云鏡像更新
        maven {
            url "http://maven.aliyun.com/nexus/content/groups/public/"
        }
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.1'
        
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        // 超級(jí)實(shí)用:某某影響乘陪,很多被墻统台,強(qiáng)烈推薦阿里云鏡像更新
        maven {
            url "http://maven.aliyun.com/nexus/content/groups/public/"
        }
        google()
        jcenter()
        
    }
}

重新創(chuàng)建一個(gè)java Library,命名為compiler
在bulid.gradle中引入注解注解處理器

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    // 注冊(cè)注解啡邑,并對(duì)其生成META-INF的配置信息贱勃,rc2在gradle5.0后有坑
    // As-3.2.1 + gradle4.10.1-all + auto-service:1.0-rc2
    // implementation 'com.google.auto.service:auto-service:1.0-rc2'

    // As-3.4.1 + gradle5.1.1-all + auto-service:1.0-rc4
    compileOnly'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'

    // 引入annotation,讓注解處理器-處理注解
    implementation project(':annotation')
}

// java控制臺(tái)輸出中文亂碼
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

// jdk編譯版本1.7
sourceCompatibility = "7"
targetCompatibility = "7"

創(chuàng)建注解處理器ARouterProcessor,繼承AbstractProcessor(javax.annotation.processing.AbstractProcessor)

/**
 * 編碼此類1句話:細(xì)心再細(xì)心贵扰,出了問(wèn)題debug真的不好調(diào)試
 */
// AutoService則是固定的寫法仇穗,加個(gè)注解即可
// 通過(guò)auto-service中的@AutoService可以自動(dòng)生成AutoService注解處理器,用來(lái)注冊(cè)
// 用來(lái)生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允許/支持的注解類型戚绕,讓注解處理器處理(新增annotation module)
@SupportedAnnotationTypes({"com.netease.annotation.ARouter"})
// 指定JDK編譯版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 注解處理器接收的參數(shù)
@SupportedOptions("content")
public class ARouterProcessor extends AbstractProcessor {

    // 操作Element工具類 (類纹坐、函數(shù)、屬性都是Element)
    private Elements elementUtils;

    // type(類信息)工具類列肢,包含用于操作TypeMirror的工具方法
    private Types typeUtils;

    // Messager用來(lái)報(bào)告錯(cuò)誤恰画,警告和其他提示信息
    private Messager messager;

    // 文件生成器 類/資源,F(xiàn)ilter用來(lái)創(chuàng)建新的源文件瓷马,class文件以及輔助文件
    private Filer filer;

    // 該方法主要用于一些初始化的操作拴还,通過(guò)該方法的參數(shù)ProcessingEnvironment可以獲取一些列有用的工具類
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        // 父類受保護(hù)屬性,可以直接拿來(lái)使用欧聘。
        // 其實(shí)就是init方法的參數(shù)ProcessingEnvironment
        // processingEnv.getMessager(); //參考源碼64行
        elementUtils = processingEnvironment.getElementUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();

        // 通過(guò)ProcessingEnvironment去獲取build.gradle傳過(guò)來(lái)的參數(shù)
        String content = processingEnvironment.getOptions().get("content");
        // 有坑:Diagnostic.Kind.ERROR片林,異常會(huì)自動(dòng)結(jié)束,不像安卓中Log.e那么好使
        messager.printMessage(Diagnostic.Kind.NOTE, content);
    }

    /**
     * 相當(dāng)于main函數(shù)怀骤,開(kāi)始處理注解
     * 注解處理器的核心方法费封,處理具體的注解,生成Java文件
     *
     * @param set              使用了支持處理注解的節(jié)點(diǎn)集合(類 上面寫了注解)
     * @param roundEnvironment 當(dāng)前或是之前的運(yùn)行環(huán)境,可以通過(guò)該對(duì)象查找找到的注解蒋伦。
     * @return true 表示后續(xù)處理器不會(huì)再處理(已經(jīng)處理完成)
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set.isEmpty()) return false;

        // 獲取所有帶ARouter注解的 類節(jié)點(diǎn)
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
        // 遍歷所有類節(jié)點(diǎn)
        for (Element element : elements) {
            // 通過(guò)類節(jié)點(diǎn)獲取包節(jié)點(diǎn)(全路徑:com.netease.xxx)
            String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
            // 獲取簡(jiǎn)單類名
            String className = element.getSimpleName().toString();
            messager.printMessage(Diagnostic.Kind.NOTE, "被注解的類有:" + className);
            // 最終想生成的類文件名
            String finalClassName = className + "$$ARouter";

            // 公開(kāi)課寫法弓摘,也是EventBus寫法(https://github.com/greenrobot/EventBus)
            try {
                // 創(chuàng)建一個(gè)新的源文件(Class),并返回一個(gè)對(duì)象以允許寫入它
                JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + finalClassName);
                // 定義Writer對(duì)象痕届,開(kāi)啟寫入
                Writer writer = sourceFile.openWriter();
                // 設(shè)置包名
                writer.write("package " + packageName + ";\n");

                writer.write("public class " + finalClassName + " {\n");

                writer.write("public static Class<?> findTargetClass(String path) {\n");

                // 獲取類之上@ARouter注解的path值
                ARouter aRouter = element.getAnnotation(ARouter.class);

                writer.write("if (path.equals(\"" + aRouter.path() + "\")) {\n");

                writer.write("return " + className + ".class;\n}\n");

                writer.write("return null;\n");

                writer.write("}\n}");

                // 最后結(jié)束別忘了
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return true;
    }
}

上面的@SupportedOptions("content")韧献,是app傳遞參數(shù)給注解處理器,可以在app的build.gradle中添加

defaultConfig {
    ......
    // 在gradle文件中配置選項(xiàng)參數(shù)值(用于APT傳參接收)
    // 切記:必須寫在defaultConfig節(jié)點(diǎn)下
    javaCompileOptions {
        annotationProcessorOptions {
            arguments = [content : 'hello apt']
        }
    }
}

app中依賴注解處理器

annotationProcessor project(':compiler')

在寫文件之前研叫,可以先寫一個(gè)模擬類锤窑,比如XActivity$$ARouter.java

package com.netease.apt.test;

import com.netease.apt.MainActivity;

/**
 * 模擬APT生成后的文件樣子
 */
public class XActivity$$ARouter {

    public static Class<?> findTargetClass(String path) {
        if (path.equals("/app/MainActivity")) {
            return MainActivity.class;
        }
        return null;
    }
}

就是通過(guò)傳進(jìn)去path找到相應(yīng)的class
然后再到注解處理器里去寫

最后生成的文件在app\build\generated\source\apt\debug下,可以看到和我們之前測(cè)試寫的一樣

javapoet寫法
javapoet寫法是從內(nèi)到外的嚷炉,是面向?qū)ο蟮恼Z(yǔ)法渊啰,先寫方法實(shí)現(xiàn),再寫方法申屹,再寫類


常用api
字符串格式化規(guī)則
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    if (set.isEmpty()) return false;

    // 獲取所有帶ARouter注解的 類節(jié)點(diǎn)
    Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
    // 遍歷所有類節(jié)點(diǎn)
    for (Element element : elements) {
        // 通過(guò)類節(jié)點(diǎn)獲取包節(jié)點(diǎn)(全路徑:com.netease.xxx)
        String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
        // 獲取簡(jiǎn)單類名
        String className = element.getSimpleName().toString();
        messager.printMessage(Diagnostic.Kind.NOTE, "被注解的類有:" + className);
        // 最終想生成的類文件名
        String finalClassName = className + "$$ARouter";

        // 高級(jí)寫法绘证,javapoet構(gòu)建工具,參考(https://github.com/JakeWharton/butterknife)
        try {
            // 獲取類之上@ARouter注解的path值
            ARouter aRouter = element.getAnnotation(ARouter.class);

            // 構(gòu)建方法體
            MethodSpec method = MethodSpec.methodBuilder("findTargetClass") // 方法名
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(Class.class) // 返回值Class<?>
                    .addParameter(String.class, "path") // 參數(shù)(String path)
                    // 方法內(nèi)容拼接:
                    // return path.equals("/app/MainActivity") ? MainActivity.class : null
                    .addStatement("return path.equals($S) ? $T.class : null",
                            aRouter.path(), ClassName.get((TypeElement) element))
                    .build(); // 構(gòu)建

            // 構(gòu)建類
            TypeSpec type = TypeSpec.classBuilder(finalClassName)
                    .addModifiers(Modifier.PUBLIC) //, Modifier.FINAL)
                    .addMethod(method) // 添加方法體
                    .build(); // 構(gòu)建

            // 在指定的包名下哗讥,生成Java類文件
            JavaFile javaFile = JavaFile.builder(packageName, type)
                    .build();
            javaFile.writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    return true;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嚷那,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子忌栅,更是在濱河造成了極大的恐慌车酣,老刑警劉巖曲稼,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異湖员,居然都是意外死亡贫悄,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門娘摔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)窄坦,“玉大人,你說(shuō)我怎么就攤上這事凳寺⊙冀颍” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵肠缨,是天一觀的道長(zhǎng)逆趋。 經(jīng)常有香客問(wèn)我,道長(zhǎng)晒奕,這世上最難降的妖魔是什么闻书? 我笑而不...
    開(kāi)封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮脑慧,結(jié)果婚禮上魄眉,老公的妹妹穿的比我還像新娘。我一直安慰自己闷袒,他們只是感情好坑律,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著囊骤,像睡著了一般晃择。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上淘捡,一...
    開(kāi)封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天藕各,我揣著相機(jī)與錄音池摧,去河邊找鬼焦除。 笑死,一個(gè)胖子當(dāng)著我的面吹牛作彤,可吹牛的內(nèi)容都是我干的膘魄。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼竭讳,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼创葡!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起绢慢,我...
    開(kāi)封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤灿渴,失蹤者是張志新(化名)和其女友劉穎洛波,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體骚露,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蹬挤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了棘幸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焰扳。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖误续,靈堂內(nèi)的尸體忽然破棺而出吨悍,到底是詐尸還是另有隱情,我是刑警寧澤蹋嵌,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布育瓜,位于F島的核電站,受9級(jí)特大地震影響栽烂,放射性物質(zhì)發(fā)生泄漏爆雹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一愕鼓、第九天 我趴在偏房一處隱蔽的房頂上張望钙态。 院中可真熱鬧,春花似錦菇晃、人聲如沸册倒。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)驻子。三九已至,卻和暖如春估灿,著一層夾襖步出監(jiān)牢的瞬間崇呵,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工馅袁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留域慷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓汗销,卻偏偏與公主長(zhǎng)得像犹褒,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子弛针,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

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