前言
APT的學習要花點時間去掌握和實踐的,短時間內只能掌握知識點,更多的是在實戰中去實踐。其實,APT就是一種工具而已,只要用多了,自然就會熟練了,不過要想實踐之前,還是必須把基礎知識學好才能實戰進入開發。文章會從基礎用例講解知識點,然後再通過實戰進行實踐
APT簡介
APT(Annotation Processing Tool)是一種處理註解的工具,它會對原始碼中的註解進行額外的處理,比如在編譯時生成一些重複性操作的Java程式碼,或者不需要程式設計師去關心的Java程式碼等。 在使用APT的過程中會涉及到下面兩個第三方庫的使用
- AutoService:這個庫的主要作用是註冊註解,並對其生成META-INF的配置資訊
- JavaPoet:這個庫的主要作用是幫助我們通過類呼叫的形式來生成Java程式碼
APT主要過程包括初始化過程和註解處理過程
- 初始化過程:獲取APT提供的工具類,為後面的註解處理提供幫助
- 註解處理過程:獲取註解的元素,對元素進行額外處理,可用JavaPoet生成Java程式碼
APT流程
1、定義註解
該註解是可以在我們的專案中使用到的,且規定為註解元素的型別為Type,和在編譯時生效
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ModuleWrapper {
}
複製程式碼
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; // 額外配置引數
@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");
}
}
}
}
複製程式碼
3、AbstractProcessor實現方法介紹
- init():初始化過程,可通過初始化方法獲取各種工具類
- process():註解處理過程,可通過獲取註解元素後,對註解元素進行額外處理
- getSupportedAnnotationTypes():獲取需要解析的註解型別
APT知識點
1、初始化介紹
APT初始化階段為init方法的呼叫,我們可以使用ProcessingEnvironment獲取一些實用類以及獲取選項引數等
2、Element介紹
Element是操作元素最主要的類,可通過getElementsAnnotatedWith獲取Element的元素,經過getKind()判斷元素的型別後,可強制轉換成對應的型別,在對應的型別中有著不同的方法可以呼叫
ElementKind為元素的型別,元素的型別判斷不需要用instanceof去判斷,而應該通過**getKind()**去判斷對應的型別3、TypeMirror介紹
TypeMirror是一個介面,表示Java程式語言中的型別。這些型別包括基本型別、引用型別、陣列型別、型別變數和null型別等等
TypeKind為型別的屬性,型別的屬性判斷不需要用instanceof去判斷,而應該通過**getKind()**去判斷對應的屬性這裡需要注意的是,如果我們通過註解去獲取Class型別的值,如果獲取的Class未被編譯,則會丟擲MirroredTypeException異常,此時我們需要通過try-catch語句在catch裡去獲取我們所需要的類元素
try {
annotation.value();//如果value為Class型別則會報異常
} catch (MirroredTypeException mte) {
DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();
TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();//通過異常去獲取類元素
}
複製程式碼
4、Filer介紹
Filer介面支援通過註解處理器建立新檔案。可以建立三種檔案型別:原始檔、類檔案和輔助資原始檔
5、Messager介紹
Messager介面提供註解處理器用來報告錯誤訊息、警告和其他通知的方式
6、Options介紹
通過getOptions()方法獲取選項引數,在gradle檔案中配置選項引數值
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [ version : '1.0.0' ]
}
}
}
}
複製程式碼
通過ProcessingEnvironment去獲取對應的引數
processingEnvironment.getOptions().get("version");
複製程式碼
7、獲取註解元素
通過RoundEnvironment介面去獲取註解元素,通過JavaPoet生成Java程式碼
APT實戰
下面通過APT的實戰,進行對專案的模組化劃分
1、專案結構
- 建立Module,名為annotation,放置我們的註解類
- 建立Module,名為compiler,放置我們的註解處理類
- 主工程則直接依賴annotation和compiler 注意事項:建立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"
複製程式碼
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"
複製程式碼
主工程必須通過依賴annotaion和compiler,由於我們只是在編譯期生效,可用annotationProcessor
implementation project(':annotation')
annotationProcessor project(':compiler')
複製程式碼
注意事項:定義編譯的jdk版本為1.7
3、定義註解
ModuleWrapper註解,表示需要載入的模組
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ModuleWrapper {
}
複製程式碼
IModule介面,表示當前類是一個模組類
public interface IModule {
String getModuleName();
}
複製程式碼
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");
}
}
}
/**
* 建立元件管理者
*
* @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);
}
/**
* 建立元件常量
*
* @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);
}
}
複製程式碼
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;
}
}
複製程式碼
6、註解使用
分別建立出禮物模組和聊天模組,聊天模組增加發訊息的方法
@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";
}
}
複製程式碼
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";
}
複製程式碼
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();
}
}
複製程式碼