2019-06-30

作者: 皓皓amous | 来源:发表于2019-06-30 22:21 被阅读0次

    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的实现一个路由架构的原理基本就介绍完了,欢迎小伙伴点赞和留言。

    相关文章

      网友评论

        本文标题:2019-06-30

        本文链接:https://www.haomeiwen.com/subject/hnjicctx.html