本篇文章还是从理解 ARouter 的架构思想和实现细节的角度出发,仿照 ARouter 实现一个简单的路由框架。如果对 ARouter 框架和组件化概念不是很熟悉,可以先看前置文章:Android 组件化基础(一)—— 概述与基本配置。
由于能力有限,就仅实现三个基本功能:
拦截器等功能暂不打算实现。
我们计划用三个模块来完成路由框架:
画一个丑丑的示意图帮助理解:
上图通过标记依赖关系的方式梳理了项目结构,依赖就是指通过 implementation 方法依赖其它模块,api 依赖是指用 api 方法依赖其它模块,而 annotationProcessor 就是指定要使用的注解处理器模块。
关于业务模块内部的依赖关系,组件化模式中,app 不依赖 order、personal 等其它功能子模块,但是在集成化模式中,app 是需要依赖它们的,上图只是画出了组件化模式的情形。
在路由框架内部,arouter-compiler 和 arouter-api 都会用到 arouter-annotation 中定义的注解,因此都要依赖后者。而 app、order、personal、common 都会用到 arouter-annotation 中的注解,所以 arouter-api 需要通过 api 依赖的方式引入 arouter-annotation 模块。
三个业务模块需要通过注解处理器扫描被 @Route、@AutoWired 注解的程序元素,所以每个模块都要用 annotationProcessor 引入 arouter-compiler。
路由表可以用一个 Map 来表示,key 是页面的 @Route 注解中指定的路径,而 value 则是该路径对应的 class 对象(或者是封装了该 class 对象的 JavaBean)。
此外,如果所有被 @Route 注解的页面全部都添加进一个 Map 中,当 Map 中的元素数量较多时,加载进内存可能占用较大的空间。考虑到这一点,将一个 Map 按照模块划分成多个并采用懒加载策略。比如 app 模块下的所有页面添加到一个 Map,而 order 模块下的所有页面添加到另一个 Map,用到哪一个模块就加载哪一个 Map,不用就不加载,一定程度上能节省内存开销。
按照以上思路设计出的路由架构图如下:
通过组文件的 Map 可以得到组名对应的 Path 路径文件的 Class 对象,通过反射可以生成 Path 路径文件,其内部 Map 记录着 <路径名,该路径对应的 Class 对象> 这个键值对,这个 class 就是我们的跳转目标,拿到它后再做进一步处理即可完成跳转,当然实际编码时 value 位置存的是一个封装了该 class 的 JavaBean。
实现路由架构的过程中,我们需要用到 APT 和 JavaPoet 两项技术,简单介绍下。
APT(Annotation Processing Tool) 是一种处理注解的工具,它对源代码文件进行检测并找出指定的注解,根据注解自动生成代码。它实际上是 javac 的一个工具:
如果想自定义的注解处理器能够正常运行,必须要通过 APT 来进行处理。也可以这样理解,只有通过声明 APT 后,程序在编译期间自定义的注解处理器才能执行。
通俗理解就是 APT 会根据规则在编译期帮我们生成代码、生成类文件。自定义 APT 需要实现 javax.annotation.processing 包下的 Processing 接口。不过实现该接口必须实现它里面所有的方法,因此通常会采用继承 AbstractProcessor 的方式来实现 APT。Google 推出的 AutoService 就是这样的一个 APT,可以帮助我们自定义一个注解处理器。它的使用方法也比较简单,首先引入依赖:
dependencies {
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
}
然后新建一个处理注解的类,需要继承 AbstractProcessor:
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 支持的注解类型,参数传注解全类名的字符串即可
@SupportedAnnotationTypes(Constants.AROUTER_ANNOTATIONS_TYPES)
// 指定JDK编译版本,这里是 1.7
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 注解处理器接收的参数,这里是模块名和自动生成文件所在的包名
@SupportedOptions({Constants.MODULE_NAME, Constants.APT_PACKAGE})
public class RouteProcessor extends AbstractProcessor {
/**
* 进行初始化、接收参数等操作。
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 通过 ProcessingEnvironment 去获取 @SupportedOptions 中支持的参数
Map<String, String> options = processingEnv.getOptions();
if (!EmptyUtils.isEmpty(options)) {
String moduleName = options.get(Constants.MODULE_NAME);
String packageNameForAPT = options.get(Constants.APT_PACKAGE);
}
}
/**
* 相当于main函数,开始处理注解
* 注解处理器的核心方法,处理具体的注解,生成Java文件
*
* @param annotations 使用了支持处理的注解集合,比如说本文件支持处理 @Route、@AutoWired
* 这两个注解,但是使用本处理器的模块只用了 @Route 这一个注解,那么
* annotations 中就只有 Route。
* @param roundEnv 当前或是之前的运行环境,可以通过该对象查找的注解。
* @return true 表示后续处理器不会再处理(已经处理完成的意思)
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
// 以下三个方法已经用类上的注解代替,所以注释掉了
// 获取支持的注解类型,可被 @SupportedAnnotationTypes 代替
/*@Override
public Set getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}*/
// 通过哪个 jdk 的版本进行编译生成 class,必填项,可被 @SupportedSourceVersion 代替
/*@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}*/
// 接收外面传进来的参数,可被 @SupportedOptions 代替
/*@Override
public Set getSupportedOptions() {
return super.getSupportedOptions();
}*/
}
AutoService 会帮助我们创建 build/classes/java/main/META-INF/services/javax.annotation.processing.Processor 文件,并将自定义的注解处理器注册到该文件中:
以上是注解处理器模块的配置与基本使用,那么一个像 app 这样的功能模块,如果想让注解处理器工作,需要在依赖中添加如下代码:
dependencies {
...
// 声明要使用的注解处理器,这样 arouter-compiler 才会扫描当前模块
annotationProcessor project(':arouter-compiler')
}
此外如果 app 模块需要给注解处理器传递参数,那么它需要:
android {
...
defaultConfig {
...
// 传递参数给注解处理器的配置
javaCompileOptions {
annotationProcessorOptions {
// 参数 moduleName 取当前模块的名字,packageNameForAPT 是指定自动生成的 Java 代码的包名
arguments = [moduleName: project.getName(), packageNameForAPT: packageNameForAPTString]
}
}
}
}
AutoService 使得注解处理器可以去扫描模块并获得它们的注解信息,但“生成代码”的工作其实是由 Square 公司的 JavaPoet 框架完成的。该框架十分实用,是我们习惯的 Java 面向对象的 OOP 语法,可以很方便的通过它根据注解生成对应的代码,从而以简洁优雅的方式替代繁琐冗杂的重复工作。
JavaPoet 中的常用类和符号如下:
JavaPoet 是倒序生成代码,是从较小的程序单元先开始生成,即以方法 -> 类 -> 包的顺序生成代码,比如说要生成如下的代码:
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
利用 JavaPoet 的生成方式就是:
// 生成方法
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
// 生成类
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
// 添加包名,生成 JavaFile 对象
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
// 生成文件
javaFile.writeTo(System.out);
Element 接口在 javax.lang.model.element 包下,它表示一个程序元素,如:包、类/接口、属性变量、方法/方法形参、泛型参数。常用的类与方法如下:
此外还有一个TypeMirror 接口,在 javax.lang.model.type 包下,表示的是元素的类型信息。
下面就开始结合代码看看怎样参考 ARouter 实现一个路由框架的基本功能(主要目的是增进对 ARouter 框架的理解,一些进阶功能没有实现,细节也一定会有不足之处)。
首先要创建注解模块 arouter-annotation,这是一个 Java Library 模块。模块内先定义 @Route 注解:
// 该注解是编译期注解
@Retention(RetentionPolicy.CLASS)
// 该注解作用在类之上
@Target(ElementType.TYPE)
public @interface Route {
// 路径
String path();
// 路径中的组,默认为空字符串
String group() default "";
}
@Route 的 path 就是路径信息,如 /app/MainActivity。接着再新建一个枚举类型 RouteType,它表示路由事件的类型:
/**
* 表示路由的类型,如 ACTIVITY 表示 Activity 间跳转,PROVIDER 表示跨模块调用服务
* 也可以扩展 SERVICE、FRAGMENT 出等类型
*/
public enum RouteType {
// PACKAGE_NAME_ACTIVITY 是 "android.app.Activity",即 Activity 的包名
ACTIVITY(0, Constants.PACKAGE_NAME_ACTIVITY);
int id;
String className;
RouteType(int id, String className) {
this.id = id;
this.className = className;
}
// getters and setters...
}
路由框架可以处理多种类型的事件,比如 Activity 的跳转、Fragment 的跳转、启动一个 Service 或者跨模块调用服务,当前我们只处理 Activity 跳转,因此暂时只有 ACTIVITY 一种类型。
最后封装一个包含跳转的目标 class 对象的 JavaBean:
public class RouteMeta {
// 事件类型,如 ACTIVITY 表示跳转,PROVIDER 表示跨模块调用服务
private RouteType mRouteType;
// 节点类型
private Element mElement;
// 路由组
private String mGroup;
// 路由地址
private String mPath;
// 被注解标记的类对象
private Class<?> mTargetClass;
private RouteMeta(RouteType routeType, String group, String path, Class<?> targetClass) {
mRouteType = routeType;
mGroup = group;
mPath = path;
mTargetClass = targetClass;
}
private RouteMeta(Builder builder) {
mRouteType = builder.mRouteType;
mElement = builder.mElement;
mGroup = builder.mGroup;
mPath = builder.mPath;
mTargetClass = builder.mTargetClass;
}
// 对外提供简易版构造方法,主要是为了方便 APT 生成代码
public static RouteMeta create(RouteType routeType, String group, String path, Class<?> targetClass) {
return new RouteMeta(routeType, group, path, targetClass);
}
// getters and setters...
public static class Builder {
private RouteType mRouteType;
private Element mElement;
private String mGroup;
private String mPath;
private Class<?> mTargetClass;
// setters...
public RouteMeta build() {
if (mPath == null || mPath.length() == 0) {
throw new IllegalArgumentException("Path 为必填项,不能为空!如:/app/MainActivity");
}
return new RouteMeta(this);
}
}
}
我们在路由表中存的是这个 RouteMeta,在需要跳转时会从中取出 mTargetClass 进行反射创建出实例,再跳转。
新建 Android Library 模块 arouter-api,先引入依赖:
dependencies {
support.each { k, v -> implementation v }
api project(':arouter-annotation')
}
arouter-api 是路由框架的功能模块,在当前阶段,只需要定义两个接口:
/**
* 路由组 Group 加载数据接口
*/
public interface ILoadGroup {
/**
* 加载路由组 Group 数据
* 比如:"app", ARouter$$Path$$app.class(实现了 ILoadPath 接口)
*
* @return key:"app", value:"app" 分组对应的路由详细对象类
*/
Map<String, Class<? extends ILoadPath>> loadGroup();
}
/**
* 路由组 Group 对应的详细 Path 加载数据接口
* 比如:app 分组对应有哪些类需要加载
*/
public interface ILoadPath {
/**
* 加载路由组 Group 中的 Path 详细数据
* 比如:"app" 分组下有这些信息:
*
* @return key:"/app/MainActivity", value:封装了MainActivity信息的RouteMeta
*/
Map<String, RouteMeta> loadPath();
}
两个接口分别用来加载路由组 Group 的路由数据、某个 Group 下详细的 Path 路由信息。
准备就绪,下面正式进入注解处理器部分。
新建 Java Library 模块 arouter-compiler,build.gradle 配置如下:
apply plugin: 'java-library'
dependencies {
// AutoService
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
// 注解模块
implementation project(":arouter-annotation")
// JavaPoet
implementation 'com.squareup:javapoet:1.9.0'
// 工具类
implementation 'org.apache.commons:commons-lang3:3.5'
implementation 'org.apache.commons:commons-collections4:4.1'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
先建一个基类处理器 BaseProcessor,把一些公共操作抽出来:
public abstract class BaseProcessor extends AbstractProcessor {
// 操作Element工具类(类、函数、属性都是Element)
Elements elementUtils;
// type(信息类)工具类 包含用于操作TypeMirror的工具方法
Types typeUtils;
// 用来输出警告、错误等日志
Messager messager;
// 文件生成器 类/资源,Filter用来创建新的类文件,class文件以及辅助文件
Filer filer;
// 模块名,由模块在 build.gradle 中通过配置 annotationProcessorOptions 传进来
String moduleName;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
typeUtils = processingEnv.getTypeUtils();
messager = processingEnv.getMessager();
filer = processingEnv.getFiler();
// 获取模块名
Map<String, String> options = processingEnv.getOptions();
if (MapUtils.isNotEmpty(options)) {
moduleName = options.get(Constants.KEY_MODULE_NAME);
}
if (StringUtils.isNotEmpty(moduleName)) {
// 将模块名中的字符清理掉,并输出编译信息
moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", "");
messager.printMessage(Diagnostic.Kind.NOTE,
"The user has configuration the module name, it was [\" + moduleName + \"]");
} else {
// 如果没有配置模块名,就输出错误信息并抛异常。
// messager 输出类型为 ERROR 的话编译会直接停掉。
messager.printMessage(Diagnostic.Kind.ERROR, Constants.NO_MODULE_NAME_TIPS);
throw new RuntimeException("\"ARouter::Compiler >>> No module name, for more information, look at gradle log.\"");
}
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedOptions() {
// 返回支持的注解参数的 key
HashSet<String> hashSet = new HashSet<>();
hashSet.add(Constants.KEY_MODULE_NAME);
return hashSet;
}
}
然后建一个 RouteProcessor 专门对 @Route 进行处理:
@AutoService(Processor.class)
@SupportedAnnotationTypes(Constants.ANNOTATION_TYPE_ROUTE)
public class RouteProcessor extends BaseProcessor {
// 扫描注解后临时保存 RouteMeta 的 Map,key 是组名
private Map<String, List<RouteMeta>> mTempPathMap = new HashMap<>();
// 存放生成 Group 文件所需信息的临时 Map,<组名,Path 文件名>
private Map<String, String> mTempGroupMap = new HashMap<>();
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 如果被扫描的模块没有使用本处理器支持的注解,就无需处理了
if (CollectionUtils.isEmpty(annotations)) return false;
// 拿到所有被 Route 注解修饰的程序元素,由于 @Route 只作用在
// 类上,所以 Set 中其实都是类元素
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Route.class);
if (CollectionUtils.isNotEmpty(elements)) {
parseElement(elements);
}
return true;
}
}
解析 elements 的工作有二:
// 扫描注解后临时保存 RouteMeta 的 Map,key 是组名
private Map<String, List<RouteMeta>> mTempPathMap = new HashMap<>();
/**
* 解析 elements 集合,先将每个 element 解析成 RouteMeta 存入临时
* Map 中,然后再遍历临时 Map,生成路由信息
*/
private void parseElement(Set<? extends Element> elements) {
// 获取 Activity 类对应的 Element 和 TypeMirror 对象
TypeElement activityType = elementUtils.getTypeElement(RouteType.ACTIVITY.getClassName());
TypeMirror activityMirror = activityType.asType();
for (Element element : elements) {
TypeMirror elementMirror = element.asType();
Route route = element.getAnnotation(Route.class);
RouteMeta.Builder builder = new RouteMeta.Builder();
// 判断 element 的类型,如 RouteType.ACTIVITY 等
if (typeUtils.isSubtype(elementMirror, activityMirror)) {
messager.printMessage(Diagnostic.Kind.NOTE, "Found activity route:" + elementMirror.toString());
builder.setRouteType(RouteType.ACTIVITY);
} else {
throw new RuntimeException("The @Route is marked on unsupported class, look at [" + elementMirror.toString() + "].");
}
RouteMeta routeMeta = builder.setElement(element)
.setPath(route.path())
.setGroup(route.group())
.build();
addToTempMap(routeMeta);
}
// 扫描结束后,从临时 Map 里取出 RouteMeta 创建路由文件。
// 注意,这里需要 app、order 等功能模块依赖 arouter-api,否则就找不到这些接口
TypeElement pathLoadType = elementUtils.getTypeElement(Constants.IROUTE_PATH);
TypeElement groupLoadType = elementUtils.getTypeElement(Constants.IROUTE_GROUP);
try {
createPathFile(pathLoadType);
createGroupFile(groupLoadType, pathLoadType);
} catch (IOException e) {
e.printStackTrace();
}
}
将 RouteMeta 添加到 Map 时需要对 path 的合法性进行检查:
private void addToTempMap(RouteMeta routeMeta) {
if (checkRouterPath(routeMeta)) {
String group = routeMeta.getGroup();
List<RouteMeta> metaList = mTempPathMap.get(group);
if (metaList == null) {
metaList = new ArrayList<>();
metaList.add(routeMeta);
mTempPathMap.put(group, metaList);
} else {
metaList.add(routeMeta);
}
}
}
/**
* 判断路径的合法性,并确定组名(因为 @Route 中组名可以不填,但是到这步要确定)
*/
private boolean checkRouterPath(RouteMeta routeMeta) {
String path = routeMeta.getPath();
String group = routeMeta.getGroup();
if (StringUtils.isEmpty(path) || !path.startsWith("/")) {
messager.printMessage(Diagnostic.Kind.ERROR,
"The value of path in @Route is empty or not start with /");
return false;
}
if (path.lastIndexOf("/") == 0) {
messager.printMessage(Diagnostic.Kind.ERROR,
"The value of path in @Route is Illegal,such as /app/MainActivity.");
return false;
}
String finalGroup = path.substring(1, path.indexOf("/", 1));
if (finalGroup.contains("/")) {
messager.printMessage(Diagnostic.Kind.ERROR,
"The value of path in @Route is Illegal,such as /app/MainActivity.");
return false;
}
// 如果 @Route 通过 group() 设置了组名,该组名必须与 path() 中截取的组名相同。
if (StringUtils.isNotEmpty(group) && !group.equals(finalGroup)) {
messager.printMessage(Diagnostic.Kind.ERROR,
"Group name in group() and path() should be same!");
return false;
}
routeMeta.setGroup(finalGroup);
return true;
}
要生成的 Path 路由文件模板如下:
public class ARouter$$Path$$app implements ILoadPath {
@Override
public Map<String, RouteMeta> loadPath() {
Map<String,RouteMeta> pathMap = new HashMap<>();
pathMap.put("/app/MainActivity",RouteMeta.create(RouteType.ACTIVITY,"app","/app/MainActivity",MainActivity.class));
return pathMap;
}
}
生成 Path 路由文件的方法:
/**
* 创建 Path 路由文件
*/
private void createPathFile(TypeElement pathLoadType) throws IOException {
if (MapUtils.isEmpty(mTempPathMap)) return;
for (Map.Entry<String, List<RouteMeta>> entry : mTempPathMap.entrySet()) {
// 方法返回值类型 Map
TypeName methodReturns = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouteMeta.class));
// 配置方法以及第一个语句 Map pathMap = new HashMap<>();
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constants.METHOD_LOAD_PATH)
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.returns(methodReturns)
.addStatement("$T<$T,$T> $N = new $T<>()",
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouteMeta.class),
Constants.VARIABLE_PATH_MAP,
ClassName.get(HashMap.class));
// 遍历 List 生成 pathMap.put("/app/MainActivity",
// RouteMeta.create(RouteType.ACTIVITY,"app","/app/MainActivity",MainActivity.class));
for (RouteMeta routeMeta : entry.getValue()) {
methodBuilder.addStatement("$N.put($S,$T.create($T.$L,$S,$S,$T.class))",
Constants.VARIABLE_PATH_MAP,
routeMeta.getPath(),
ClassName.get(RouteMeta.class),
ClassName.get(RouteType.class),
routeMeta.getRouteType(),
routeMeta.getGroup(),
routeMeta.getPath(),
ClassName.get((TypeElement) routeMeta.getElement()));
}
methodBuilder.addStatement("return $N", Constants.VARIABLE_PATH_MAP);
// 生成类节点
String pathFileName = Constants.PREFIX_OF_PATH_NAME + entry.getKey();
TypeSpec typeSpec = TypeSpec.classBuilder(pathFileName)
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ClassName.get(pathLoadType))
.addMethod(methodBuilder.build())
.build();
// 生成文件,包名由一个常量指定
JavaFile.builder(Constants.PACKAGE_OF_GENERATE_FILE, typeSpec)
.build()
.writeTo(filer);
// 非常重要一步!!!路径文件生成后,才能赋值路由组 mTempGroupMap
mTempGroupMap.put(entry.getKey(), pathFileName);
}
}
要生成的 Group 路由文件模板如下:
public class ARouter$$Group$$app implements ILoadGroup {
@Override
public Map<String, Class<? extends ILoadPath>> loadGroup() {
Map<String,Class<? extends ILoadPath>> pathMap = new HashMap<>();
pathMap.put("app",ARouter$$Path$$app.class);
return pathMap;
}
}
生成 Group 路由文件的方法:
/**
* 创建 Group 路由文件
*/
private void createGroupFile(TypeElement groupLoadType, TypeElement pathLoadType) throws IOException {
// 返回值类型 Map>
TypeName returnType = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(pathLoadType))));
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constants.METHOD_LOAD_GROUP)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(returnType);
// Map> pathMap = new HashMap<>();
methodBuilder.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.VARIABLE_PATH_MAP,
ClassName.get(HashMap.class));
// pathMap.put("app",ARouter$$Path$$app.class);
for (Map.Entry<String, String> entry : mTempGroupMap.entrySet()) {
methodBuilder.addStatement("$N.put($S,$T.class)",
Constants.VARIABLE_PATH_MAP,
entry.getKey(),
ClassName.get(Constants.PACKAGE_OF_GENERATE_FILE,entry.getValue()));
}
methodBuilder.addStatement("return $N", Constants.VARIABLE_PATH_MAP);
// 创建类对象
String groupFileName = Constants.PREFIX_OF_GROUP_NAME + moduleName;
TypeSpec typeSpec = TypeSpec.classBuilder(groupFileName)
.addModifiers(Modifier.PUBLIC)
.addMethod(methodBuilder.build())
.addSuperinterface(ClassName.get(groupLoadType))
.build();
// 创建文件
JavaFile.builder(Constants.PACKAGE_OF_GENERATE_FILE, typeSpec)
.build()
.writeTo(filer);
}
两个路由文件生成的路径是在模块下的 build/generated/ap_generated_sources/debug/out/[packageName] 下:
接下来我们要实现页面的跳转了,先回顾一下 ARouter 框架是如何进行跳转的:
ARouter.getInstance().build("/order/Order_MainActivity")
.withString("key1", "TestString")
.withInt("key2", 66)
.navigation();
我们也要仿照这种调用形式。在 arouter-api 模块下新建单例类 ARouter:
public class ARouter {
private static volatile ARouter sInstance;
// 缓存
private LruCache<String, ILoadGroup> mGroupCache;
private LruCache<String, ILoadPath> mPathCache;
private ARouter() {
mGroupCache = new LruCache<>(100);
mPathCache = new LruCache<>(100);
}
public static ARouter getInstance() {
if (sInstance == null) {
synchronized (ARouter.class) {
if (sInstance == null) {
sInstance = new ARouter();
}
}
}
return sInstance;
}
}
build() 要对传入的路径进行检查,并从路径中截取出组名:
private String mGroup;
private String mPath;
public BundleManager build(String path) {
if (TextUtils.isEmpty(path)) {
throw new IllegalArgumentException("Path should not be null!");
}
mPath = path;
mGroup = extractGroup(path);
return new BundleManager();
}
private String extractGroup(String path) {
if (path.lastIndexOf("/") == 0) {
throw new IllegalArgumentException("Please input a correct path ,such as:/app/MainActivity");
}
String group = path.substring(1, path.indexOf("/", 1));
if (TextUtils.isEmpty(group)) {
throw new IllegalArgumentException("Please input a correct path ,such as:/app/MainActivity");
}
return group;
}
BundleManager 负责维护参数,在最终调用 navigation() 执行跳转操作时,要回到 ARouter 中:
public class BundleManager {
private Bundle mBundle;
public BundleManager() {
mBundle = new Bundle();
}
public BundleManager withString(@NonNull String key, @Nullable String value) {
mBundle.putString(key, value);
return this;
}
public BundleManager withInt(@NonNull String key, @Nullable int value) {
mBundle.putInt(key, value);
return this;
}
public BundleManager withBoolean(@NonNull String key, @Nullable boolean value) {
mBundle.putBoolean(key, value);
return this;
}
public BundleManager withBundle(@NonNull Bundle bundle) {
mBundle = bundle;
return this;
}
// 更多类型自己扩展...
public Bundle getBundle() {
return mBundle;
}
public Object navigation(Context context, int requestCode) {
return ARouter.getInstance().navigation(context, this, requestCode);
}
public Object navigation(Context context) {
return navigation(context, -1);
}
}
ARouter 的 navigation() 来执行寻址和跳转工作,其实就是根据 build() 传进来的路径去路由表中找到对应的 RouteMeta,再拿出 Class 对象通过 startActivity() 执行跳转:
// 拼接文件全类名时用到的常量
public static final String SEPARATOR = "$$";
public static final String PROJECT = "ARouter";
public static final String PREFIX_OF_GROUP_NAME = PROJECT + SEPARATOR + "Group" + SEPARATOR;
public static final String PACKAGE_OF_GENERATE_FILE = "com.demo.arouter.routes";
// 缓存
private Map<String, ILoadGroup> mGroupMap = new HashMap<>();
private Map<String, ILoadPath> mPathMap = new HashMap<>();
public BundleManager build(String path) {
if (TextUtils.isEmpty(path)) {
throw new IllegalArgumentException("Path should not be null!");
}
mPath = path;
mGroup = extractGroup(path);
return new BundleManager();
}
/**
* 从 path 中提取出默认的 group,如果 @Route 中没填 group(),
* 那么默认 group 就作为最终的路由组
*/
private String extractGroup(String path) {
if (!path.startsWith("/") || path.lastIndexOf("/") == 0) {
throw new IllegalArgumentException("Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!");
}
String defaultGroup = path.substring(1, path.indexOf("/", 1));
if (TextUtils.isEmpty(defaultGroup)) {
throw new IllegalArgumentException("Extract the default group failed! There's nothing between 2 '/'!");
}
return defaultGroup;
}
/**
* 根据组名去 Group Map 里找到对应的 Path 的 Class 对象,通过反射拿到该 Class
* 对应的实例,再从 Map 中根据路径找到对应的 RouteMeta,拿出 Class 做跳转
*/
public Object navigation(Context context, BundleManager bundleManager, int requestCode) {
try {
// Group 路由文件的全类名
String groupFileName = PACKAGE_OF_GENERATE_FILE + "." + PREFIX_OF_GROUP_NAME + mGroup;
// 先去缓存中找 Group 路由文件,缓存没有则通过反射获取
ILoadGroup loadGroup = mGroupCache.get(groupFileName);
if (loadGroup == null) {
loadGroup = (ILoadGroup) Class.forName(groupFileName).newInstance();
mGroupCache.put(groupFileName, loadGroup);
}
if (loadGroup.loadGroup() == null) {
throw new RuntimeException("Group route table loads failed.");
}
// 类似的过程,去获取 Path 路由文件
ILoadPath loadPath = mPathCache.get(mPath);
if (loadPath == null) {
loadPath = loadGroup.loadGroup().get(mGroup).newInstance();
mPathCache.put(mPath, loadPath);
}
Map<String, RouteMeta> pathMap = loadPath.loadPath();
if (pathMap == null) {
throw new RuntimeException("Path route table loads failed.");
}
RouteMeta routeMeta = pathMap.get(mPath);
if (routeMeta != null) {
switch (routeMeta.getRouteType()) {
case ACTIVITY:
Intent intent = new Intent(context, routeMeta.getTargetClass());
intent.putExtras(bundleManager.getBundle());
if (requestCode > 0) {
((Activity) context).startActivityForResult(intent, requestCode);
} else {
context.startActivity(intent);
}
break;
// todo 其它类型处理待添加...
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
到这里 Activity 的跳转功能就基本完成了,可以增加几个子模块帮助测试,新模块基本配置大致如下:
// 切换集成化环境与组件化环境
if (isRelease) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
def androidId = rootProject.ext.androidId
def appId = rootProject.ext.appId
def support = rootProject.ext.dependencies
android {
compileSdkVersion androidId.compileSdkVersion
buildToolsVersion androidId.buildToolsVersion
defaultConfig {
// 只有组件化环境时才需要赋予 applicationId
if (!isRelease) {
applicationId appId.order
}
minSdkVersion androidId.minSdkVersion
targetSdkVersion androidId.targetSdkVersion
versionCode androidId.versionCode
versionName androidId.versionName
// 给注解处理器传参
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
// 源集区分不同环境下使用的 AndroidManifest
sourceSets {
main {
if (!isRelease) {
// 测试版本,组件化环境,让 /main/debug/ 下的 AndroidManifest 生效
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
// 正式版本,集成化环境,让 main 下的 AndroidManifest 生效
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
// release 时 debug 目录下的文件不需要合并到主工程中,减小 apk 体积
exclude '**/debug/**'
}
}
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
support.each { k, v -> implementation v }
// 依赖 common 库,common 已经通过 api 依赖 arouter-api
implementation project(":lib-common")
// 声明所使用的注解处理器
annotationProcessor project(":arouter-compiler")
}
跳转代码:
public void jumpToOrder(View view) {
ARouter.getInstance().build("/order/Order_MainActivity")
.withString("key","value")
.navigation(this);
}
@AutoWired 注解可以自动为成员赋值,其实也是通过注解处理器生成代码,在代码中替开发者做了成员赋值这件事。实现方法其实是参考了 ButterKnife 框架的实现思想,先来个注解的定义:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface Autowired {
String name() default "";
}
注解处理器还是要根据注解生成一个文件,在该文件中给使用了 @Autowired 注解的字段进行赋值操作。比如说被启动 Activity 这样添加注解:
@Route(path = "/order/Order_MainActivity")
public class Order_MainActivity extends AppCompatActivity {
@Autowired
String name;
@Autowired(name = "agex")
int age = 6;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_order_main);
// 给当前 Activity 执行依赖注入
ARouter.getInstance().inject(this);
Log.d("Test", "name: " + name + ",age: " + age);
}
}
那么我们要生成的文件大概是这个样子的:
public class Order_MainActivity$$Parameter implements ILoadParameter {
@Override
public void loadParameter(Object target) {
Order_MainActivity t = (Order_MainActivity) target;
t.name = t.getIntent().getStringExtra("name");
t.age = t.getIntent().getIntExtra("agex",t.age);
}
}
ILoadParameter 接口还是定义在功能模块 arouter-api 中,其中的 loadParameter() 就是负责给字段赋值的:
public interface ILoadParameter {
/**
* 通过以下方式完成赋值:
* 目标对象.属性名 = getIntent().属性类型("注解值or属性名");
*
* @param target 目标对象,如:MainActivity
*/
void loadParameter(Object target);
}
新建一个注解处理器类 ParameterProcessor:
@AutoService(Processor.class)
@SupportedAnnotationTypes(Constants.ANNOTATION_TYPE_AUTOWIRED)
public class ParameterProcessor extends BaseProcessor {
// key:类节点,value:被 @Autowired 注解的属性集合
private Map<TypeElement, List<Element>> mTempParameterMap = new HashMap<>();
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (CollectionUtils.isEmpty(annotations)) return false;
// 获取所有被 @Autowired 注解的属性集合
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Autowired.class);
if (CollectionUtils.isNotEmpty(elements)) {
try {
// 解析元素,存入 mTempParameterMap 这个临时 Map 中
parseElements(elements);
// 根据 mTempParameterMap 的信息生成文件
createParameterFile();
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
/**
* 把被 @Autowired 注解的成员信息存入临时 Map
*/
private void parseElements(Set<? extends Element> elements) {
for (Element element : elements) {
// element 是被 @Autowired 修饰的属性,获取属性的上级元素,
// 即类, TypeElement 作为 Map 的 key
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
if (mTempParameterMap.containsKey(typeElement)) {
mTempParameterMap.get(typeElement).add(element);
} else {
List<Element> fieldList = new ArrayList<>();
fieldList.add(element);
mTempParameterMap.put(typeElement, fieldList);
}
}
}
private void createParameterFile() throws IOException {
if (MapUtils.isEmpty(mTempParameterMap)) return;
TypeElement activityElement = elementUtils.getTypeElement(Constants.ACTIVITY);
TypeElement parameterElement = elementUtils.getTypeElement(Constants.IPARAMETER);
// 方法参数
ParameterSpec parameterSpec = ParameterSpec.builder(TypeName.OBJECT, Constants.PARAMETER_NAME).build();
for (Map.Entry<TypeElement, List<Element>> entry : mTempParameterMap.entrySet()) {
TypeElement typeElement = entry.getKey();
// 限制 @Autowired 当前只能用在 Activity 内的属性之上
if (!typeUtils.isSubtype(typeElement.asType(), activityElement.asType())) {
throw new RuntimeException("@Autowired only works on Activity now!");
}
ClassName className = ClassName.get(typeElement);
// 通过 ParameterFactory 生成方法体内容
ParameterFactory parameterFactory = new ParameterFactory.Builder(parameterSpec)
.setClassName(className)
.setMessager(messager)
.build();
// 添加方法体内容第一行
parameterFactory.addFirstStatement();
// 遍历所有属性,生成对应的语句
for (Element element : entry.getValue()) {
parameterFactory.buildStatement(element);
}
// 生成类
String fileName = typeElement.getSimpleName() + Constants.SUFFIX_OF_PARAMETER_FILE;
messager.printMessage(Diagnostic.Kind.NOTE, "APT生成获取参数类文件:" +
className.packageName() + "." + fileName);
TypeSpec typeSpec = TypeSpec.classBuilder(fileName)
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ClassName.get(parameterElement))
.addMethod(parameterFactory.build())
.build();
JavaFile.builder(typeElement.getEnclosingElement().toString(), typeSpec)
.build()
.writeTo(filer);
}
}
}
ParameterFactory 用来生成方法内的各个语句:
public class ParameterFactory {
private static final String CONTENT = "$T t = ($T) target";
private MethodSpec.Builder methodBuilder;
private ClassName className;
private Messager messager;
private ParameterSpec parameterSpec;
private ParameterFactory(Builder builder) {
this.className = builder.className;
this.messager = builder.messager;
this.parameterSpec = builder.parameterSpec;
methodBuilder = MethodSpec.methodBuilder(Constants.METHOD_LOAD_PARAMETER)
.addModifiers(Modifier.PUBLIC)
.addParameter(parameterSpec)
.addAnnotation(Override.class);
}
/**
* 添加方法体内容的第一行:MainActivity t = (MainActivity) target;
*/
public void addFirstStatement() {
methodBuilder.addStatement(CONTENT, className, className);
}
/**
* 构建方体内容,如:t.s = t.getIntent.getStringExtra("s");
*
* @param element 被注解的属性元素
*/
public void buildStatement(Element element) {
TypeMirror typeMirror = element.asType();
// 获取 TypeKind 枚举类型的序列号
int type = typeMirror.getKind().ordinal();
// 获取属性名
String fieldName = element.getSimpleName().toString();
// 获取属性值,如果 @Autowired 的 name() 为空,则采用用属性名
Autowired autowired = element.getAnnotation(Autowired.class);
String annotationValue = StringUtils.isEmpty(autowired.name()) ?
fieldName : autowired.name();
// 最终拼接的前缀
String finalValue = "t." + fieldName;
String methodContent = finalValue + " = t.getIntent().";
// 根据成员类型拼接 methodContent 后面的内容
if (TypeKind.INT.ordinal() == type) {
// finalValue 字符串作为 getIntExtra() 中的默认值部分,因为被
// 注解的属性可能有初始值。
methodContent += "getIntExtra($S," + finalValue + ")";
} else if (TypeKind.BOOLEAN == typeMirror.getKind()) {
// t.fieldName = t.getIntent.getStringExtra("s",t.fieldName);
methodContent += "getBooleanExtra($S," + finalValue + ")";
} else {
// TypeKind 中没有定义字符串类型,需要做特殊处理
if (typeMirror.toString().equalsIgnoreCase(Constants.STRING)) {
// t.s = t.getIntent.getStringExtra("s");
methodContent += "getStringExtra($S)";
}
}
if (methodContent.endsWith(")")) {
methodBuilder.addStatement(methodContent, annotationValue);
} else {
messager.printMessage(Diagnostic.Kind.ERROR, "Just support String、int、boolean type temporarily.");
}
}
public MethodSpec build() {
return methodBuilder.build();
}
public static class Builder {
// ...
}
}
最后在 ARouter 中添加 inject 方法,执行依赖注入:
public static final String SUFFIX_OF_PARAMETER_FILE = "$$Parameter";
private LruCache<String, ILoadParameter> mParameterCache;
private ARouter() {
...
mParameterCache = new LruCache<>(100);
}
public void inject(Activity activity) {
String activityName = activity.getClass().getName();
ILoadParameter iLoadParameter = mParameterCache.get(activityName);
try {
if (iLoadParameter == null) {
// 缓存中没有,就要通过反射拿到接口实现类
String parameterFileName = activityName + SUFFIX_OF_PARAMETER_FILE;
iLoadParameter = (ILoadParameter) Class.forName(parameterFileName).newInstance();
mParameterCache.put(activityName, iLoadParameter);
}
iLoadParameter.loadParameter(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
先上一幅图帮助梳理思路:
图中黑色线表示项目的静态配置,如接口的继承实现关系,还有编译时生成路由表等操作;红色线表示调用者跨模块调用服务的整个过程,已经用序号标出。
图中示例是 personal 模块要调用 order 模块的服务,由于组件化后各个业务模块之间没有依赖,那么想要直接调用其它模块的服务是不可能的,只能迂回操作,让 order 模块主动暴露要对外提供的服务,并将其用 @Route 修饰,在编译时添加到路由表中。而 personal 模块作为调用方需要知道调用服务的路径,并将该路径传给 ARouter 去路由表中查找从而得到服务的实现类,最后执行服务调用。
以上是整体思路,下面来看实际操作。
如图所示,arouter-api 模块中要新建一个 IProvider 接口:
public interface IProvider {
}
它就是一个空接口,表示一种可以被跨模块调用的能力。common 模块中定义的具体的业务接口都需要继承 IProvider,最后再由业务模块中的实现类去实现那个接口,这个过程在后面的测试代码中会有体现。
接下来要处理实现类被添加到路由表中的过程。其实前面在做 Activity 跳转时已经将大部分逻辑都处理了,现在只需要针对跨模块调用服务这种事件类型增加相应的处理即可。先增加事件类型 PROVIDER:
public enum RouteType {
ACTIVITY(0, Constants.PACKAGE_NAME_ACTIVITY),
PROVIDER(1,Constants.PACKAGE_NAME_IPROVIDER);
}
RouteProcessor 在扫描被 @Route 修饰的类时,如果其类型是 RouteType.PROVIDER,那么需要给 RouteMeta 的 routeType 属性设置为 RouteType.PROVIDER:
private void parseElement(Set<? extends Element> elements) {
// 获取 Activity 类对应的 Element 和 TypeMirror 对象
TypeElement activityType = elementUtils.getTypeElement(RouteType.ACTIVITY.getClassName());
TypeElement providerType = elementUtils.getTypeElement(RouteType.PROVIDER.getClassName());
TypeMirror activityMirror = activityType.asType();
for (Element element : elements) {
TypeMirror elementMirror = element.asType();
Route route = element.getAnnotation(Route.class);
RouteMeta.Builder builder = new RouteMeta.Builder();
// 判断 element 的类型,如 RouteType.ACTIVITY、RouteType.PROVIDER 等
if (typeUtils.isSubtype(elementMirror, activityMirror)) {
messager.printMessage(Diagnostic.Kind.NOTE, "Found activity route:" + elementMirror.toString());
builder.setRouteType(RouteType.ACTIVITY);
} else if (typeUtils.isSubtype(elementMirror, providerType.asType())) {
// 新增代码,对 RouteType.PROVIDER 类型的处理
messager.printMessage(Diagnostic.Kind.NOTE, "Found activity route:" + elementMirror.toString());
builder.setRouteType(RouteType.PROVIDER);
} else {
throw new RuntimeException("The @Route is marked on unsupported class, look at [" + elementMirror.toString() + "].");
}
RouteMeta routeMeta = builder.setElement(element)
.setPath(route.path())
.setGroup(route.group())
.build();
addToTempMap(routeMeta);
}
ARouter 去路由表中查找路径对应的 RouteMeta,这个逻辑在处理 Activity 跳转时已经实现了,现在 ARouter 只需要把拿到的 RouteMeta 的 Class 对象的实例返回给调用者即可:
/**
* 根据组名去 Group Map 里找到对应的 Path 的 Class 对象,通过反射拿到该 Class
* 对应的实例,再从 Map 中根据路径找到对应的 RouteMeta,拿出 Class 做跳转
*/
public Object navigation(Context context, BundleManager bundleManager, int requestCode) {
try {
// 从路由中拿到 RouteMeta 的过程前面写过,省略了...
RouteMeta routeMeta = pathMap.get(mPath);
if (routeMeta != null) {
switch (routeMeta.getRouteType()) {
case ACTIVITY:
// ...
break;
// 返回 Class 对象实例
case PROVIDER:
return routeMeta.getTargetClass().newInstance();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
这样调用方拿到接口实现类的实例后就可以调用相应服务了,为了调用方使用更方便,可以让 @Autowired 修饰接口成员,在 ParameterProcessor 生成的代码中执行接口赋值操作:
public class ParameterFactory {
private Types typeUtils;
private TypeMirror providerType;
private ParameterFactory(Builder builder) {
this.className = builder.className;
this.messager = builder.messager;
this.typeUtils = builder.typeUtils;
methodBuilder = MethodSpec.methodBuilder(Constants.METHOD_LOAD_PARAMETER)
.addModifiers(Modifier.PUBLIC)
.addParameter(builder.parameterSpec)
.addAnnotation(Override.class);
// Constants.IPROVIDER 是 IProvider 接口的全类名
providerType = builder.elementUtils.getTypeElement(Constants.IPROVIDER).asType();
}
public void buildStatement(Element element) {
//....
if (TypeKind.INT.ordinal() == type) {
methodContent += "getIntExtra($S," + finalValue + ")";
} else if (TypeKind.BOOLEAN == typeMirror.getKind()) {
methodContent += "getBooleanExtra($S," + finalValue + ")";
} else {
if (typeMirror.toString().equalsIgnoreCase(Constants.STRING)) {
methodContent += "getStringExtra($S)";
}
// 增加接口赋值语句的处理
if (typeUtils.isSubtype(typeMirror, providerType)) {
// t.orderService = (IOrderService) ARouter.getInstance().build("/order/OrderServiceImpl").navigation(t);
methodContent = finalValue+" = ($T) $T.getInstance().build($S).navigation(t)";
methodBuilder.addStatement(methodContent,
ClassName.get(typeMirror),
ClassName.get(Constants.MANAGER_PACKAGE,Constants.PROJECT),
annotationValue);
return;
}
}
}
}
这样生成的参数赋值文件如下:
public class Personal_MainActivity$$Parameter implements ILoadParameter {
@Override
public void loadParameter(Object target) {
Personal_MainActivity t = (Personal_MainActivity) target;
t.orderService = (IOrderService) ARouter.getInstance().build("/order/OrderServiceImpl").navigation(t);
}
}
到这针对跨模块调用的修改就基本完成了。
在 common 模块中定义一个 IOrderService 接口,这是具体的模块要对外暴露的业务接口:
public interface IOrderService extends IProvider {
/**
* @return 订单数量
*/
int getOrderCount();
}
然后 Order 模块要实现这个接口,并且要将该实现类通过 @Route 添加到路由中:
@Route(path = "/order/OrderServiceImpl")
public class OrderServiceImpl implements IOrderService {
@Override
public int getOrderCount() {
return 666;
}
}
最后在调用者模块中给接口成员添加 @Autowired 注解并标明要调用接口的路径:
@Route(path = "/personal/Personal_MainActivity")
public class Personal_MainActivity extends AppCompatActivity {
@Autowired(name = "/order/OrderServiceImpl")
IOrderService orderService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_personal_main);
ARouter.getInstance().inject(this);
Toast.makeText(this, "订单数量:" + orderService.getOrderCount(), Toast.LENGTH_LONG).show();
}
}
测试效果:
使用这种方式不仅可以像上面这样传输一个简单的 Int 值,也可以传资源 ID 或者一个引用类型对象等。
项目代码已经上传 GitHub:ARouterDemo