1.1 简单介绍
1.2 如何在编译期就生成源文件
1.1 ActivityRouter路由跳转执行流程
简单介绍:
ActivityRouter通过APT编译时,统计项目中使用@Modules、@Module、@Router注解的地方,通过@Module和@Router为每个模块创建了自己的路由信息。比如module1模块如下。
在通过@Modules创建了RouterInit源文件,在RouterInit中调用了每一个模块路由表的静态map方法,把相应的路由加载到到内存中来,这个内存是在ActivityRouter的源码中,每个模块都依赖有它的源码。所以都可以拿到这快内存来用。从而实现模块化的功能。
ActivityRouter如何在编译期根据注解为每个模块生成了相应的路由信息。
1.1 首先了解两个东西。
APT(Abstract Processor Tool) :注解处理器,可以在编译过程中对编译时Annotation做一些操作,按照自己的规则生成想应的源文件。
Javapoet: 官方介绍:在执行诸如批注处理或与元数据文件(例如,数据库模式,协议格式)交互之类的操作时,源文件的生成可能非常有用。 通过生成代码,您无需编写样板文件,同时还保留了元数据的唯一真实来源。简单说就是通过面向对象的方式,可以很方便的生成源文件。不需要一行字符串一行字符串的去写入了。
javapoet在github地址:https://github.com/square/javapoet
1.2 在 RouterProcessor 中对@Modules,@Module,@Router做处理。
继承AbstractProcessor,标识RouterProcessor是一个注解处理器类。
@AutoService(Processor.class)
public class RouterProcessor extends AbstractProcessor {
}
getSupportedAnnotationTypes方法,这里返回Modules、Module、Router。标示对这三个注解做处理,在
process 方法中可以拿到所有此注解的节点信息。
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> ret = new HashSet<>();
ret.add(Modules.class.getCanonicalName());
ret.add(Module.class.getCanonicalName());
ret.add(Router.class.getCanonicalName());
return ret;
}
process 方法
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
debug("process apt with " + annotations.toString());
if (annotations.isEmpty()) {
return false;
}
boolean hasModule = false;
boolean hasModules = false;
// module
String moduleName = "RouterMapping";
Set<? extends Element> moduleList = roundEnv.getElementsAnnotatedWith(Module.class);
if (moduleList != null && moduleList.size() > 0) { //-------1 、获取Module
Module annotation = moduleList.iterator().next().getAnnotation(Module.class);
moduleName = moduleName + "_" + annotation.value();
hasModule = true;
}
// modules
String[] moduleNames = null;
Set<? extends Element> modulesList = roundEnv.getElementsAnnotatedWith(Modules.class);
if (modulesList != null && modulesList.size() > 0) {-------1 、获取Modules
Element modules = modulesList.iterator().next();
moduleNames = modules.getAnnotation(Modules.class).value();
hasModules = true;
}
// RouterInit 生成
if (hasModules) { //------3
debug("generate modules RouterInit");
generateModulesRouterInit(moduleNames);
} else if (!hasModule) {
debug("generate default RouterInit");
generateDefaultRouterInit();
}
// RouterMapping_module 生成
return handleRouter(moduleName, roundEnv);
}
第一步首先获取Module节点,拿到Module中的name,拼接RouterMapping_,当作路由信息文件名。
第二步获取Modules,如果Modules注解中有moduleName,讲会走到第三步。
generateModulesRouterInit方法生成RouterInit。handleRouter 生成模块路由信息类。
先看一下generateModulesRouterInit方法。
generateModulesRouterInit,生成RouterInit源文件。如下图
private void generateModulesRouterInit(String[] moduleNames) {
//1、生成public static final init()方法
MethodSpec.Builder initMethod = MethodSpec.methodBuilder("init") //方法
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC);
for (String module : moduleNames) {
initMethod.addStatement("RouterMapping_" + module + ".map()");//2
}
TypeSpec routerInit = TypeSpec.classBuilder("RouterInit") //生成public final RouterInit类
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(initMethod.build())
.build();
try {//4
JavaFile.builder("com.github.mzule.activityrouter.router", routerInit)
.build()
.writeTo(filer); //写入文件,com.github.mzule.activityrouter.router.RouterInit
} catch (Exception e) {
e.printStackTrace();
}
}
generateModulesRouterInit 中用javapoet
1、生成init方法
2、initMethod.addStatement("RouterMapping_" + module + ".map()"); RouterMapping_module就是每个模块的路由信息,这里调用它的map方法。虽然这时候路由信息还没有生成,后面会有。
3、生成public final RouterInit类。
4、写入文件,固定包名com.github.mzule.activityrouter.router
handleRouter 方法
为每个模块生成路由信息类。 生成后的文件如下图
private boolean handleRouter(String genClassName, RoundEnvironment roundEnv) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Router.class);
MethodSpec.Builder mapMethod = MethodSpec.methodBuilder("map") //TODO 1、 生成Map方法
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
.addStatement("java.util.Map<String,String> transfer = null")
.addStatement("com.github.mzule.activityrouter.router.ExtraTypes extraTypes")
.addCode("\n");
for (Element element : elements) {//TODO Router操作
Router router = element.getAnnotation(Router.class);
String[] transfer = router.transfer();
if (transfer.length > 0 && !"".equals(transfer[0])) { // TODO 2、transfer 转换操作
mapMethod.addStatement("transfer = new java.util.HashMap<String, String>()");
for (String s : transfer) {
String[] components = s.split("=>");
if (components.length != 2) {
error("transfer `" + s + "` not match a=>b format");
break;
}
mapMethod.addStatement("transfer.put($S, $S)", components[0], components[1]);
}
} else {
mapMethod.addStatement("transfer = null");
}
mapMethod.addStatement("extraTypes = new com.github.mzule.activityrouter.router.ExtraTypes()");
mapMethod.addStatement("extraTypes.setTransfer(transfer)");
//TODO 3、 参数操作,拼接写入
addStatement(mapMethod, int.class, router.intParams());
addStatement(mapMethod, long.class, router.longParams());
addStatement(mapMethod, boolean.class, router.booleanParams());
addStatement(mapMethod, short.class, router.shortParams());
addStatement(mapMethod, float.class, router.floatParams());
addStatement(mapMethod, double.class, router.doubleParams());
addStatement(mapMethod, byte.class, router.byteParams());
addStatement(mapMethod, char.class, router.charParams());
for (String format : router.value()) {
ClassName className;
Name methodName = null;
if (element.getKind() == ElementKind.CLASS) { //TODO 4.1 Router修饰Activity
className = ClassName.get((TypeElement) element);
} else if (element.getKind() == ElementKind.METHOD) {// 4.2 Router修饰方法
className = ClassName.get((TypeElement) element.getEnclosingElement());
methodName = element.getSimpleName();
} else {
throw new IllegalArgumentException("unknow type");
}
if (format.startsWith("/")) {
error("Router#value can not start with '/'. at [" + className + "]@Router(\"" + format + "\")");
return false;
}
if (format.endsWith("/")) {
error("Router#value can not end with '/'. at [" + className + "]@Router(\"" + format + "\")");
return false;
}
if (element.getKind() == ElementKind.CLASS) { //TODO 5.1 写入Routers.map class为Activity 做activity跳转
mapMethod.addStatement("com.github.mzule.activityrouter.router.Routers.map($S, $T.class, null, extraTypes)", format, className);
} else { //TODO //TODO 5.2 写入Routers.map class为null method 为静态方法名,做静态方法调用
mapMethod.addStatement("com.github.mzule.activityrouter.router.Routers.map($S, null, " +
"new MethodInvoker() {\n" +
" public void invoke(android.content.Context context, android.os.Bundle bundle) {\n" +
" $T.$N(context, bundle);\n" +
" }\n" +
"}, " +
"extraTypes)", format, className, methodName);
}
}
mapMethod.addCode("\n");
}
TypeSpec routerMapping = TypeSpec.classBuilder(genClassName)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(mapMethod.build())
.build();
try {
//TODO 写入到文件
JavaFile.builder("com.github.mzule.activityrouter.router", routerMapping)
.build()
.writeTo(filer);
} catch (Throwable e) {
e.printStackTrace();
}
return true;
}
以上代码就是生成了module的路由信息类,分为几块分析。
1、javapoet创建public static final 的map方法,方法内加入了两行代码:
java.util.Map<String,String> transfer = null com.github.mzule.activityrouter.router.ExtraTypes extraTypes
MethodSpec.Builder mapMethod = MethodSpec.methodBuilder("map") //TODO 1、 生成Map方法
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
.addStatement("java.util.Map<String,String> transfer = null")
.addStatement("com.github.mzule.activityrouter.router.ExtraTypes extraTypes")
.addCode("\n");
2、 transfer操作,支持传参的key name 转换,这里读取Router注解的transfer参数,然后根据"=>"分割,所以components[0]为原始key,components[1] 为要转换后的key。添加到transfer这个map集合中。然后把这个transfer添加到了ExtraTypes的变量中。
for (Element element : elements) {//TODO Router操作
Router router = element.getAnnotation(Router.class);
String[] transfer = router.transfer();
if (transfer.length > 0 && !"".equals(transfer[0])) { // TODO 2、transfer 转换操作
mapMethod.addStatement("transfer = new java.util.HashMap<String, String>()");
for (String s : transfer) {
String[] components = s.split("=>");
if (components.length != 2) {
error("transfer `" + s + "` not match a=>b format");
break;
}
mapMethod.addStatement("transfer.put($S, $S)", components[0], components[1]);
}
} else {
mapMethod.addStatement("transfer = null");
}
mapMethod.addStatement("extraTypes = new com.github.mzule.activityrouter.router.ExtraTypes()");
mapMethod.addStatement("extraTypes.setTransfer(transfer)");
3、从Router中拿到intParams,longParams等。做参数拼接,比如int类型,需要写入 setIntExtra
//TODO 3、 参数操作,拼接写入
addStatement(mapMethod, int.class, router.intParams());
addStatement(mapMethod, long.class, router.longParams());
addStatement(mapMethod, boolean.class, router.booleanParams());
addStatement(mapMethod, short.class, router.shortParams());
addStatement(mapMethod, float.class, router.floatParams());
addStatement(mapMethod, double.class, router.doubleParams());
addStatement(mapMethod, byte.class, router.byteParams());
addStatement(mapMethod, char.class, router.charParams());
看一下addStatement操作
private void addStatement(MethodSpec.Builder mapMethod, Class typeClz, String[] args) {
String extras = join(args);
if (extras.length() > 0) {
String typeName = typeClz.getSimpleName();
//55555
String s = typeName.substring(0, 1).toUpperCase() + typeName.replaceFirst("\\w", "");//TODO int 转成 Int
mapMethod.addStatement("extraTypes.set" + s + "Extra($S.split(\",\"))", extras);
}
}
join
private String join(String[] args) {
if (args == null || args.length == 0) {
return "";
}
if (args.length == 1) {
return args[0];
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < args.length - 1; i++) {
sb.append(args[i]).append(",");
}
sb.append(args[args.length - 1]);
return sb.toString();
}
在join方法中,把参数的key name数组,用,拼接成字符串,在5555地方把类型的首字母大些,比如int 变成Int,拼接出来后就成了 extraTypes.setIntExtra("aa".split(","));
4、判断Router修饰的节点,如果是class ,就代表是跳转Activity,直接取className就可以。如果是Method,取外部className和methodName。
if (element.getKind() == ElementKind.CLASS) { //TODO 4.1 Router修饰Activity
className = ClassName.get((TypeElement) element);
} else if (element.getKind() == ElementKind.METHOD) {// 4.2 Router修饰方法
className = ClassName.get((TypeElement) element.getEnclosingElement());
methodName = element.getSimpleName();
} else {
throw new IllegalArgumentException("unknow type");
}
5、 如果是Class ,写入
com.github.mzule.activityrouter.router.Routers.map("mainactivity", MainActivity.class, null, extraTypes);
如果是method ,写入
com.github.mzule.activityrouter.router.Routers.map("download", null, new MethodInvoker() { public void invoke(android.content.Context context, android.os.Bundle bundle) { DownLoadService.downloadHeader(context, bundle); } }, extraTypes);
if (element.getKind() == ElementKind.CLASS) { //TODO 5.1 写入Routers.map class为Activity 做activity跳转
mapMethod.addStatement("com.github.mzule.activityrouter.router.Routers.map($S, $T.class, null, extraTypes)", format, className);
} else { //TODO //TODO 5.2 写入Routers.map class为null method 为静态方法名,做静态方法调用
mapMethod.addStatement("com.github.mzule.activityrouter.router.Routers.map($S, null, " +
"new MethodInvoker() {\n" +
" public void invoke(android.content.Context context, android.os.Bundle bundle) {\n" +
" $T.$N(context, bundle);\n" +
" }\n" +
"}, " +
"extraTypes)", format, className, methodName);
}
以上就是APT生成源文件的实现方式。
Arouter 跳转路线分析
Routers.open 方法
private static boolean open(Context context, Uri uri, int requestCode, RouterCallback callback) {
boolean success = false;
if (callback != null) {
if (callback.beforeOpen(context, uri)) {
return false;
}
}
try {
success = doOpen(context, uri, requestCode);
} catch (Throwable e) {
e.printStackTrace();
if (callback != null) {
callback.error(context, uri, e);
}
}
if (callback != null) {
if (success) {
callback.afterOpen(context, uri);
} else {
callback.notFound(context, uri);
}
}
return success;
}
操作都在doOpen中。
doOpen方法
private static boolean doOpen(Context context, Uri uri, int requestCode) {
initIfNeed();
Path path = Path.create(uri);
for (Mapping mapping : mappings) {
if (mapping.match(path)) {
if (mapping.getActivity() == null) {
mapping.getMethod().invoke(context, mapping.parseExtras(uri));
return true;
}
Intent intent = new Intent(context, mapping.getActivity());
intent.putExtras(mapping.parseExtras(uri));
intent.putExtra(KEY_RAW_URL, uri.toString());
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
if (requestCode >= 0) {
if (context instanceof Activity) {
((Activity) context).startActivityForResult(intent, requestCode);
} else {
throw new RuntimeException("can not startActivityForResult context " + context);
}
} else {
context.startActivity(intent);
}
return true;
}
}
return false;
}
第一步调用initIfNeed 做初始化。
private static void initIfNeed() {
if (!mappings.isEmpty()) {
return;
}
RouterInit.init();
sort();
}
initIfNeed调用了刚刚讲的APT生成的RouterInit.init方法。然后调用了模块的路由信息类。
看一下RouterMapping_module1.map();
调用com.github.mzule.activityrouter.router.Routers.map,把路由信息都放到了Routers中的mappings集合中。
通过initIfNeed方法后,路由信息加载到了内存中来。
Path path = Path.create(uri); 执行后,返回的Path是一个链表结构。 比如传入的路由地址为axx://module/main/2,解析后Path为如下。
3、遍历mapping进行匹配路径,mapping现在已经装载了所有的路由信息。在 mapping.match(path)中进行匹配操作。
match方法
public boolean match(Path fullLink) {
if (formatPath.isHttp()) {
return Path.match(formatPath, fullLink);
} else {
// fullLink without host
boolean match = Path.match(formatPath.next(), fullLink.next());
if (!match && fullLink.next() != null) {
// fullLink with host
match = Path.match(formatPath.next(), fullLink.next().next());
}
return match;
}
}
如果是http的话,这里是直接的操作是每一层路径都需要匹配,包括 scheme,host、和每一层路径。
如果不是http的话,scheme没有做处理,即使两条路径的scheme不同,也会认为匹配成功。
match = Path.match(formatPath.next(), fullLink.next().next()); 即下两级结构开始做匹配也认为匹配成功。 举例描述
比如:传入aaa/bbb/ccc ,路由表中记录的是bbb/ccc, 也认为是匹配成功。
4、匹配成功后
if (mapping.getActivity() == null) {
mapping.getMethod().invoke(context, mapping.parseExtras(uri));
return true;
}
Intent intent = new Intent(context, mapping.getActivity());
intent.putExtras(mapping.parseExtras(uri));
intent.putExtra(KEY_RAW_URL, uri.toString());
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
if (requestCode >= 0) {
if (context instanceof Activity) {
((Activity) context).startActivityForResult(intent, requestCode);
} else {
throw new RuntimeException("can not startActivityForResult context " + context);
}
} else {
context.startActivity(intent);
}
return true;
}
如果mapping.getActivity 是null,代表路由的是方法,mapping.getMethod直接执行invoke,parseExtras解析后把参数传入
parseExtras
public Bundle parseExtras(Uri uri) {
Bundle bundle = new Bundle();
// parameter
Set<String> names = UriCompact.getQueryParameterNames(uri);
for (String name : names) {
String value = uri.getQueryParameter(name);
put(bundle, name, value);
}
return bundle;
}
这里UriCompact.getQueryParameterNames(uri); 就是后去到uri中?以后的字符串,然后在uri.getQueryParameter(name)中搭配&和=符号,把key 和 value分离,调用put(bundle,name,value)
put方法
private void put(Bundle bundle, String name, String value) {
int type = extraTypes.getType(name);
name = extraTypes.transfer(name);
if (type == ExtraTypes.STRING) {
type = extraTypes.getType(name);
}
switch (type) {
case ExtraTypes.INT:
bundle.putInt(name, Integer.parseInt(value));
break;
case ExtraTypes.LONG:
bundle.putLong(name, Long.parseLong(value));
break;
case ExtraTypes.BOOL:
bundle.putBoolean(name, Boolean.parseBoolean(value));
break;
case ExtraTypes.SHORT:
bundle.putShort(name, Short.parseShort(value));
break;
case ExtraTypes.FLOAT:
bundle.putFloat(name, Float.parseFloat(value));
break;
case ExtraTypes.DOUBLE:
bundle.putDouble(name, Double.parseDouble(value));
break;
case ExtraTypes.BYTE:
bundle.putByte(name, Byte.parseByte(value));
break;
case ExtraTypes.CHAR:
bundle.putChar(name, value.charAt(0));
break;
default:
bundle.putString(name, value);
break;
}
}
1.1 extraTypes.getType(name)根据key获取类型,如果这个key 在 intExtra数组或者 longExtra有则标记类型,否则一律按String处理。
public int getType(String name) {
if (arrayContain(intExtra, name)) {
return INT;
}
if (arrayContain(longExtra, name)) {
return LONG;
}
if (arrayContain(booleanExtra, name)) {
return BOOL;
}
if (arrayContain(shortExtra, name)) {
return SHORT;
}
if (arrayContain(floatExtra, name)) {
return FLOAT;
}
if (arrayContain(doubleExtra, name)) {
return DOUBLE;
}
if (arrayContain(byteExtra, name)) {
return BYTE;
}
if (arrayContain(charExtra, name)) {
return CHAR;
}
return STRING;
}
private static boolean arrayContain(String[] array, String value) {
if (array == null) {
return false;
}
for (String s : array) {
if (s.equals(value)) {
return true;
}
}
return false;
}
回到put 方法中,第二步 name = extraTypes.transfer(name); 进行transfer转换key name。
public String transfer(String name) {
if (transfer == null) {
return name;
}
String result = transfer.get(name);
return result != null ? result : name;
}
transfer是一个map,是在apt编译时生成并添加到extraTypes中的,key为原生key,value为要转换的key。操作完之后根据类型执行对饮的putInt或putLong操作。
在回到匹配成功后的操作,如果Activity不为null ,则代表Activity界面跳转,代码如下
if (mapping.match(path)) {
if (mapping.getActivity() == null) {
mapping.getMethod().invoke(context, mapping.parseExtras(uri));
return true;
}
Intent intent = new Intent(context, mapping.getActivity());
intent.putExtras(mapping.parseExtras(uri));
intent.putExtra(KEY_RAW_URL, uri.toString());
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
if (requestCode >= 0) {
if (context instanceof Activity) {
((Activity) context).startActivityForResult(intent, requestCode);
} else {
throw new RuntimeException("can not startActivityForResult context " + context);
}
} else {
context.startActivity(intent);
}
return true;
}
通过startActivity直接跳转对应界面。
over!!