如何使用Android注解處理器
我們就可以結(jié)合今天的Annotation Processing Tool(APT)來(lái)自定義注解處理器。
注解處理器簡(jiǎn)單解釋就是收集我們標(biāo)記的注解,處理注解上提供的信息。
本篇用我之前寫的Saber舉例說(shuō)明。
1.定義注解
推薦New -> Module -> Java Library,新建一個(gè)Java Library Module,命名為xx-annotation。用來(lái)單獨(dú)存放注解。
既然是注解處理器,那么首先需要有注解。自定義一個(gè)注解使用@interface關(guān)鍵字。
public @interface LiveData {
}
然后我們需要用到注解的注解,也就是元注解來(lái)控制注解的行為。這里我簡(jiǎn)單介紹一些元注解。
- Retention 表示注解的保留范圍。值用
RetentionPolicy枚舉類型表示,分為CLASS、RUNTIME、SOURCE。 Target表示注解的使用范圍。值用ElementType枚舉類型表示,有TYPE(作用于類)、FIELD(作用于屬性)、METHOD(作用于方法)等。
這里我的@LiveData注解作用是為了便于創(chuàng)建LiveData,而創(chuàng)建時(shí)需要知道數(shù)據(jù)類型。所以這個(gè)注解的使用范圍就是類和屬性。
其次這個(gè)注解處理生成模板代碼后,我們不需要保留在編譯后的.class文件中。所以可以使用SOURCE。
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface LiveData {
}
2.實(shí)現(xiàn)處理器
首先New -> Module -> Java Library,新建一個(gè)Java Library Module,命名為xx-complier。用來(lái)存放注解處理器。

創(chuàng)建一個(gè)繼承AbstractProcessor的類LiveDataProcessor。
public class LiveDataProcessor extends AbstractProcessor {
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(LiveData.class.getCanonicalName());
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}
需要實(shí)現(xiàn)三個(gè)方法,getSupportedSourceVersion 指定支持的Java版本,getSupportedAnnotationTypes指定處理的注解。process是處理注解的地方。
不過(guò)這里還需要初始化一些工具,可以重寫init 來(lái)實(shí)現(xiàn)。
private Elements elementUtils; // 操作元素的工具類
private Filer filer; // 用來(lái)創(chuàng)建文件
private Messager messager; // 用來(lái)輸出日志、錯(cuò)誤或警告信息
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.elementUtils = processingEnv.getElementUtils();
this.filer = processingEnv.getFiler();
this.messager = processingEnv.getMessager();
}
下面就是重點(diǎn)process了,我們的注解作用范圍是類和屬性。所以我們需要將同一個(gè)類下的注解整理到一起。這里使用getElementsAnnotatedWith循環(huán)所有注解元素。
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(LiveData.class)) {
if (element.getKind() == ElementKind.FIELD) {
handlerField((VariableElement) element); // 表示一個(gè)字段
}
if (element.getKind() == ElementKind.CLASS) {
handlerClass((TypeElement) element); // 表示一個(gè)類或接口
}
// ExecutableElement表示某個(gè)類或接口的方法
}
return true;
}
private void handlerClass(TypeElement element) {
ClassEntity classEntity = new ClassEntity(element);
String className = element.getSimpleName().toString();
if (classEntityMap.get(className) == null) {
classEntityMap.put(className, classEntity);
}
}
private void handlerField(VariableElement element) {
FieldEntity fieldEntity = new FieldEntity(element);
String className = fieldEntity.getClassSimpleName();
if (classEntityMap.get(className) == null) {
classEntityMap.put(className,
new ClassEntity((TypeElement) element.getEnclosingElement()));
}
ClassEntity classEntity = classEntityMap.get(className);
classEntity.addFieldEntity(fieldEntity);
}
上面代碼中的element.getKind()獲取的是元素種類,對(duì)應(yīng)的基本是上面元注解ElementType的類型。
| ElementType | ElementKind | Element |
|---|---|---|
| TYPE | CLASS | TypeElement |
| FIELD | FIELD | VariableElement |
| METHOD | METHOD | ExecutableElement |
下面是封裝的簡(jiǎn)易element,便于實(shí)際的使用。
class ClassEntity {
private final TypeElement element;
private final Name classSimpleName;
private final Map<String, FieldEntity> fields = new HashMap<>();
public ClassEntity(TypeElement element) {
this.element = element;
this.classSimpleName = element.getSimpleName();
}
public String getClassSimpleName() {
return classSimpleName.toString();
}
public void addFieldEntity(FieldEntity fieldEntity) {
String fieldName = fieldEntity.getElement().toString();
if (fields.get(fieldName) == null) {
fields.put(fieldName, fieldEntity);
}
}
public TypeElement getElement() {
return element;
}
public Map<String, FieldEntity> getFields() {
return fields;
}
}
class FieldEntity {
private VariableElement element;
private String classSimpleName;
public FieldEntity(VariableElement element) {
this.element = element;
this.classSimpleName = element.getEnclosingElement().getSimpleName().toString();
}
public VariableElement getElement() {
return element;
}
public String getClassSimpleName() {
return classSimpleName;
}
}
下面就是使用JavaPoet來(lái)生成代碼,具體使用見(jiàn)JavaPoet使用攻略。這部分直接上代碼:
private JavaFile brewViewModel(Map.Entry<String, ClassEntity> item) {
ClassEntity classEntity = item.getValue();
LiveData liveData = classEntity.getElement().getAnnotation(LiveData.class);
/*類名*/
String className = classEntity.getElement().getSimpleName().toString() + "ViewModel";
ClassName viewModelClazz = ClassName.get("androidx.lifecycle", "ViewModel");
TypeSpec.Builder builder = TypeSpec
.classBuilder(className)
.addModifiers(Modifier.PUBLIC)
.superclass(viewModelClazz);
// 優(yōu)先執(zhí)行類LiveData注解
if (liveData != null){
TypeName valueTypeName = ClassName.get(classEntity.getElement());
brewLiveData(classEntity.getClassSimpleName(), valueTypeName, builder);
}else {
Map<String, FieldEntity> fields = classEntity.getFields();
for (FieldEntity fieldEntity : fields.values()){
String fieldName = StringUtils.upperCase(fieldEntity.getElement().getSimpleName().toString());
TypeName valueTypeName = ClassName.get(fieldEntity.getElement().asType());
brewLiveData(fieldName, valueTypeName, builder);
}
}
TypeSpec typeSpec = builder.build();
// 指定包名
return JavaFile.builder("com.zl.weilu.saber.viewmodel", typeSpec).build();
}
private void brewLiveData(String fieldName, TypeName valueTypeName, TypeSpec.Builder builder){
String liveDataType;
ClassName liveDataTypeClassName;
liveDataType = "m$L = new MutableLiveData<>()";
liveDataTypeClassName = ClassName.get("androidx.lifecycle", "MutableLiveData");
ParameterizedTypeName typeName = ParameterizedTypeName.get(liveDataTypeClassName, valueTypeName);
FieldSpec field = FieldSpec.builder(typeName, "m" + fieldName, Modifier.PRIVATE)
.build();
MethodSpec getMethod = MethodSpec
.methodBuilder("get" + fieldName)
.addModifiers(Modifier.PUBLIC)
.returns(field.type)
.beginControlFlow("if (m$L == null)", fieldName)
.addStatement(liveDataType, fieldName)
.endControlFlow()
.addStatement("return m$L", fieldName)
.build();
MethodSpec getValue = MethodSpec
.methodBuilder("get" + fieldName + "Value")
.addModifiers(Modifier.PUBLIC)
.returns(valueTypeName)
.addStatement("return this.$N().getValue()", getMethod)
.build();
MethodSpec setMethod = MethodSpec
.methodBuilder("set" + fieldName)
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(valueTypeName, "mValue")
.beginControlFlow("if (this.m$L == null)", fieldName)
.addStatement("return")
.endControlFlow()
.addStatement("this.m$L.setValue(mValue)", fieldName)
.build();
MethodSpec postMethod = MethodSpec
.methodBuilder("post" + fieldName)
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(valueTypeName, "mValue")
.beginControlFlow("if (this.m$L == null)", fieldName)
.addStatement("return")
.endControlFlow()
.addStatement("this.m$L.postValue(mValue)", fieldName)
.build();
builder.addField(field)
.addMethod(getMethod)
.addMethod(getValue)
.addMethod(setMethod)
.addMethod(postMethod);
}
輸出文件:
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
...
for (Map.Entry<String, ClassEntity> item : classEntityMap.entrySet()) {
try {
brewViewModel(item).writeTo(filer);
} catch (Exception e) {
messager.printMessage(Diagnostic.Kind.ERROR, e.getMessage(), item.getValue().getElement());
}
}
return true;
}
3.注冊(cè)處理器
注冊(cè)處理器才可以使處理器生效,使用Google開(kāi)源的AutoService的庫(kù)。
dependencies {
implementation 'com.squareup:javapoet:1.13.0'
implementation 'com.google.auto.service:auto-service:1.0-rc7'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
}
然后添加@AutoService注解即可。
@AutoService(Processor.class)
public class LiveDataProcessor extends AbstractProcessor {
}
4.調(diào)試注解處理器
項(xiàng)目的gradle.properties中配置:
org.gradle.daemon=true org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
接著Run -> Edit Configurations -> 點(diǎn)擊左上角加號(hào) -> 選擇 Remote -> 指定module(可選)

注意兩個(gè)端口號(hào)一致。然后選擇添加的“APT”,點(diǎn)擊debug按鈕啟動(dòng)。
后面就是打斷點(diǎn),編譯項(xiàng)目即可。
5.支持增量編譯
Gradle 在 5.0 增加了對(duì) Java 增量編譯的支持,通過(guò)增量編譯,我們能夠獲得一些優(yōu)點(diǎn):
- 更少的編譯耗時(shí)
- 更少的字節(jié)碼修改
如果注解處理器沒(méi)有支持增量編譯,那么編譯時(shí),會(huì)輸出以下日志:
w: [kapt] Incremental annotation processing requested, but support is disabled because the following processors are not incremental: com.x.XXProcessor (NON_INCREMENTAL).
Gradle 支持兩種注解處理器的增量編譯:isolating 和 aggregating。
支持方法是在 META-INF/gradle/incremental.annotation.processors 文件中聲明支持增量編譯的注解處理器。
xx-complier/src/main/
├── java
│ ...
│ └── LiveDataProcessor
└── resources
└── META-INF
├── gradle
│ └── incremental.annotation.processors
└── services
└── javax.annotation.processing.Processor
incremental.annotation.processors內(nèi)容如下:
com.zl.weilu.saber.compiler.LiveDataProcessor,aggregating
這部分詳細(xì)內(nèi)容見(jiàn) 讓 Annotation Processor 支持增量編譯。
6.使用處理器
添加依賴:
dependencies {
implementation project(':saber-annotation')
annotationProcessor project(':saber-compiler')
}
首先創(chuàng)建一個(gè)類,使用@LiveData注解標(biāo)記你要保存的數(shù)據(jù)。
public class SeekBar {
@LiveData
Integer value;
}
Build – > Make Project 生成代碼如下:
public class SeekBarViewModel extends ViewModel {
private MutableLiveData<Integer> mValue;
public MutableLiveData<Integer> getValue() {
if (mValue == null) {
mValue = new MutableLiveData<>();
}
return mValue;
}
public Integer getValueValue() {
return getValue().getValue();
}
public void setValue(Integer mValue) {
if (this.mValue == null) {
return;
}
this.mValue.setValue(mValue);
}
public void postValue(Integer mValue) {
if (this.mValue == null) {
return;
}
this.mValue.postValue(mValue);
}
}
至此,我們就完成了一個(gè)簡(jiǎn)單的自定義處理器。
以上就是如何使用Android注解處理器的詳細(xì)內(nèi)容,更多關(guān)于Android注解處理器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Flutter給控件實(shí)現(xiàn)鉆石般的微光特效
這篇文章主要給大家介紹了關(guān)于Flutter給控件實(shí)現(xiàn)鉆石般的微光特效的相關(guān)資料,實(shí)現(xiàn)的效果非常不錯(cuò),非常適合大家做開(kāi)發(fā)的時(shí)候參考,需要的朋友可以參考下2021-08-08
Android 應(yīng)用更換皮膚實(shí)現(xiàn)方法
本文主要介紹Android 應(yīng)用更換皮膚,Android應(yīng)用如果想更換皮膚這里幫大家整理了相關(guān)資料,有需要的小伙伴可以參考下2016-08-08
Android Handler 原理分析及實(shí)例代碼
這篇文章主要介紹了Android Handler 原理分析及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02
Android 顯示和隱藏輸入法實(shí)現(xiàn)代碼
本文所要介紹的這個(gè)方法可以轉(zhuǎn)換軟件輸入法在窗體中的顯示狀態(tài),具體實(shí)現(xiàn)代碼如下,感興趣的你可以參考下哈,希望可以幫助到你2013-03-03
Android dataBinding與ListView及事件詳解
這篇文章主要介紹了Android dataBinding與ListView及事件詳解的相關(guān)資料,需要的朋友可以參考下2016-10-10
Flutter實(shí)現(xiàn)webview與原生組件組合滑動(dòng)的示例代碼
這篇文章主要介紹了Flutter實(shí)現(xiàn)webview與原生組件組合滑動(dòng)的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
Android 圖片保存到相冊(cè)不顯示的解決方案(兼容Android 10及更高版本)
這篇文章主要介紹了Android 圖片保存到系統(tǒng)相冊(cè)不顯示的解決方案,幫助大家更好的理解和學(xué)習(xí)使用Android開(kāi)發(fā),感興趣的朋友可以了解下2021-04-04
Android使用SharedPreferences存儲(chǔ)數(shù)據(jù)的實(shí)現(xiàn)方法
這篇文章主要介紹了Android使用SharedPreferences存儲(chǔ)數(shù)據(jù)的實(shí)現(xiàn)方法,可實(shí)現(xiàn)針對(duì)短信的臨時(shí)保存功能,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2016-06-06

