前言
APT的學習要花點時間去掌握和實踐的,短時間內只能掌握知識點,更多的是在實戰(zhàn)中去實踐。其實,APT就是一種工具而已,只要用多了,自然就會熟練了,不過要想實踐之前,還是必須把基礎知識學好才能實戰(zhàn)進入開發(fā)。文章會從基礎用例講解知識點,然后再通過實戰(zhàn)進行實踐
APT簡介
APT(Annotation Processing Tool)是一種處理注解的工具,它會對源代碼中的注解進行額外的處理,比如在編譯時生成一些重復性操作的Java代碼,或者不需要程序員去關心的Java代碼等。在使用APT的過程中會涉及到下面兩個第三方庫的使用
- AutoService:這個庫的主要作用是注冊注解,并對其生成META-INF的配置信息
- JavaPoet:這個庫的主要作用是幫助我們通過類調用的形式來生成Java代碼
APT主要過程包括初始化過程和注解處理過程
- 初始化過程:獲取APT提供的工具類,為后面的注解處理提供幫助
- 注解處理過程:獲取注解的元素,對元素進行額外處理,可用JavaPoet生成Java代碼
APT流程
1、定義注解
該注解是可以在我們的項目中使用到的,且規(guī)定為注解元素的類型為Type,和在編譯時生效
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ModuleWrapper {
}
- 1
- 2
- 3
- 4
- 5
2、定義Processor
Processor會在編譯期對注解進行解析,取出對應的元素進行處理。至于AutoService則是固定的寫法,加個注解即可
@AutoService(Processor.class)
public class ModuleProcessor extends AbstractProcessor {
private Filer filerUtils; // 文件寫入
private Elements elementUtils; // 操作Element工具類
private Messager messagerUtils; // Log 日志
private Map<String, String> options; // 額外配置參數(shù)
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filerUtils = processingEnvironment.getFiler();
elementUtils = processingEnvironment.getElementUtils();
messagerUtils = processingEnvironment.getMessager();
options = processingEnvironment.getOptions();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(ModuleWrapper.class.getCanonicalName());
return types;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
initModuleMap(roundEnvironment);
return false;
}
private void initModuleMap(RoundEnvironment roundEnv) {
//獲取對應的注解元素
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(ModuleWrapper.class);
for (Element element : set) {
//如果是個類
if (element.getKind() == ElementKind.CLASS) {
//獲取類名
String clzName = element.getSimpleName().toString();
//對元素進行處理,可用javapoet生成Java代碼
......
} else {
messagerUtils.printMessage(Diagnostic.Kind.NOTE, "only support class");
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
3、AbstractProcessor實現(xiàn)方法介紹
- init():初始化過程,可通過初始化方法獲取各種工具類
- process():注解處理過程,可通過獲取注解元素后,對注解元素進行額外處理
- getSupportedAnnotationTypes():獲取需要解析的注解類型
APT知識點
1、初始化介紹
APT初始化階段為init
方法的調用,我們可以使用ProcessingEnvironment
獲取一些實用類以及獲取選項參數(shù)等
方法 | 說明 |
---|---|
getElementUtils() | 返回實現(xiàn)Elements接口的對象,用于操作元素的工具類 |
getFiler() | 返回實現(xiàn)Filer接口的對象,用于創(chuàng)建文件、類和輔助文件 |
getMessager() | 返回實現(xiàn)Messager接口的對象,用于報告錯誤信息、警告提醒 |
getOptions() | 返回指定的參數(shù)選項 |
getTypeUtils() | 返回實現(xiàn)Types接口的對象,用于操作類型的工具類 |
2、Element介紹
Element是操作元素最主要的類,可通過getElementsAnnotatedWith
獲取Element的元素,經(jīng)過getKind()
判斷元素的類型后,可強制轉換成對應的類型,在對應的類型中有著不同的方法可以調用
類型 | 說明 |
---|---|
ExecutableElement | 表示類、接口的方法元素。包括構造方法、注解類型 |
PackageElement | 表示包元素。提供對有關包及其成員的信息的訪問 |
TypeElement | 表示類、接口元素。提供對有關類型及其成員的信息的訪問 |
TypeParameterElement | 表示類、接口、方法、構造方法的參數(shù)元素 |
VariableElement | 表示字段、enum、方法、構造方法參數(shù)、局部變量、異常參數(shù) |
ElementKind為元素的類型,元素的類型判斷不需要用instanceof
去判斷,而應該通過getKind()
去判斷對應的類型
類型 | 說明 |
---|---|
PACKAGE | 包 |
ENUM | 枚舉 |
CLASS | 類 |
ANNOTATION_TYPE | 注解 |
INTERFACE | 接口 |
ENUM_CONSTANT | 枚舉常量 |
FIELD | 字段 |
PARAMETER | 方法參數(shù) |
LOCAL_VARIABLE | 局部變量 |
METHOD | 方法 |
CONSTRUCTOR | 構造方法 |
TYPE_PARAMETER | 類型參數(shù) |
3、TypeMirror介紹
TypeMirror是一個接口,表示Java編程語言中的類型。這些類型包括基本類型、引用類型、數(shù)組類型、類型變量和null類型等等
類型 | 說明 |
---|---|
ArrayType | 表示數(shù)組類型 |
DeclaredType | 表示聲明類型(類或接口類型) |
ErrorType | 表示異常類或接口類型 |
ExecutableType | 表示executable類型(方法、構造方法、初始化) |
NoType | 表示在實際類型不適合的地方使用的偽類型 |
NullType | 表示null類型 |
PrimitiveType | 表示基本數(shù)據(jù)類型 |
ReferenceType | 表示引用類型 |
TypeVariable | 表示類型變量 |
WildcardType | 表示通配符類型參數(shù) |
TypeKind為類型的屬性,類型的屬性判斷不需要用instanceof
去判斷,而應該通過getKind()
去判斷對應的屬性
類型 | 說明 |
---|---|
BOOLEAN | 基本類型boolean |
INT | 基本類型int |
LONG | 基本類型long |
FLOAT | 基本類型float |
DOUBLE | 基本類型double |
VOID | 對應于關鍵字void的偽類型 |
NULL | null類型 |
ARRAY | 數(shù)組類型 |
PACKAGE | 對應于包元素的偽類型 |
EXECUTABLE | 方法、構造方法、初始化 |
這里需要注意的是,如果我們通過注解去獲取Class類型的值,如果獲取的Class未被編譯,則會拋出MirroredTypeException異常,此時我們需要通過try-catch語句在catch里去獲取我們所需要的類元素
try {
annotation.value();//如果value為Class類型則會報異常
} catch (MirroredTypeException mte) {
DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();
TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();//通過異常去獲取類元素
}
- 1
- 2
- 3
- 4
- 5
- 6
4、Filer介紹
Filer接口支持通過注解處理器創(chuàng)建新文件??梢詣?chuàng)建三種文件類型:源文件、類文件和輔助資源文件
方法 | 說明 |
---|---|
createSourceFile | 創(chuàng)建源文件 |
createClassFile | 創(chuàng)建類文件 |
createResource | 創(chuàng)建輔助資源文件 |
5、Messager介紹
Messager接口提供注解處理器用來報告錯誤消息、警告和其他通知的方式
方法 | 說明 |
---|---|
printMessage | 打印錯誤消息 |
6、Options介紹
通過getOptions()方法獲取選項參數(shù),在gradle文件中配置選項參數(shù)值
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [ version : '1.0.0' ]
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
通過ProcessingEnvironment
去獲取對應的參數(shù)
processingEnvironment.getOptions().get("version");
- 1
7、獲取注解元素
通過RoundEnvironment
接口去獲取注解元素,通過JavaPoet生成Java代碼
方法 | 說明 |
---|---|
getElementsAnnotatedWith | 返回注解元素的集合 |
APT實戰(zhàn)
下面通過APT的實戰(zhàn),進行對項目的模塊化劃分
1、項目結構
- 創(chuàng)建Module,名為annotation,放置我們的注解類
- 創(chuàng)建Module,名為compiler,放置我們的注解處理類
- 主工程則直接依賴annotation和compiler
注意事項:創(chuàng)建Module的時候,需要選擇java Lib,而不是Android Lib
2、Gradle配置
annotation的Module必須聲明Java編譯版本
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
compiler的Module必須聲明Java編譯版本,且依賴于annotation和導入我們所需的庫
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':annotation')
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation 'com.squareup:javapoet:1.7.0'
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
主工程必須通過依賴annotaion和compiler,由于我們只是在編譯期生效,可用annotationProcessor
implementation project(':annotation')
annotationProcessor project(':compiler')
- 1
- 2
注意事項:定義編譯的jdk版本為1.7
3、定義注解
ModuleWrapper注解,表示需要加載的模塊
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ModuleWrapper {
}
- 1
- 2
- 3
- 4
- 5
IModule接口,表示當前類是一個模塊類
public interface IModule {
String getModuleName();
}
- 1
- 2
- 3
4、定義Processor
@AutoService(Processor.class)
public class ModuleProcessor extends AbstractProcessor {
private Map<String, ModuleInfo> moduleMaps = new HashMap<>();
private Filer filerUtils; // 文件寫入
private Elements elementUtils; // 操作Element 的工具類
private Messager messagerUtils; // Log 日志
private Map<String, String> options;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filerUtils = processingEnvironment.getFiler();
elementUtils = processingEnvironment.getElementUtils();
messagerUtils = processingEnvironment.getMessager();
options = processingEnvironment.getOptions();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(ModuleWrapper.class.getCanonicalName());
return types;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
try {
initModuleMap(roundEnvironment);
createModuleMap();
createModuleConstant();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* 通過注解元素獲取組件實體
*
* @param roundEnv
*/
private void initModuleMap(RoundEnvironment roundEnv) {
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(ModuleWrapper.class);
for (Element element : set) {
if (element.getKind() == ElementKind.CLASS) {
String clzName = element.getSimpleName().toString();
if (moduleMaps.get(clzName) == null) {
ModuleInfo info = new ModuleInfo(elementUtils, (TypeElement) element);
moduleMaps.put(clzName, info);
}
} else {
messagerUtils.printMessage(Diagnostic.Kind.NOTE, "only support class");
}
}
}
/**
* 創(chuàng)建組件管理者
*
* @throws IOException
*/
private void createModuleMap() throws IOException {
FieldSpec fieldSpec = FieldSpec
.builder(ParameterizedTypeName.get(HashMap.class, String.class, IModule.class)
, "moduleMap", Modifier.PRIVATE)
.initializer("new HashMap<>()")
.build();
CodeBlock.Builder codeBlock = CodeBlock.builder();
for (String key : moduleMaps.keySet()) {
ModuleInfo info = moduleMaps.get(key);
codeBlock.addStatement("moduleMap.put($S ,new $T())", info.getFullClassName(),
ClassName.get(info.packageName, info.className));
}
MethodSpec initMethod = MethodSpec.methodBuilder("init")
.addModifiers(Modifier.PUBLIC)
.addCode(codeBlock.build())
.returns(TypeName.VOID)
.build();
MethodSpec getMethod = MethodSpec.methodBuilder("get")
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "cls")
.addStatement("return moduleMap.get(cls)")
.returns(IModule.class)
.build();
ArrayList<MethodSpec> methods = new ArrayList<>();
methods.add(initMethod);
methods.add(getMethod);
TypeSpec moduleFactory = TypeSpec.classBuilder("ModuleFactory")
.addModifiers(Modifier.PUBLIC)
.addMethods(methods)
.addField(fieldSpec)
.build();
JavaFile javaFile = JavaFile.builder("com.hensen.compiler.processor", moduleFactory)
.build();
javaFile.writeTo(filerUtils);
}
/**
* 創(chuàng)建組件常量
*
* @throws IOException
*/
private void createModuleConstant() throws IOException {
TypeSpec.Builder moduleConstant = TypeSpec.classBuilder("ModuleConstant")
.addModifiers(Modifier.PUBLIC);
for (String key : moduleMaps.keySet()) {
ModuleInfo info = moduleMaps.get(key);
FieldSpec fieldSpec = FieldSpec.builder(String.class, info.className)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("$S", info.getFullClassName())
.build();
moduleConstant.addField(fieldSpec);
}
JavaFile javaFile = JavaFile.builder("com.hensen.compiler.processor", moduleConstant.build())
.build();
javaFile.writeTo(filerUtils);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
5、組件實體
組件實體保存著組件的信息
public class ModuleInfo {
public String packageName;
public String className;
public ModuleInfo(Elements elementUtils, TypeElement typeElement) {
packageName = getPackageName(elementUtils, typeElement);
className = getClassName(typeElement, packageName);
}
public String getClassName(TypeElement type, String packageName) {
int packageLen = packageName.length() + 1;
return type.getQualifiedName().toString().substring(packageLen)
.replace('.', '$');
}
public String getPackageName(Elements elementUtils, TypeElement classElement) {
PackageElement packageElement = elementUtils.getPackageOf(classElement);
return packageElement.getQualifiedName().toString();
}
public String getFullClassName() {
return packageName + "." + className;
}
public String getClassName(){
return className;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
6、注解使用
分別創(chuàng)建出禮物模塊和聊天模塊,聊天模塊增加發(fā)消息的方法
@ModuleWrapper
public class ChatModule implements IModule{
@Override
public String getModuleName() {
return "ChatModule";
}
public void sendMessage() {
Log.i("TAG", "Hi");
}
}
@ModuleWrapper
public class GiftModule implements IModule{
@Override
public String getModuleName() {
return "GiftModule";
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
7、生成代碼
在生成代碼之前需要gradle build,查看生成代碼。當然對于模塊來說,不僅有get()、還有add()、remove()等其他擴展功能,具體的就留給大家去操作
public class ModuleFactory {
private HashMap<String, IModule> moduleMap = new HashMap<>();
public void init() {
moduleMap.put("com.hensen.geneapt.GiftModule" ,new GiftModule());
moduleMap.put("com.hensen.geneapt.ChatModule" ,new ChatModule());
}
public IModule get(String cls) {
return moduleMap.get(cls);
}
}
public class ModuleConstant {
public static final String GiftModule = "com.hensen.geneapt.GiftModule";
public static final String ChatModule = "com.hensen.geneapt.ChatModule";
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
8、組件使用
初始化組件的加載,通過工廠獲取對應的模塊進行操作
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ModuleFactory moduleFactory = new ModuleFactory();
moduleFactory.init();
ChatModule chatModule = (ChatModule) moduleFactory.get(ModuleConstant.ChatModule);
chatModule.sendMessage();