一、概念及作用
什麼是APT?全稱是Annotation Processing Tool,翻譯過來就是註解處理工具,它的作用就是可以在代碼編譯期間對註解進行處理,並且生成Java文件,減少手動的代碼輸入,因此它能夠使我們編寫的代碼更加優雅。目前很多優秀的第三方庫就是使用APT的技術,比如butterknife、retrofit、enentBus等。
二、使用
1、在當前工程中創建註解的java library,命名爲annotation
創建註解類,命名ARouter.java。
/**
* <pre>
* author : QB
* time : 2019/7/15
* version : v1.0.0
* desc : 註解文件,提供path參數
* </pre>
*/
@Target(ElementType.TYPE)// 該註解作用在類之上
@Retention(RetentionPolicy.CLASS) // 要在編譯時進行一些預處理操作,註解會在class文件中存在
public @interface ARouter {
String path();
}
2、在當前工程中創建註解處理器的java library,命名爲compiler
(1)添加依賴
在當前library的build.gradle中添加如下依賴:
// AS3.4.1 + Gradle5.1.1 + 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'
//javaPoet依賴
implementation 'com.squareup:javapoet:1.11.1'
// 引入annotation,處理@ARouter註解
implementation project(':annotation')
(2)創建註解處理器類
創建ARouterCompiler.java類,繼承至AbstractProcessor,代碼如下:
/**
* <pre>
* author : QB
* time : 2019/7/15
* version : v1.0.0
* desc : 採用javaPoet生成文件
* </pre>
*/
//使用AutoService生成註解處理器
@AutoService(Processor.class)
//當前註解類型
@SupportedAnnotationTypes({"com.xinyartech.annotation.ARouter"})
//JDK版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
//接收參數
@SupportedOptions("content")
public class ARouterCompiler extends AbstractProcessor {
//節點信息
private Elements elementUtils;
//文件生成器
private Filer mFiler;
//日誌打印
private Messager mMessager;
private Types typeUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mMessager = processingEnvironment.getMessager();
mFiler = processingEnvironment.getFiler();
elementUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
mMessager.printMessage(Diagnostic.Kind.NOTE, processingEnvironment.getOptions().get(
"content"));
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set.isEmpty()) return false;
//拿到所有被ARouter註解的類
Set<? extends Element> elements =
roundEnvironment.getElementsAnnotatedWith(ARouter.class);
for (Element element : elements) {
//獲取完整包名
String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
//獲取類名
String className = element.getSimpleName().toString();
//打印當前類信息
mMessager.printMessage(Diagnostic.Kind.NOTE, "當前ARouter註解的類:" + className);
//最終編譯生成的java文件
String finalClassName = className + "$$ARouter";
//拿到當前類的註解
ARouter aRouter = element.getAnnotation(ARouter.class);
String path = aRouter.path();
//採用拼接的方式生成註解文件
try {
//創建java文件 用於處理業務代碼
JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + finalClassName);
//返回可操作java文件的對象
Writer writer = sourceFile.openWriter();
//設置包名
writer.write("package " + packageName + ";\n");
//設置類名
writer.write("public class " + finalClassName + " {\n");
//添加方法
writer.write("public static Class<?> findTargetClass(String path){\n");
//拿到類註解
ARouter aRouter = element.getAnnotation(ARouter.class);
//拿到當前註解的路徑
String aRouterPath = aRouter.path();
//以下爲方法邏輯
writer.write("if(path.equalsIgnoreCase(\"" + aRouterPath + "\")){\n");
writer.write("return " + className + ".class;\n}\n");
writer.write("return null;\n");
writer.write("}\n}");
//切記不要忘記關閉write
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
}
3、主項目app
(1)添加依賴
在app的build.gradle中添加如下依賴
//註解依賴
implementation project(path: ':annotation')
//依賴註解處理器
annotationProcessor project(':compiler')
如果需要傳遞參數給註解處理器,比如我們在註解處理器接受的key爲"content",那麼可以在app的build.gradle配置傳遞的值,代碼如下:
defaultConfig {
...
// 在gradle文件中配置選項參數值(用於APT傳參接收)
// 切記:必須寫在defaultConfig節點下
javaCompileOptions {
annotationProcessorOptions {
arguments = [content : 'hello javaPoet']
}
}
}
切記,一定要在defaultConfig節點下配置。
(2)創建MainActivity.java
@ARouter(path = "/main/MainActivity")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void jump(View view) {
startActivity(new Intent(this, SecondActivity$$ARouter.getTargetClass("/main" +
"/SecondActivity")));
}
}
(3)創建SecondActivity.java
@ARouter(path = "/main/SecondActivity")
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
}
在類上添加ARouter註解,此時編譯項目,會在build目錄下生成目標文件。截圖如下:
這樣就可以像代碼中那樣實現跳轉了。
二、JavaPoet
1、概念
JavaPoet是square推出的開源java代碼生成框架,提供Java Api生成.java源文件。這個框架功能非常有用,我們可以很方便的使用它根據註解、數據庫模式、協議格式等來對應生成代碼。通過這種自動化生成代碼的方式,可以讓我們用更加簡潔優雅的方式要替代繁瑣冗雜的重複工作。
2、使用
(1)需要在註解處理器的libary即compiler中build.config中加入依賴
//javaPoet依賴
implementation 'com.squareup:javapoet:1.11.1'
(2)修改ARouterCompiler.java類中代碼生成方式
主要的代碼在process()方法中。代碼如下:
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set.isEmpty()) return false;
//拿到所有被ARouter註解的類
Set<? extends Element> elements =
roundEnvironment.getElementsAnnotatedWith(ARouter.class);
for (Element element : elements) {
//獲取完整包名
String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
//獲取類名
String className = element.getSimpleName().toString();
//打印當前類信息
mMessager.printMessage(Diagnostic.Kind.NOTE, "當前ARouter註解的類:" + className);
//最終編譯生成的java文件
String finalClassName = className + "$$ARouter";
//拿到當前類的註解
ARouter aRouter = element.getAnnotation(ARouter.class);
String path = aRouter.path();
//文件處理器編寫代碼 ,採用javaPoet方式 先寫方法,在寫類,最後寫包 method->class->package
//構建方法 $T:代表類 $S:代表字符串
MethodSpec methodSpec = MethodSpec.methodBuilder("getTargetClass")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(Class.class)
.addParameter(String.class, "path")
.addStatement("return path.equalsIgnoreCase($S) ? " +
"$T.class : null", path, ClassName.get((TypeElement) element))
.build();
//構建類
TypeSpec typeSpec = TypeSpec.classBuilder(finalClassName)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(methodSpec)
.build();
//構建包
JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
.build();
//寫入到文件生成器
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
重新編譯,即可達到同樣的效果。