讀完本篇能夠了解的內(nèi)容
1、注解的一些基本使用;
2、gradle 5.4.1版本中如何正確的導(dǎo)入com.google.auto.service:auto-service:1.0-rc7
類(lèi)庫(kù);
3、利用javapoet編寫(xiě)java文件;
4、如何在編譯期生成代碼;
5、利用反射執(zhí)行編譯時(shí)生成的java類(lèi)文件。
起因
項(xiàng)目中看到中臺(tái)編寫(xiě)的router路由框架可以利用注解注釋后的值進(jìn)行跳轉(zhuǎn),于是產(chǎn)生了興趣,探究了里面實(shí)現(xiàn)的基本原理。
具體表現(xiàn)如下代碼:
// 注解注釋某個(gè)activity
@Route(path = "/module_mall/my_points_activity")
public class MyPointsActivity
// 跳轉(zhuǎn)
ARouter.getInstance().build("/module_mall/my_points_activity")
.navigation();
主要實(shí)現(xiàn)過(guò)程如下圖:

實(shí)現(xiàn)過(guò)程
自定義Annotation注解
新建一個(gè)java library模塊,用來(lái)編寫(xiě)注解相關(guān)的代碼@Target(ElementType.TYPE)表示該注解只能作用于類(lèi),@Retention(RetentionPolicy.CLASS)表示該注解作用的生命周期為編譯期間。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Router {
String value();
}
自定義AbstractProcessor的子類(lèi)
接著,新建另一個(gè)java library模塊,用來(lái)編寫(xiě)AbstractProcessor的子類(lèi)。該模塊的gradle文件定義如下:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7' // 需要引入auto-service中的注解,這里要這樣寫(xiě)。要不然不能正確生成
compileOnly 'com.google.auto.service:auto-service:1.0-rc7'
implementation 'com.squareup:javapoet:1.13.0' // 方便生成java類(lèi)
implementation project(':my_annotation') // 這是自定義的模塊
implementation project(path: ':apt') // 這是自定義的模塊
}
sourceCompatibility = "7"
targetCompatibility = "7"
注意點(diǎn):要用annotationProcessor來(lái)引入com.google.auto.service:auto-service:1.0-rc7,這樣才能保證正確生成代碼,這里對(duì)應(yīng)第二個(gè)問(wèn)題。
getSupportedAnnotationTypes方法返回一個(gè)set集合,用來(lái)保存編譯器支持掃描的注解類(lèi)型。
@Override
public Set<String> getSupportedAnnotationTypes() {
LinkedHashSet<String> types = new LinkedHashSet<>();
types.add(Router.class.getCanonicalName());
return types;
}
process方法,在編譯期間會(huì)被調(diào)用,此時(shí)我們可以利用javapoet來(lái)生成相應(yīng)的java代碼。具體的解釋在代碼中寫(xiě)的已經(jīng)很清楚,可以對(duì)照著看。
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 得到所有被router注解的類(lèi)
Set<? extends Element> annotatedElement = roundEnv.getElementsAnnotatedWith(Router.class);
/**
* public void handle(Map<String, Class<?>> routeMap) {
* }
*
* 構(gòu)建map類(lèi)型的參數(shù),Map<String, Class<?>>類(lèi)型
*/
ParameterizedTypeName typeName = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(
ClassName.get(Class.class),
WildcardTypeName.subtypeOf(Object.class)
)
);
// 方法名 handle
MethodSpec.Builder methodSpec = MethodSpec.methodBuilder("handle")
.addAnnotation(Override.class) // 給方法添加override注解
.addModifiers(Modifier.PUBLIC) // public類(lèi)型的方法
.returns(void.class) // 返回值為void
.addParameter(ParameterSpec.builder(typeName, "routeMap").build()); // 方法參數(shù)名 routeMap
for (Element element : annotatedElement) {
// 1、ElementKind.CLASS表示注解注解的類(lèi)型是class
// 2、ElementKind.FIELD表示注解的類(lèi)型是類(lèi)的屬性
if (element.getKind() == ElementKind.CLASS) {
TypeElement typeElement = (TypeElement) element;
// 獲取注解類(lèi)的包名信息
PackageElement packageElement = (PackageElement) element.getEnclosingElement();
// 通過(guò)類(lèi)的全名獲取注解類(lèi)的classname對(duì)象
ClassName className = ClassName.get(packageElement.getQualifiedName().toString(), typeElement.getSimpleName().toString());
// 獲取該注解
Router annotation = element.getAnnotation(Router.class);
// 1、annotation.value()注解的值
// 2、$N.put($S,$T.class) 這里有三個(gè)占位符$N、$S、$T
// 3、$N 可代表方法,變量等等;$S表示字符串;$T表示類(lèi),并且會(huì)自動(dòng)import該類(lèi)
methodSpec.addStatement("$N.put($S,$T.class)", "routeMap", annotation.value(), className);
}
}
ClassName interfaceName = ClassName.get(RouteTable.class);
// 動(dòng)態(tài)生成類(lèi),類(lèi)名目前是寫(xiě)死的,其實(shí)可以利用包名生成特定的Java類(lèi)
TypeSpec helloWorld = TypeSpec.classBuilder("PerryRouteTable")
.addModifiers(Modifier.PUBLIC) // public類(lèi)型的類(lèi)
.addSuperinterface(interfaceName) // 實(shí)現(xiàn)的接口
.addMethod(methodSpec.build()) // 實(shí)現(xiàn)的方法
.build();
// 動(dòng)態(tài)生成文件
JavaFile javaFile = JavaFile.builder(AptManager.PACKAGE_NAME, helloWorld)
.build();
try {
//來(lái)自javapoet 動(dòng)態(tài)生成方法
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
此時(shí),build一下我們的代碼,就會(huì)在如下路徑中

自動(dòng)生成如下代碼內(nèi)容:
public class PerryRouteTable implements RouteTable {
@Override
public void handle(Map<String, Class<?>> routeMap) {
routeMap.put("third_activity",ThirdActivity.class);
}
}
利用反射調(diào)用build中的代碼
把字符串與類(lèi)之間的關(guān)系寫(xiě)入到map集合中去。
public class AptManager {
public static final String PACKAGE_NAME = "com.perry.router";
private static Map<String, Class<?>> sRouteTable = new HashMap<>();
public static void registerModule(String moduleName) {
String routeTableName = PACKAGE_NAME + "." + moduleName;
try {
Class<?> clazz = Class.forName(routeTableName);
Constructor constructor = clazz.getConstructor();
RouteTable instance = (RouteTable) constructor.newInstance();
instance.handle(sRouteTable);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Class<?> getClazzFromKey(String routerKey) {
if (sRouteTable.isEmpty()) return null;
return sRouteTable.get(routerKey);
}
}
在我們的主模塊中利用反射調(diào)用handle方法,我這邊為了簡(jiǎn)單起見(jiàn),就默認(rèn)只生成了一個(gè)java文件,按理說(shuō),不同的模塊就會(huì)生成一份不同的java文件,然后循環(huán)遍歷并且利用反射調(diào)用handle方法就可以了。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AptManager.registerModule("PerryRouteTable");
}
}
以上就是整個(gè)實(shí)現(xiàn)過(guò)程,如有問(wèn)題,歡迎討論。