在组件化子模块间交互中说了类加载、全局Map记录实现组件化模块之间的交互。那么我们是否可以通过APT帮生成java文件,文件中记录需要记录的类,然后在我们需要的时候,通过查找获取类,然后通过类加载的方式实现模块之间的跳转呢?
思考:在组件化架构中,我们需要通过APT和JavaPoet技术生成什么样的类文件呢?
我们会使用APT生成ARouter$$Group$$模块名.java
与ARouter$$Path$$模块名.java
两个文件。
思考:
1、为什么需要组名?
在做路由加载管理类的时候,我们不需要加载所有的类文件。这个时候分组就很有必要了。2、生成这些文件干嘛用?
我们可以通过组文件记录,找到路径文件记录,通过路径获取RouterBean。
项目部署
可以参照APT的介绍与使用创建自定义注解的Module(arouter_annotation)与注解处理器的Module(arouter_compiler)
// 引入arouter_annotation注解,目的是引入RouterBean
implementation project(':arouter_annotation')
到这里就可以思考两个问题了,RouterBean类为什么要在arouter_annotation 下面创建?
RouterBean里面有Element的属性,而Element它是在javax.lang包下的,不属于Android Library,这样就导致需要在Java Library Module里面创建,而arouter_compiler、app、order、personal都需要导入arouter_annotation,所以就在arouter_annotation下创建了RouterBean。
Element类节点为什么要存储起来呢?
因为我们要在注解处理器中,循环拿到每个类节点。方便赋值与调用。
组文件与路径文件需要继承的接口
/**
* 路由组Group加载数据接口
*/
public interface ARouterLoadGroup {
/**
* 加载路由组Group数据
* 比如:"app", ARouter$$Path$$app.class(实现了ARouterLoadPath接口)
*
* @return key:"app", value:"app"分组对应的路由详细对象类
*/
Map> loadGroup();
}
public interface ARouterLoadPath {
/**
* 加载路由组Group对应的详细Path加载数据接口
* 比如:app分组对应有哪些类需要加载
*
* @return key:"/app/MainActivity", value:MainActivity信息封装到RouterBean对象中
*/
Map loadPath();
}
// 每个功能子模块既然都要生成APT源文件,而且又是所有模块依赖的公共库
// 那么公共基础库依赖路由arouter_api就能向每个子模块提供开放api了
api project(':arouter_api') // 路由对外开放api模块
// 在gradle文件中配置选项参数值(用于APT传参接收)
// 切记:必须写在defaultConfig节点下
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName(), packageNameForAPT: "com.migill.modular.apt"]
}
}
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({Constants.IROUTER_ANNOTATION_TYPES})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedOptions({Constants.MODULE_NAME, Constants.APT_PACKAGE})
public class ARouterProcessor extends AbstractProcessor {
// 操作Element工具类 (类、函数、属性都是Element)
private Elements elementUtils;
// type(类信息)工具类,包含用于操作TypeMirror的工具方法
private Types typeUtils;
// Messager用来报告错误,警告和其他提示信息
private Messager messager;
// 文件生成器 类/资源,Filter用来创建新的源文件,class文件以及辅助文件
private Filer filer;
// 子模块名,如:app/order/personal。需要拼接类名时用到(必传)ARouter$$Group$$order
private String moduleName;
//包名,用于存放APT生成的类文件
private String packageNameForAPT;
// 临时map存储,用来存放路由组Group对应的详细Path类对象,生成路由路径类文件时遍历
// key:组名"app", value:"app"组的路由路径"ARouter$$Path$$app.class"
private Map> tempPathMap = new HashMap<>();
// 临时map存储,用来存放路由Group信息,生成路由组类文件时遍历
// key:组名"app", value:类名"ARouter$$Path$$app.class"
private Map tempGroupMap = new HashMap<>();
// 该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
// 通过ProcessingEnvironment去获取对应的参数
Map options = processingEnvironment.getOptions();
if (!EmptyUtils.isEmpty(options)) {
moduleName = options.get(Constants.MODULE_NAME);
packageNameForAPT = options.get(Constants.APT_PACKAGE);
messager.printMessage(Diagnostic.Kind.NOTE, "moduleName >>> " + moduleName);
messager.printMessage(Diagnostic.Kind.NOTE, "packageNameForAPT >>> " + packageNameForAPT);
}
if (EmptyUtils.isEmpty(moduleName) || EmptyUtils.isEmpty(packageNameForAPT)) {
throw new RuntimeException("注解处理器需要的参数moduleName或者packageName为空,请在对应build.gradle配置参数");
}
}
/**
* 相当于main函数,开始处理注解
* 注解处理器的核心方法,处理具体的注解,生成Java文件
*
* @param set 使用了支持处理注解的节点集合
* @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找的注解。
* @return true 表示后续处理器不会再处理(已经处理完成)
*/
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
//一旦有类之上使用@ARouter注解
if (!EmptyUtils.isEmpty(set)) {
//获取所有被@ARouter注解的 元素集合
Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
if (!EmptyUtils.isEmpty(elements)) {
try {
parseElements(elements);
return true;
} catch (IOException e) {
e.printStackTrace();
}
}
// 坑:必须写返回值,表示处理@ARouter注解完成
return true;
}
return false;
}
// 解析所有被@ARouter注解的类元素集合
private void parseElements(Set extends Element> elements) throws IOException {
//通过Element工具类,获取Activity、Callback类型
TypeElement activityType = elementUtils.getTypeElement(Constants.ACTIVITY);
//显示类信息(获取被注解节点,类节点)这里也叫字描述 Mirror
TypeMirror activityMirror = activityType.asType();
//遍历节点
for (Element element : elements) {
//获取每个元素类信息,用于比较
TypeMirror elementMirror = element.asType();
messager.printMessage(Diagnostic.Kind.NOTE, "遍历元素信息:" + elementMirror.toString());
//获取每个类上的@ARouter注解中的注解值
ARouter aRouter = element.getAnnotation(ARouter.class);
//路由详细信息、最终实体封装类
RouterBean bean = new RouterBean.Builder()
.setGroup(aRouter.group())
.setPath(aRouter.path())
.setElement(element)
.build();
//高级判断:ARouter注解仅能使用在类之上,并且是规定的Activity
//类型工具类方法isSubtype,相当于instance一样
if (typeUtils.isSubtype(elementMirror, activityMirror)) {
bean.setType(RouterBean.Type.ACTIVITY);
} else {
// 不匹配抛出异常,这里谨慎使用!考虑维护问题
throw new RuntimeException("@ARouter注解目前仅限用于Activity类之上");
}
//赋值临时map存储,用来存放路由组Group对应的详细Path类对象
valueOfPathMap(bean);
}
//routerMap遍历后,用来生成类文件
//获取ARouterLoadGroup、ARouterLoadPath类型(生成类文件需要实现的接口)
//组接口
TypeElement groupLoadType = elementUtils.getTypeElement(Constants.AROUTE_GROUP);
messager.printMessage(Diagnostic.Kind.NOTE, "groupLoadType :" + groupLoadType.getQualifiedName().toString());
//路径接口
TypeElement pathLoadType = elementUtils.getTypeElement(Constants.AROUTE_PATH);
messager.printMessage(Diagnostic.Kind.NOTE, "pathLoadType :" + pathLoadType.getQualifiedName().toString());
// 第一步:生成路由组Group对应详细Path类文件,如:ARouter$$Path$$app
createPathFile(pathLoadType);
// 第二步:生成路由组Group类文件(没有第一步,取不到类文件),如:ARouter$$Group$$app
createGroupFile(groupLoadType, pathLoadType);
}
/**
* 生成路由组Group对应详细Path,如:ARouter$$Path$$app
*
* @param pathLoadType ARouterLoadPath接口信息
*/
private void createPathFile(TypeElement pathLoadType) throws IOException {
// 判断是否有需要生成的类文件
if (EmptyUtils.isEmpty(tempPathMap)) return;
TypeName methodReturns = ParameterizedTypeName.get(
ClassName.get(Map.class),// Map
ClassName.get(String.class),// Map
);
// 遍历分组,每一个分组创建一个路径类文件,如:ARouter$$Path$$app
for (Map.Entry> entry : tempPathMap.entrySet()) {
//方法配置:public Map loadPath() {
MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(Constants.PATH_METHOD_NAME)// 方法名
.addAnnotation(Override.class)// 重写注解
.addModifiers(Modifier.PUBLIC)//public修饰符
.returns(methodReturns);//方法返回值
// 遍历之前:Map pathMap = new HashMap<>();
methodBuidler.addStatement("$T<$T, $T> $N = new $T<>()",
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouterBean.class),
Constants.PATH_PARAMETER_NAME,
HashMap.class);
// 一个分组,如:ARouter$$Path$$app。有很多详细路径信息,如:/app/MainActivity、/app/OtherActivity
List pathList = entry.getValue();
// 方法内容配置(遍历每个分组中每个路由详细路径)
for (RouterBean bean : pathList) {
// pathMap.put("/app/MainActivity", RouterBean.create(RouterBean.Type.ACTIVITY, MainActivity.class, "/app/MainActivity", "app"));
methodBuidler.addStatement(
"$N.put($S, $T.create($T.$L, $T.class, $S, $S))",
Constants.PATH_PARAMETER_NAME, // pathMap.put
bean.getPath(), // "/app/MainActivity"
ClassName.get(RouterBean.class), // RouterBean
ClassName.get(RouterBean.Type.class), // RouterBean.Type
bean.getType(), // 枚举类型:ACTIVITY
ClassName.get((TypeElement) bean.getElement()), // MainActivity.class
bean.getPath(), // 路径名
bean.getGroup() // 组名
);
}
// 遍历之后:return pathMap;
methodBuidler.addStatement("return $N", Constants.PATH_PARAMETER_NAME);
// 最终生成的类文件名
String finalClassName = Constants.PATH_FILE_NAME + entry.getKey();
messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由Path类文件:" +
packageNameForAPT + "." + finalClassName);
// 生成类文件:public class ARouter$$Path$$app implements ARouterLoadPath {
JavaFile.builder(packageNameForAPT,//包名
TypeSpec.classBuilder(finalClassName)//类名
.addSuperinterface(ClassName.get(pathLoadType))
.addModifiers(Modifier.PUBLIC)
.addMethod(methodBuidler.build())
.build())//类构建完成
.build()
.writeTo(filer);
//非常重要的一步!!!!!路径文件生成出来了,才能赋值路由组tempGroupMap
tempGroupMap.put(entry.getKey(), finalClassName);
}
}
/**
* 生成路由组Group文件,如:ARouter$$Group$$app
*
* @param groupLoadType ARouterLoadGroup接口信息
* @param pathLoadType ARouterLoadPath接口信息
*/
private void createGroupFile(TypeElement groupLoadType, TypeElement pathLoadType) throws IOException {
// 判断是否有需要生成的类文件
if (EmptyUtils.isEmpty(tempGroupMap) || EmptyUtils.isEmpty(tempPathMap)) return;
TypeName methodReturns = ParameterizedTypeName.get(
ClassName.get(Map.class), // Map
ClassName.get(String.class), // Map
// 某某Class是否属于ARouterLoadPath接口的实现类
ParameterizedTypeName.get(ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(pathLoadType)))
);
// 方法配置:public Map> loadGroup() {
MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(Constants.GROUP_METHOD_NAME) // 方法名
.addAnnotation(Override.class) // 重写注解
.addModifiers(Modifier.PUBLIC) // public修饰符
.returns(methodReturns); // 方法返回值
// 遍历之前:Map> groupMap = new HashMap<>();
methodBuidler.addStatement("$T<$T, $T> $N = new $T<>()",
ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(pathLoadType))),
Constants.GROUP_PARAMETER_NAME,
HashMap.class);
//方法内容配置
for (Map.Entry entry : tempGroupMap.entrySet()) {
// groupMap.put("main", ARouter$$Path$$app.class);
methodBuidler.addStatement("$N.put($S, $T.class)",
Constants.GROUP_PARAMETER_NAME, // groupMap.put
entry.getKey(),
// 类文件在指定包名下
ClassName.get(packageNameForAPT, entry.getValue()));
}
// 遍历之后:return groupMap;
methodBuidler.addStatement("return $N", Constants.GROUP_PARAMETER_NAME);
// 最终生成的类文件名
String finalClassName = Constants.GROUP_FILE_NAME + moduleName;
messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由组Group类文件:" +
packageNameForAPT + "." + finalClassName);
// 生成类文件:ARouter$$Group$$app
JavaFile.builder(packageNameForAPT, // 包名
TypeSpec.classBuilder(finalClassName) // 类名
.addSuperinterface(ClassName.get(groupLoadType)) // 实现ARouterLoadGroup接口
.addModifiers(Modifier.PUBLIC) // public修饰符
.addMethod(methodBuidler.build()) // 方法的构建(方法参数 + 方法体)
.build()) // 类构建完成
.build() // JavaFile构建完成
.writeTo(filer); // 文件生成器开始生成类文件
}
/**
* 赋值临时map存储,用来存放路由组Group对应的详细Path类对象,生成路由路径类文件时遍历
*
* @param bean 路由详细信息,最终实体封装类
*/
private void valueOfPathMap(RouterBean bean) {
messager.printMessage(Diagnostic.Kind.NOTE, "RouterBean >>> " + bean.toString());
if (checkRouterPath(bean)) {
messager.printMessage(Diagnostic.Kind.NOTE, "RouterBean >>> " + bean.toString());
//开始赋值Map
List routerBeans = tempPathMap.get(bean.getGroup());
// 如果从Map中找不到key为:bean.getGroup()的数据,就新建List集合再添加进Map
if (EmptyUtils.isEmpty(routerBeans)) {
routerBeans = new ArrayList<>();
routerBeans.add(bean);
tempPathMap.put(bean.getGroup(), routerBeans);
} else {// 找到了key,直接加入List集合
routerBeans.add(bean);
}
} else {
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范配置,如:/app/MainActivity");
}
}
private boolean checkRouterPath(RouterBean bean) {
String group = bean.getGroup();
String path = bean.getPath();
// @ARouter注解中的path值,必须要以 / 开头(模仿阿里Arouter规范)
if (EmptyUtils.isEmpty(path) || !path.startsWith("/")) {
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的path值,必须要以 / 开头");
return false;
}
// 比如开发者代码为:path = "/MainActivity",最后一个 / 符号必然在字符串第1位
if (path.lastIndexOf("/") == 0) {
// 架构师定义规范,让开发者遵循
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范配置,如:/app/MainActivity");
return false;
}
// 从第一个 / 到第二个 / 中间截取,如:/app/MainActivity 截取出 app 作为group
String finalGroup = path.substring(1, path.indexOf("/", 1));
// @ARouter注解中的group有赋值情况
if (!EmptyUtils.isEmpty(group) && !group.equals(moduleName)) {
// 架构师定义规范,让开发者遵循
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的group值必须和子模块名一致!");
return false;
} else {
bean.setGroup(finalGroup);
}
return true;
}
}
/**
* 路由加载管理器
*/
public final class RouterManager {
// 路由组名
private String group;
// 路由详细路径
private String path;
private static RouterManager instance;
// Lru缓存,key:类名, value:路由组Group加载接口
private LruCache groupCache;
// Lru缓存,key:类名, value:路由组Group对应的详细Path加载接口
private LruCache pathCache;
// APT生成的路由组Group源文件前缀名
private static final String GROUP_FILE_PREFIX_NAME = ".ARouter$$Group$$";
// 单例方式,全局唯一
public static RouterManager getInstance() {
if (instance == null) {
synchronized (RouterManager.class) {
if (instance == null) {
instance = new RouterManager();
}
}
}
return instance;
}
private RouterManager() {
// 初始化,并赋值缓存中条目的最大值
groupCache = new LruCache<>(200);
// 每组最多163条路径值
pathCache = new LruCache<>(200);
}
public BundleManager build(String path) {
// @ARouter注解中的path值,必须要以 / 开头(模仿阿里Arouter规范)
if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
throw new IllegalArgumentException("未按规范配置,如:/app/MainActivity");
}
group = subFromPath2Group(path);
// 检查后再赋值
this.path = path;
return new BundleManager();
}
private String subFromPath2Group(String path) {
// 比如开发者代码为:path = "/MainActivity",最后一个 / 符号必然在字符串第1位
if (path.lastIndexOf("/") == 0) {
// 架构师定义规范,让开发者遵循
throw new IllegalArgumentException("@ARouter注解未按规范配置,如:/app/MainActivity");
}
// 从第一个 / 到第二个 / 中间截取,如:/app/MainActivity 截取出 app 作为group
String finalGroup = path.substring(1, path.indexOf("/", 1));
if (TextUtils.isEmpty(finalGroup)) {
// 架构师定义规范,让开发者遵循
throw new IllegalArgumentException("@ARouter注解未按规范配置,如:/app/MainActivity");
}
// 最终组名:app
return finalGroup;
}
/**
* 开始跳转
*
* @param context 上下文
* @param bundleManager Bundle拼接参数管理类
* @param code 这里的code,可能是requestCode,也可能是resultCode。取决于isResult
* @return 普通跳转可以忽略,用于跨模块CALL接口
*/
Object navigation(@NonNull Context context, BundleManager bundleManager, int code) {
String groupClassName = context.getPackageName() + ".apt" + GROUP_FILE_PREFIX_NAME + group;
try {
ARouterLoadGroup groupLoad = groupCache.get(group);
if (groupLoad == null) {
Class> clazz = Class.forName(groupClassName);
groupLoad = (ARouterLoadGroup) clazz.newInstance();
groupCache.put(group, groupLoad);
}
// 获取路由路径类ARouter$$Path$$app
if (groupLoad.loadGroup().isEmpty()) {
throw new RuntimeException("路由加载失败");
}
ARouterLoadPath pathLoad = pathCache.get(path);
if (pathLoad == null) {
Class extends ARouterLoadPath> clazz = groupLoad.loadGroup().get(group);
if (clazz != null) pathLoad = clazz.newInstance();
if (pathLoad != null) pathCache.put(path, pathLoad);
}
if (pathLoad != null) {
if (pathLoad.loadPath().isEmpty()) {
throw new RuntimeException("路由路径加载失败");
}
RouterBean routerBean = pathLoad.loadPath().get(path);
if (routerBean != null) {
switch (routerBean.getType()) {
case ACTIVITY:
Intent intent = new Intent(context, routerBean.getClazz());
intent.putExtras(bundleManager.getBundle());
// startActivityForResult -> setResult
if (bundleManager.isResult()) {
((Activity) context).setResult(code, intent);
((Activity) context).finish();
}
if (code > 0) { // 跳转时是否回调
((Activity) context).startActivityForResult(intent, code, bundleManager.getBundle());
} else {
context.startActivity(intent, bundleManager.getBundle());
}
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
/**
* Bundle拼接参数管理类
*/
public final class BundleManager {
private Bundle bundle = new Bundle();
// 是否回调setResult()
private boolean isResult;
Bundle getBundle() {
return bundle;
}
boolean isResult() {
return isResult;
}
// @NonNull不允许传null,@Nullable可以传null
public BundleManager withString(@NonNull String key, @Nullable String value) {
bundle.putString(key, value);
return this;
}
// 示例代码,需要拓展
public BundleManager withResultString(@NonNull String key, @Nullable String value) {
bundle.putString(key, value);
isResult = true;
return this;
}
public BundleManager withBoolean(@NonNull String key, boolean value) {
bundle.putBoolean(key, value);
return this;
}
public BundleManager withInt(@NonNull String key, int value) {
bundle.putInt(key, value);
return this;
}
public BundleManager withBundle(@NonNull Bundle bundle) {
this.bundle = bundle;
return this;
}
public Object navigation(Context context) {
return RouterManager.getInstance().navigation(context, this, -1);
}
// 这里的code,可能是requestCode,也可能是resultCode。取决于isResult
public Object navigation(Context context, int code) {
return RouterManager.getInstance().navigation(context, this, code);
}
}
public void jumpApp(View view) {
RouterManager.getInstance()
.build("/app/MainActivity")
.withString("username", "migill")
.navigation(this );
}
public void jumpOrder(View view) {
RouterManager.getInstance()
.build("/order/Order_MainActivity")
.withString("username", "migill")
.navigation(this);
}