组件化开发之路由器模块详解(ActivityRouter源码详解)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xiatiandefeiyu/article/details/79567253

    路由器的作用是什么?通俗的讲,路由器的作用就是一根网线满足多人上网的需求。而在开发中路由器模块的作用就是实现中转分发,也就是说将原来有关系的模块(有依赖的模块分开),产生一个中间的模块,让原来依赖的两个模块都去和路由模块交互,从而将原来两个有关系的模块拆分开,利如我现在在开发一个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技术)。


看了这么多,是不是有点累了,先欣赏下美女休息一下



好了,进入正题,这里我们来一起分析一下ActivityRouter源码,参观一下别人是怎么实现的,ActivityRouter源码 至于为什么要通过这个框架分析,因为它虽然有点缺点,但是它小巧并且已经能将路由框架的原理思想大体的表现出来。


   前面提到编译时期的注解,那么先从这个框架的apt部分开始说起,如果想在android studio中实现apt功能只需要在app下build.gradle配置文件中加入

compile 'com.google.auto.service:auto-service:1.0-rc3'

然后在你的编译处理类上加入这个注解

@AutoService(Processor.class)
public class RouterProcessor extends AbstractProcessor 
 
 
先来看下一下实现这个编译处理类需要实现哪些方法:
 
 
 
 
public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        messager = processingEnv.getMessager();
        filer = processingEnv.getFiler();
    }

初始化方法,在这里你需要获得你需要使用的工具类,例如Messager(日志相关的辅助类),Filter(文件相关的辅助类(用它辅助生成新的java文件))
具体的其它辅助类,小伙伴们请查阅相关文档。
 
 
 
 
 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处理注解需要处理,也就是说这个框架只声明了这
三种注解
 
 
@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版本

最重要的是实现下面这个方法,只要捕捉到你设置的注解最终就会回调这个方法供你生成java文件,如下:
 
 
 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文件的框架。
javaPoet
这个方法的意思就是创建包名为com.github.mzule.activityrouter.router的类,并在这个类中创建静态init方法,并在init方法中调用
RouterMapping_module(注解module的名字被添加注解的activity)类的map方法,当然RouterMapping_module类也是动态生成的,来看一下
它生成的代码
 
 
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方法,将映射存到路由表中

讨论到这里,大体的可以看出这个框架利用apt技术动态的生成两个java类,这两个java类的主要作用就是将注解交给路由器的
解析类解析映射关系,最终将,映射关系存到路由器缓存中。只要在app启动时调用这两个java类,就可以将Activity和ip的映射
关系保存到路由器表中,在路由器中转中,就可以找到合适的模块的主Activity进行分发跳转了。

  现在来简单的运用这个框架,如果想把某个模块的主Activity加入到路由表中,直接在这个Activity上添加这个注解:
 
 
@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);
该Activity结束的时候需要传值给上一个Activity

用起来确实很简单,那么如果要传值的话,需要加上

Router(value = {"main", "home"},
        longParams = {"id", "updateTime"},
        booleanParams = "web")

这里声明了域名是main或者home,定义了三个参数long类型:id,updateTime,boolean类型:web,那么再调用这个Activity的时候,
完整的url为router://main?id=1103&updateTime=537896&web=true,是不是类似于get方式传值,其它的方式小伙伴们请看作者的介绍
ActivityRouter介绍

好,继续看跳转的流程,当调用open或者openForResult的方法时都会走到下面这个方法

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



猜你喜欢

转载自blog.csdn.net/xiatiandefeiyu/article/details/79567253