前言
APT的學(xué)習(xí)要花點(diǎn)時(shí)間去掌握和實(shí)踐的,短時(shí)間內(nèi)只能掌握知識(shí)點(diǎn),更多的是在實(shí)戰(zhàn)中去實(shí)踐。其實(shí),APT就是一種工具而已,只要用多了,自然就會(huì)熟練了,不過要想實(shí)踐之前,還是必須把基礎(chǔ)知識(shí)學(xué)好才能實(shí)戰(zhàn)進(jìn)入開發(fā)。文章會(huì)從基礎(chǔ)用例講解知識(shí)點(diǎn),然后再通過實(shí)戰(zhàn)進(jìn)行實(shí)踐
APT簡(jiǎn)介
APT(Annotation Processing Tool)是一種處理注解的工具,它會(huì)對(duì)源代碼中的注解進(jìn)行額外的處理,比如在編譯時(shí)生成一些重復(fù)性操作的Java代碼,或者不需要程序員去關(guān)心的Java代碼等。在使用APT的過程中會(huì)涉及到下面兩個(gè)第三方庫的使用
- AutoService:這個(gè)庫的主要作用是注冊(cè)注解,并對(duì)其生成META-INF的配置信息
- JavaPoet:這個(gè)庫的主要作用是幫助我們通過類調(diào)用的形式來生成Java代碼
APT主要過程包括初始化過程和注解處理過程
- 初始化過程:獲取APT提供的工具類,為后面的注解處理提供幫助
- 注解處理過程:獲取注解的元素,對(duì)元素進(jìn)行額外處理,可用JavaPoet生成Java代碼
APT流程
1、定義注解
該注解是可以在我們的項(xiàng)目中使用到的,且規(guī)定為注解元素的類型為Type,和在編譯時(shí)生效
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ModuleWrapper {
}- 1
- 2
- 3
- 4
- 5
2、定義Processor
Processor會(huì)在編譯期對(duì)注解進(jìn)行解析,取出對(duì)應(yīng)的元素進(jìn)行處理。至于AutoService則是固定的寫法,加個(gè)注解即可
@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) {
//獲取對(duì)應(yīng)的注解元素
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(ModuleWrapper.class);
for (Element element : set) {
//如果是個(gè)類
if (element.getKind() == ElementKind.CLASS) {
//獲取類名
String clzName = element.getSimpleName().toString();
//對(duì)元素進(jìn)行處理,可用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實(shí)現(xiàn)方法介紹
- init():初始化過程,可通過初始化方法獲取各種工具類
- process():注解處理過程,可通過獲取注解元素后,對(duì)注解元素進(jìn)行額外處理
- getSupportedAnnotationTypes():獲取需要解析的注解類型
APT知識(shí)點(diǎn)
1、初始化介紹
APT初始化階段為init方法的調(diào)用,我們可以使用ProcessingEnvironment獲取一些實(shí)用類以及獲取選項(xiàng)參數(shù)等
| 方法 | 說明 |
|---|---|
| getElementUtils() | 返回實(shí)現(xiàn)Elements接口的對(duì)象,用于操作元素的工具類 |
| getFiler() | 返回實(shí)現(xiàn)Filer接口的對(duì)象,用于創(chuàng)建文件、類和輔助文件 |
| getMessager() | 返回實(shí)現(xiàn)Messager接口的對(duì)象,用于報(bào)告錯(cuò)誤信息、警告提醒 |
| getOptions() | 返回指定的參數(shù)選項(xiàng) |
| getTypeUtils() | 返回實(shí)現(xiàn)Types接口的對(duì)象,用于操作類型的工具類 |
2、Element介紹
Element是操作元素最主要的類,可通過getElementsAnnotatedWith獲取Element的元素,經(jīng)過getKind()判斷元素的類型后,可強(qiáng)制轉(zhuǎn)換成對(duì)應(yīng)的類型,在對(duì)應(yīng)的類型中有著不同的方法可以調(diào)用
| 類型 | 說明 |
|---|---|
| ExecutableElement | 表示類、接口的方法元素。包括構(gòu)造方法、注解類型 |
| PackageElement | 表示包元素。提供對(duì)有關(guān)包及其成員的信息的訪問 |
| TypeElement | 表示類、接口元素。提供對(duì)有關(guān)類型及其成員的信息的訪問 |
| TypeParameterElement | 表示類、接口、方法、構(gòu)造方法的參數(shù)元素 |
| VariableElement | 表示字段、enum、方法、構(gòu)造方法參數(shù)、局部變量、異常參數(shù) |
ElementKind為元素的類型,元素的類型判斷不需要用instanceof去判斷,而應(yīng)該通過getKind()去判斷對(duì)應(yīng)的類型
| 類型 | 說明 |
|---|---|
| PACKAGE | 包 |
| ENUM | 枚舉 |
| CLASS | 類 |
| ANNOTATION_TYPE | 注解 |
| INTERFACE | 接口 |
| ENUM_CONSTANT | 枚舉常量 |
| FIELD | 字段 |
| PARAMETER | 方法參數(shù) |
| LOCAL_VARIABLE | 局部變量 |
| METHOD | 方法 |
| CONSTRUCTOR | 構(gòu)造方法 |
| TYPE_PARAMETER | 類型參數(shù) |
3、TypeMirror介紹
TypeMirror是一個(gè)接口,表示Java編程語言中的類型。這些類型包括基本類型、引用類型、數(shù)組類型、類型變量和null類型等等
| 類型 | 說明 |
|---|---|
| ArrayType | 表示數(shù)組類型 |
| DeclaredType | 表示聲明類型(類或接口類型) |
| ErrorType | 表示異常類或接口類型 |
| ExecutableType | 表示executable類型(方法、構(gòu)造方法、初始化) |
| NoType | 表示在實(shí)際類型不適合的地方使用的偽類型 |
| NullType | 表示null類型 |
| PrimitiveType | 表示基本數(shù)據(jù)類型 |
| ReferenceType | 表示引用類型 |
| TypeVariable | 表示類型變量 |
| WildcardType | 表示通配符類型參數(shù) |
TypeKind為類型的屬性,類型的屬性判斷不需要用instanceof去判斷,而應(yīng)該通過getKind()去判斷對(duì)應(yīng)的屬性
| 類型 | 說明 |
|---|---|
| BOOLEAN | 基本類型boolean |
| INT | 基本類型int |
| LONG | 基本類型long |
| FLOAT | 基本類型float |
| DOUBLE | 基本類型double |
| VOID | 對(duì)應(yīng)于關(guān)鍵字void的偽類型 |
| NULL | null類型 |
| ARRAY | 數(shù)組類型 |
| PACKAGE | 對(duì)應(yīng)于包元素的偽類型 |
| EXECUTABLE | 方法、構(gòu)造方法、初始化 |
這里需要注意的是,如果我們通過注解去獲取Class類型的值,如果獲取的Class未被編譯,則會(huì)拋出MirroredTypeException異常,此時(shí)我們需要通過try-catch語句在catch里去獲取我們所需要的類元素
try {
annotation.value();//如果value為Class類型則會(huì)報(bào)異常
} 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接口提供注解處理器用來報(bào)告錯(cuò)誤消息、警告和其他通知的方式
| 方法 | 說明 |
|---|---|
| printMessage | 打印錯(cuò)誤消息 |
6、Options介紹
通過getOptions()方法獲取選項(xiàng)參數(shù),在gradle文件中配置選項(xiàng)參數(shù)值
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [ version : '1.0.0' ]
}
}
}
}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
通過ProcessingEnvironment去獲取對(duì)應(yīng)的參數(shù)
processingEnvironment.getOptions().get("version");- 1
7、獲取注解元素
通過RoundEnvironment接口去獲取注解元素,通過JavaPoet生成Java代碼
| 方法 | 說明 |
|---|---|
| getElementsAnnotatedWith | 返回注解元素的集合 |
APT實(shí)戰(zhàn)
下面通過APT的實(shí)戰(zhàn),進(jìn)行對(duì)項(xiàng)目的模塊化劃分
1、項(xiàng)目結(jié)構(gòu)
- 創(chuàng)建Module,名為annotation,放置我們的注解類
- 創(chuàng)建Module,名為compiler,放置我們的注解處理類
- 主工程則直接依賴annotation和compiler
注意事項(xiàng):創(chuàng)建Module的時(shí)候,需要選擇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和導(dǎo)入我們所需的庫
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
注意事項(xiàng):定義編譯的jdk版本為1.7
3、定義注解
ModuleWrapper注解,表示需要加載的模塊
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ModuleWrapper {
}- 1
- 2
- 3
- 4
- 5
IModule接口,表示當(dāng)前類是一個(gè)模塊類
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;
}
/**
* 通過注解元素獲取組件實(shí)體
*
* @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、組件實(shí)體
組件實(shí)體保存著組件的信息
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,查看生成代碼。當(dāng)然對(duì)于模塊來說,不僅有g(shù)et()、還有add()、remove()等其他擴(kuò)展功能,具體的就留給大家去操作
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、組件使用
初始化組件的加載,通過工廠獲取對(duì)應(yīng)的模塊進(jìn)行操作
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();


