2019-06-30
https://blog.csdn.net/xiatiandefeiyu/article/details/79567253
组件化开发之路由器模块详解(ActivityRouter源码详解)
路由器的作用是什么?通俗的讲,路由器的作用就是一根网线满足多人上网的需求。而在开发中路由器模块的作用就是实现中转分发,也就是说将原来有关系的模块(有依赖的模块分开),产生一个中间的模块,让原来依赖的两个模块都去和路由模块交互,从而将原来两个有关系的模块拆分开,利如我现在在开发一个app,根据业务需求这里需要开发一个便民中心模块,但是进入这个模块肯定得先从主模块点击进去,这里如果有数据之间的传递的话,就产生了交互。而交互产生了的话,就会产生依赖,此时便民中心模块依赖主模块,而此时如果我们搭建路由器模块的话,那么主模块就和便民中心模块解耦了,主模块通过路由器模块将数据分发给便民中心模块,而不像以前我们在android端跳转的转的话直接通过Intent跳转,传值也通过Intent传,这里有个弊端就如定义传值的key值,在接受这个key随确定value值得时候,你必须得知道这个key是什么,后期维护你需要一个一个Activity的去找,跳转到那个Activity需要传什么值,甚是头疼,那么路由器的另一个优点来了,我们从一个模块跳到另一个模块需要传的值的key完全可以通过注解标记出来,一目了然,而不用那么费力的找了,而且具体怎么传的完全隔离出去,用户完全不用考虑实现的细节。
通俗一点的讲,这里我们先不讨论模块之间的隔离,只简单的讨论一下从Activity(A)跳转到Activity(B)的场景,首先我们第一点得确定是从哪一个Activity跳到哪一个Activity,最初的跳转是你要么直接显示跳转、要么隐式跳转,但是不管怎么跳转都需要知道具体是那个Activity(显示)、隐式(知道action什么的等),而采用路由器模式的时候,你完全不用关心他的Activity的具体名字是什么,或者他的action等是什么,你只需要给它造一个匹配规则,让路由器自己找到你想传值和跳转的Activity到底是哪一个,就好比我定义一个IP地址,这个Ip地址就是指向B(Activity)的,那么A(Activity)通过将ip地址传给路由器然后路由器帮你分析你想跳转到哪一个Activity中,最终锁定到B,,日后你想修改跳转规则或传值是怎么传的,只要查看路由表就好了,这就是路由模块的好处了。
如果你想自己实现一个路由框架的话,得做哪些准备呢?首先你得知道路由器到底是干嘛使的,通过上面的分析,你大体应该知道路由器在Android端的作用了,那么我们首先需要做的就是将所有的模块的主Activity制定他们匹配的ip,也就是说为A模块主(Activity)标记一个唯一的ip地址,也就是加个域名,例如A对应http://feiyu/,B模块对应http://zp/
然后将对应关系保存到缓存中,保存到缓存中后,我们通过路由器调用之后,路由器从路由表中取出对应关系,但是路由表必须知道这个关系的规则是什么,他需要根据规则将路由表中数据解析出来,然后实现跳转,那么在写路由器模块的时候我们必须为路由器写上解析器模块,那么这里我们已经想到要用至少到两个设计模式了,单利模式、解释器模式(专门用来解析规则例如正则表达式就是用的这种设计模式)。
如果我们要保存映射关系,是否将它固定,也就是说每写一个模块的话,将它手动添加到路由表中,很显然这是不科学的,因为我们写的路由器模块是给其他小伙伴用的,他不一定愿意看你的代码,那么怎么样让他写的模块映射出路由表里的数据,这里就用到了注解,我们在路由器模块中写出注解规则,和你合作的小伙伴只要按照你的注解规则,为他的模块主activity标记上注解,我们就能动态的将注解解析出来,然后将它放到路由表中。这样路由器解析的时候就会找到需要跳转的模块。
但是这里又有一个问题,该采用运行时注解还是编译器时注解呢,运行时注解,就是你动态通过反射将注解解析出来放在集合中,但是那需要在运行时解析,比较耗费点时间,那么采用编译器注解呢,就是说在编译的时候先检查有没有编译注解,如果有的话先通过注解生成java文件,然后才将新生成的java文件和你写的项目java文件一起编译成class文件,但是这么做的话会多出java文件,从而使apk包增大,还有可能遇到android的65536的限制,但是一点不影响运行时的速度,综合考虑还是采用编译时注解(apt技术)。
看了这么多,是不是有点累了,先欣赏下美女休息一下
image好了,进入正题,这里我们来一起分析一下ActivityRouter源码,参观一下别人是怎么实现的,ActivityRouter源码 至于为什么要通过这个框架分析,因为它虽然有点缺点,但是它小巧并且已经能将路由框架的原理思想大体的表现出来。
前面提到编译时期的注解,那么先从这个框架的apt部分开始说起,如果想在android studio中实现apt功能只需要在app下build.gradle配置文件中加入
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">compile 'com.google.auto.service:auto-service:1.0-rc3'</pre>
然后在你的编译处理类上加入这个注解
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">@AutoService(Processor.class)
public class RouterProcessor extends AbstractProcessor </pre>
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">先来看下一下实现这个编译处理类需要实现哪些方法:</pre>
public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); messager = processingEnv.getMessager(); filer = processingEnv.getFiler(); }
初始化方法,在这里你需要获得你需要使用的工具类,例如Messager(日志相关的辅助类),Filter(文件相关的辅助类(用它辅助生成新的java文件))
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">具体的其它辅助类,小伙伴们请查阅相关文档。</pre>
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; }
这个方法用来告诉注解处理器那些注解需要处理。这里Module、Modules和Router处理注解需要处理,也就是说这个框架只声明了这
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">三种注解</pre>
@Retention(RetentionPolicy.CLASS)public @interface Module { String value();}
@Retention(RetentionPolicy.CLASS)public @interface Modules { String[] value();}
@Retention(RetentionPolicy.CLASS)这个注解用来标记是在编译时的注解,没有标记要注解的类型的话,默认为类注解
Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.CLASS)public @interface Router { String[] value(); String[] stringParams() default ""; String[] intParams() default ""; String[] longParams() default ""; String[] booleanParams() default ""; String[] shortParams() default ""; String[] floatParams() default ""; String[] doubleParams() default ""; String[] byteParams() default ""; String[] charParams() default ""; String[] transfer() default "";}
这个注解即可以标记类,又可以标记方法,其中带params是传递的参数
接下来是下面这个方法
public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); }
返回支持的java版本
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">最重要的是实现下面这个方法,只要捕捉到你设置的注解最终就会回调这个方法供你生成java文件,如下:</pre>
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) { 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) { Element modules = modulesList.iterator().next(); moduleNames = modules.getAnnotation(Modules.class).value(); hasModules = true; } // RouterInit if (hasModules) { debug("generate modules RouterInit"); generateModulesRouterInit(moduleNames); } else if (!hasModule) { debug("generate default RouterInit"); generateDefaultRouterInit(); } // RouterMapping return handleRouter(moduleName, roundEnv); }
在这个方法里面处理所有被注解了的元素,这里需要弄懂元素类型总共有多少种,如下:
ackage com.example; // PackageElement public class Foo { // TypeElement 类型元素 private int a; // VariableElement代表成员变量 private Foo other; // VariableElement public Foo () {} // ExecuteableElement 匹配方法元素 public void setA ( // ExecuteableElement int newA // TypeElement 参数也代表TypeElement ) {}}
这个方法首先获得Module和Modules注解的元素,然后创建RouterInit这个类,来看一下创建的这个类中有哪些方法和变量
MethodSpec.Builder initMethod = MethodSpec.methodBuilder("init") .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC); for (String module : moduleNames) { initMethod.addStatement("RouterMapping_" + module + ".map()"); } TypeSpec routerInit = TypeSpec.classBuilder("RouterInit") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(initMethod.build()) .build(); try { JavaFile.builder("com.github.mzule.activityrouter.router", routerInit) .build() .writeTo(filer); } catch (Exception e) { e.printStackTrace(); } }
这个方法用了javaPoet来生成java文件,javaPoet是啥?javaPoet是JakeWharton大神编写的用于辅助生成java文件的框架。
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">javaPoet
这个方法的意思就是创建包名为com.github.mzule.activityrouter.router的类,并在这个类中创建静态init方法,并在init方法中调用</pre>
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">RouterMapping_module(注解module的名字被添加注解的activity)类的map方法,当然RouterMapping_module类也是动态生成的,来看一下</pre>
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">它生成的代码</pre>
private boolean handleRouter(String genClassName, RoundEnvironment roundEnv) { Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Router.class); MethodSpec.Builder mapMethod = MethodSpec.methodBuilder("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) { Router router = element.getAnnotation(Router.class); String[] transfer = router.transfer(); if (transfer.length > 0 && !"".equals(transfer[0])) { 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)"); 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) { className = ClassName.get((TypeElement) element); } else if (element.getKind() == ElementKind.METHOD) { 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) { mapMethod.addStatement("com.github.mzule.activityrouter.router.Routers.map($S, $T.class, null, extraTypes)", format, className); } else { 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 { JavaFile.builder("com.github.mzule.activityrouter.router", routerMapping) .build() .writeTo(filer); } catch (Throwable e) { e.printStackTrace(); } return true; }
这个方法就是生成RouterMapping_module类,并生成静态的map()方法,那么在map方法里都添加了哪些操作呢?
java.util.Map<String,String> transfer = null
添加转化的对象(顾名思义就是将一个名字转化为另一个名字),介绍完生成java的对象的时候会详细讨论
extraTypes = new com.github.mzule.activityrouter.router.ExtraTypes()
创建传值类型的类,用来标记所传的值是什么类型的
mapMethod.addStatement("com.github.mzule.activityrouter.router.Routers.map($S, $T.class, null, extraTypes)", format, className);
调用Routers类的map方法,将映射存到路由表中
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">讨论到这里,大体的可以看出这个框架利用apt技术动态的生成两个java类,这两个java类的主要作用就是将注解交给路由器的</pre>
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">解析类解析映射关系,最终将,映射关系存到路由器缓存中。只要在app启动时调用这两个java类,就可以将Activity和ip的映射</pre>
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">关系保存到路由器表中,在路由器中转中,就可以找到合适的模块的主Activity进行分发跳转了。</pre>
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);"> 现在来简单的运用这个框架,如果想把某个模块的主Activity加入到路由表中,直接在这个Activity上添加这个注解:</pre>
@Router("user/collection")public class UserCollectionActivity extends DumpExtrasActivity {
这个注解相当于为这个Activity在路由器表中添加了user/collection这个域名映射记录,接下来在想要跳转的地方加上这么一句话:
Routers.open(context, "router://user/collection")
只是跳转到该Activity,该Activity结束的时候不需要传值给上一个Activity
Routers.openForResult(context,"router://user/collection" ,requestCode);
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">该Activity结束的时候需要传值给上一个Activity</pre>
用起来确实很简单,那么如果要传值的话,需要加上
Router(value = {"main", "home"}, longParams = {"id", "updateTime"}, booleanParams = "web")
这里声明了域名是main或者home,定义了三个参数long类型:id,updateTime,boolean类型:web,那么再调用这个Activity的时候,
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">完整的url为router://main?id=1103&updateTime=537896&web=true,是不是类似于get方式传值,其它的方式小伙伴们请看作者的介绍</pre>
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">ActivityRouter介绍 </pre>
<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: 宋体; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">好,继续看跳转的流程,当调用open或者openForResult的方法时都会走到下面这个方法 </pre>
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; }
这里有一个回调函数,用户在自己定义的时候可以监听跳转之前和跳转之后(可以做一些事情,比如拦截、打印等等,自定义默认跳转等等),如下:
public interface RouterCallback { void notFound(Context context, Uri uri); boolean beforeOpen(Context context, Uri uri); void afterOpen(Context context, Uri uri); void error(Context context, Uri uri, Throwable e);
接下来进入下面这个方法实现真正的跳转,
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; }
可以看到最终还是通过startActivity或StartActivityForResult实现跳转,但是在此之前需要从路由表中查询是否能找到用跳转的Activity的路由信息,找到的话将参数都解析出来,用intent进行传值,这个方法的第一行有initIfNeed()这个方法,这个方法是用来调用apt产生的java对象的的方法的,也就是说将有注解标记的java类收集起来注册进路由表中。
先来看一看这个框架是怎么将注解信息注册到路由表中的?
private static void initIfNeed() { if (!mappings.isEmpty()) { return; } RouterInit.init(); sort(); }
这里的路由表就是一个private staticList<Mapping> mappings= newArrayList<>()(List集合),如果这个集合有元素的话,则表明已经注册了,否则这个调用RouterInit.init()j进行注册,RouterInit类是用apt生成的,如果在没有编译之前,这个引用肯定会报错,作者这里采用了取巧的方式,就是先写死两个类
public class RouterInit { public static void init() { }}
public final class RouterMapping { public static final void map() { }
起到骗过编译器检查的效果,正常的使用还没有被编译注解处理器生成的java类的时候是利用反射,这个欺骗反而增加了那么点速度,编译器完成之后,新生成的java类将覆盖掉那两个写死的java类(占坑java类),在前面的APT生成java类时已经提到过,最终生成的类最后是调用了这个框架已经存在的类方法,也就是Routers.map****的方法 来看一下这个方法,如下:
mappings.add(new Mapping(format, activity, method, extraTypes));
直接向表中装填mapping对象,这里注意一下第三个参数,如果Router注解标记的是类,那么第三个参数为null,如果标记的是方法,那么第三个参数为MethodInvoker引用,第二个参数为null。 下面是路由表信息对象Mapping的构造函数:
public Mapping(String format, Class<? extends Activity> activity, MethodInvoker method, ExtraTypes extraTypes) { if (format == null) { throw new NullPointerException("format can not be null"); } this.format = format; this.activity = activity; this.method = method; this.extraTypes = extraTypes; if (format.toLowerCase().startsWith("http://") || format.toLowerCase().startsWith("https://")) { this.formatPath = Path.create(Uri.parse(format)); } else { this.formatPath = Path.create(Uri.parse("helper://".concat(format))); } }
这个构造函数主要做的工作就是将域名解析为Path类,如果域名以http或https开头那表示是在配置文件中配置了下面这些东东
<action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:host="mzule.com" android:scheme="http" />
接下来看一下 Path.create的方法干了些什么?如下:
public static Path create(Uri uri) { //鍒涘缓helper Path path = new Path(uri.getScheme().concat("://")); //得到路径 String urlPath = uri.getPath(); if (urlPath == null) { urlPath = ""; } //截取掉最后一个/ if (urlPath.endsWith("/")) { urlPath = urlPath.substring(0, urlPath.length() - 1); } parse(path, uri.getHost() + urlPath); return path; } //有多少种路径可以找到它就用多少种path private static void parse(Path scheme, String s) { String[] components = s.split("/"); Path curPath = scheme; for (String component : components) { Path temp = new Path(component); curPath.next = temp; curPath = temp; } }
这个方法根据host和path将当前的path创建了一个链表,表头的path持有Scheme,然后依次链接host和path以"/"分开的字符串,打个比方,如果Uri是zp://www.zp.com/path/zp的话那么表头的Path为value为zp://,而后一个Path的value为www.zp.com,第三个Path的value为path,第四个Path的value为zp,也就是说总共产生了四个path的链表。
ok,将Activity的映射信息注册到路由表后,那么又回到doOpen方法,遍历路由表信息看看有没有匹配的Path有的话跳转,没有的话回调notFound方法,接下来假设要跳转的url为router://main?id=1103&updateTime=537896&web=true,这里会创建一个拥有两个Path的链表,然后遍历Mapping,调用下面这个方法看Path是否匹配
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; } }
这个方法也很简单,循环判断链表下面是否所有的Path的value都相等(这里要排除掉带:的参数),如果相等那就说明匹配到了,注意这里去掉表头的Scheme的比较
public static boolean match(final Path format, final Path link) { if (format == null || link == null) { return false; } if (format.length() != link.length()) { return false; } Path x = format; Path y = link; while (x != null) { if (!x.match(y)) { return false; } x = x.next; y = y.next; } return true; }
假设此时路由表中已经找到匹配的Activity的映射信息了,那么接下来就需要将Uri里面的传的参数截取出来,将参数传递转化为bundle传递,如下方法所示:
public Bundle parseExtras(Uri uri) { Bundle bundle = new Bundle(); // path segments // ignore scheme Path p = formatPath.next(); Path y = Path.create(uri).next(); while (p != null) { if (p.isArgument()) { put(bundle, p.argument(), y.value()); } p = p.next(); y = y.next(); } // parameter Set<String> names = UriCompact.getQueryParameterNames(uri); for (String name : names) { String value = uri.getQueryParameter(name); put(bundle, name, value); } return bundle; }
这里的router://main?id=1103&updateTime=537896&web=true参数为id=1103&updateTime=537896&web=true,三个参数,这里需要做的就是将这些字符串取出来,按参数的key名字和value截取出来分别存在集合中,如下所示:
public static Set<String> getQueryParameterNames(Uri uri) { String query = uri.getEncodedQuery(); if (query == null) { return Collections.emptySet(); } Set<String> names = new LinkedHashSet<String>(); int start = 0; do { int next = query.indexOf('&', start); int end = (next == -1) ? query.length() : next; int separator = query.indexOf('=', start); if (separator > end || separator == -1) { separator = end; } String name = query.substring(start, separator); names.add(Uri.decode(name)); // Move start to end of name. start = end + 1; } while (start < query.length()); return Collections.unmodifiableSet(names); }
最后一步就是将参数转化为不同的类型
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; } }
在APT中解析参数类型的时候,每一个mapping都有一个唯一的ExtraTypes类来储存不同的参数类型,以参数的名字标记之
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; }
其实这个方法就是直接根据参数的名字去ExtraTypes集合中去找有没有这个名字的存储,如果有这个名字的话,那么将类型提出出来,也就是已经确定类型了
private String[] intExtra; private String[] longExtra; private String[] booleanExtra; private String[] shortExtra; private String[] floatExtra; private String[] doubleExtra; private String[] byteExtra; private String[] charExtra;
ExtraTypes类总共有这么多集合,在编译期已经将参数名字保存到ExtraTypes中,最后通过新生成的java文件,调用RouterInit.init()方法将ExtraTypes和mapping绑定,从而达到在传参数时的动态转化。好了,在android的实现一个路由架构的原理基本就介绍完了,欢迎小伙伴点赞和留言。