[TOC]
> APT(Annotation Processing Tool)是一種處理注釋的工具,它對源代碼文件進行檢測找出其中的Annotation,根據注解自動生成代碼。 Annotation處理器在處理Annotation時可以根據源文件中的Annotation生成額外的源文件和其它的文件(文件具體內容由Annotation處理器的編寫者決定),APT還會編譯生成的源文件和原來的源文件,將它們一起生成class文件。

## annotationProcessor 與 android-apt
**annotationProcessor** 是APT工具中的一種,他是google開發的內置框架,可以直接在build.gradle文件中使用,推薦大家使用
**android-apt** 是由一位開發者自己開發的apt框架,源代碼托管在[這里](https://bitbucket.org/hvisser/android-apt),目前不推薦大家使用了
## 整體設計
一般基于APT實現一個框架,項目目錄一般分為三個部分
1. xxxx-annotation:該框架所使用的全部注解,及其相關類
2. xxxx-compiler:注解編譯處理器,引入“xxxx-annotation”,在編譯器把注解標注的相關目標類生成映射文件
3. xxxx-api/xxxx-library:功能的具體實現以及暴露給外面的api
下面我們仔細說是xxxx-compiler。
## AbstractProcessor
~~~
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env) { }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) {
}
@Override
public Set<String> getSupportedAnnotationTypes() { }
@Override
public SourceVersion getSupportedSourceVersion(
~~~
### init
init(ProcessingEnvironment env): 【核心】
每一個注解處理器類都必須有一個空的構造函數。然而,這里有一個特殊的init()方法,它會被注解處理工具調用,并輸入ProcessingEnviroment參數。
ProcessingEnviroment提供很多有用的工具類Elements,Types和Filer。后面我們將看到詳細的內容?
* Filer是個接口,支持通過注解處理器創建新文件?
* Elements 元素操作輔助工具類
### process
process(Set annotations, RoundEnvironment env):【核心】
這相當于每個處理器的主函數main()。你在這里寫你的掃描、評估和處理注解的代碼,以及生成Java文件。
輸入參數annotations 請求處理的注解類型集合
輸入參數RoundEnviroment,可以讓你查詢出包含特定注解的被注解元素,相當于“有關全局源碼的上下文環境”。后面我們將看到詳細的內容。
@return 如果返回 true,則這些注解已聲明并且不要求后續 Processor 處理它們;如果返回 false,則這些注解未聲明并且可能要求后續 Processor 處理它們
### getSupportedAnnotationTypes
這里你必須指定,這個注解處理器是注冊給哪個注解的。注意,它的返回值是一個字符串的集合,包含本處理器想要處理的注解類型的合法全稱。
換句話說,你在這里定義你的注解處理器注冊到哪些注解上。
### getSupportedSourceVersion
用來指定你使用的Java版本。通常這里返回SourceVersion.latestSupported()。
### 例子
~~~
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private Messager mMessager;
private Elements mElementUtils;
private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mMessager = processingEnv.getMessager();
mElementUtils = processingEnv.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");
mProxyMap.clear();
//得到所有的注解
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
VariableElement variableElement = (VariableElement) element;
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
String fullClassName = classElement.getQualifiedName().toString();
ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
if (proxy == null) {
proxy = new ClassCreatorProxy(mElementUtils, classElement);
mProxyMap.put(fullClassName, proxy);
}
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
int id = bindAnnotation.value();
proxy.putElement(id, variableElement);
}
//通過遍歷mProxyMap,創建java文件
for (String key : mProxyMap.keySet()) {
ClassCreatorProxy proxyInfo = mProxyMap.get(key);
try {
mMessager.printMessage(Diagnostic.Kind.NOTE, " --> create " + proxyInfo.getProxyClassFullName());
JavaFileObject jfo = processingEnv.getFiler().createSourceFile(proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement());
Writer writer = jfo.openWriter();
writer.write(proxyInfo.generateJavaCode());
writer.flush();
writer.close();
} catch (IOException e) {
mMessager.printMessage(Diagnostic.Kind.NOTE, " --> create " + proxyInfo.getProxyClassFullName() + "error");
}
}
mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
return true;
~~~
通過roundEnvironment.getElementsAnnotatedWith(BindView.class)得到所有注解elements,然后將elements的信息保存到mProxyMap中,最后通過mProxyMap創建對應的Java文件,其中mProxyMap是ClassCreatorProxy的Map集合。
ClassCreatorProxy是創建Java代碼的代理類,如下:
~~~
public class ClassCreatorProxy {
private String mBindingClassName;
private String mPackageName;
private TypeElement mTypeElement;
private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();
public ClassCreatorProxy(Elements elementUtils, TypeElement classElement) {
this.mTypeElement = classElement;
PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);
String packageName = packageElement.getQualifiedName().toString();
String className = mTypeElement.getSimpleName().toString();
this.mPackageName = packageName;
this.mBindingClassName = className + "_ViewBinding";
}
public void putElement(int id, VariableElement element) {
mVariableElementMap.put(id, element);
}
/**
* 創建Java代碼
* @return
*/
public String generateJavaCode() {
StringBuilder builder = new StringBuilder();
builder.append("package ").append(mPackageName).append(";\n\n");
builder.append("import com.example.gavin.apt_library.*;\n");
builder.append('\n');
builder.append("public class ").append(mBindingClassName);
builder.append(" {\n");
generateMethods(builder);
builder.append('\n');
builder.append("}\n");
return builder.toString();
}
/**
* 加入Method
* @param builder
*/
private void generateMethods(StringBuilder builder) {
builder.append("public void bind(" + mTypeElement.getQualifiedName() + " host ) {\n");
for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String name = element.getSimpleName().toString();
String type = element.asType().toString();
builder.append("host." + name).append(" = ");
builder.append("(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));\n");
}
builder.append(" }\n");
}
public String getProxyClassFullName()
{
return mPackageName + "." + mBindingClassName;
}
public TypeElement getTypeElement()
{
return mTypeElement;
~~~
效果生成
~~~
public class MainActivity_ViewBinding {
public void bind(com.example.gavin.apttest.MainActivity host) {
host.mButton = (android.widget.Button) (((android.app.Activity) host).findViewById(2131165218));
host.mTextView = (android.widget.TextView) (((android.app.Activity) host).findViewById(2131165321));
}
}
~~~
## javapoet
上面在ClassCreatorProxy中,通過StringBuilder來生成對應的Java代碼。這種做法是比較麻煩的,還有一種更優雅的方式,那就是javapoet。
~~~
public class ClassCreatorProxy {
//省略部分代碼...
/**
* 創建Java代碼
* @return
*/
public TypeSpec generateJavaCode2() {
TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethods2())
.build();
return bindingClass;
}
/**
* 加入Method
*/
private MethodSpec generateMethods2() {
ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(host, "host");
for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String name = element.getSimpleName().toString();
String type = element.asType().toString();
methodBuilder.addCode("host." + name + " = " + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));");
}
return methodBuilder.build();
}
public String getPackageName() {
return mPackageName;
~~~
最后在?BindViewProcessor中
~~~
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//省略部分代碼...
//通過javapoet生成
for (String key : mProxyMap.keySet()) {
ClassCreatorProxy proxyInfo = mProxyMap.get(key);
JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCode2()).build();
try {
// 生成文件
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
return true;
}
~~~
## 參考資料
[深入理解Java:注解(Annotation)–編譯時注解的處理](https://blog.csdn.net/fei20121106/article/details/73742537)
[你必須知道的APT、annotationProcessor、android-apt、Provided、自定義注解](https://blog.csdn.net/xx326664162/article/details/68490059)
[【Android】APT](https://www.jianshu.com/p/7af58e8e3e18)
[javapoet](https://github.com/square/javapoet)
[ANNOTATION PROCESSING](http://hannesdorfmann.com/annotation-processing/annotationprocessing101)
- Android
- 四大組件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介紹
- MessageQueue詳細
- 啟動流程
- 系統啟動流程
- 應用啟動流程
- Activity啟動流程
- View
- view繪制
- view事件傳遞
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大數據
- Binder小結
- Android組件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 遷移與修復
- Sqlite內核
- Sqlite優化v2
- sqlite索引
- sqlite之wal
- sqlite之鎖機制
- 網絡
- 基礎
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP進化圖
- HTTP小結
- 實踐
- 網絡優化
- Json
- ProtoBuffer
- 斷點續傳
- 性能
- 卡頓
- 卡頓監控
- ANR
- ANR監控
- 內存
- 內存問題與優化
- 圖片內存優化
- 線下內存監控
- 線上內存監控
- 啟動優化
- 死鎖監控
- 崩潰監控
- 包體積優化
- UI渲染優化
- UI常規優化
- I/O監控
- 電量監控
- 第三方框架
- 網絡框架
- Volley
- Okhttp
- 網絡框架n問
- OkHttp原理N問
- 設計模式
- EventBus
- Rxjava
- 圖片
- ImageWoker
- Gilde的優化
- APT
- 依賴注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 協程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 運行期Java-hook技術
- 編譯期hook
- ASM
- Transform增量編譯
- 運行期Native-hook技術
- 熱修復
- 插件化
- AAB
- Shadow
- 虛擬機
- 其他
- UI自動化
- JavaParser
- Android Line
- 編譯
- 疑難雜癥
- Android11滑動異常
- 方案
- 工業化
- 模塊化
- 隱私合規
- 動態化
- 項目管理
- 業務啟動優化
- 業務架構設計
- 性能優化case
- 性能優化-排查思路
- 性能優化-現有方案
- 登錄
- 搜索
- C++
- NDK入門
- 跨平臺
- H5
- Flutter
- Flutter 性能優化
- 數據跨平臺