先在項(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),再寫方法申屹,再寫類
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;
}