ActivityRouter 分析

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!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值