组件是如何通信的?揭秘ARouter路由机制

上集回顾

上一篇文章解说了模块化以及组件,插件化的概念。模块化是一种解决项目分层的思想,组件化和插件化分别是其不同的实现。其中组件化以Module的形式拆分一个项目,在调试时独立为组件, 在集成打包时则又承担了库的角色,可谓一会儿排成人字, 一会儿排成一字。插件化则以apk插件的形式划分模块,在最终打包时动态集成到一起。

除了说到三种概念以及对比, 还提前说了一下组件化的核心思路,以及配置实现。那么接下来本篇内容说什么呢?

1. 总结一下组件的配置(前篇回顾,亦是本篇需要)
2. 带着ARouter揭秘组件之间的通讯

这里先总结也是简单提一下前一篇的组件化流程,不作深入剖析。如果还是不了解或不清楚,回去再看一下
模块化,组件化傻傻分不清?附带组件化福利

Case1: 通过config.gradle实现多组件实现统一配置

项目大了组件必然划分极多,不然也用不到组件化的程度。不同的组件gradle如果单独配置,很难做到统一,并且会出现很多奇怪难解的问题;为了保证配置的统一性,采用单独的统一配置文件是势在必得,也即config.gradle

Case2: 组件角色变换带来的配置变更

角色变换必然意味着配置的更新。在Library模式下, Manifest.xml 注册文件, Application应用类, applicationId是不需要的。而在application模式下, 这些又都是需要的。因为我们就知道了两种模式切换下,需要配置哪些东西。我们必然要根据isModule变量来分别配置gralde;以及通过一个单独的module(名字随意)文件夹存放application模式下的Manifest.xml以及Application类

Case3: 资源的冲突

资源的冲突是没有完全的解决办法的,根据不同的业务每个公司的方式都不同。我们只能提供一些方法极力避免,但是不能根除,这部分就是开发中需要注意的。方法1: 在module中配置资源前缀限制(只能限制xml文件,而不能作用于图片)方法2:多组件公共的图片采用统一的module来管理

Case4: Application分发冲突

不同的组件,除了一些需要在Application中公共初始化的东西,还有一些自己Module需要的部分。那必然需要一种方案,来解决这个问题。这种情况想必都不陌生,可以采用公共的Application继承的方式解决。每一个模块有两个角色,因此在开发时,不建议使用Application来获取Context。

Case5: 组件化开发,不建议使用ButterKnife

从这里开始,切入我们的正题了。
组件之间通信不像是单任务模式,使用EventBus就可以达到很好的效果。那么组件化之间,是通过什么方式通信呢?

隐式跳转 ? 嗯,会抢答了。但是有没有想过一个问题, 隐式跳转的局限性在哪 ? 每个组件(这里指四大组件)都需要添加隐式注册,而且很多时候却还是无能为力。

路由?
可以哦? 组件化通信的知名框架主要有两个ActivitRouter, ARouter。 其中ARouter是出自Alibaba, 也比较成熟。那么今天通过ARouter来揭秘组件通信的原理。

先说路由的概念,关于移动端架构的思考与总结这篇中有关于路由的说明,这里简单提一下作为前戏。


路由

说起路由,不禁想到了路由器


image.png

路由器的组成我们看一下, 一个接入口, N(N > 1)个输出口。

路由的工作是从信号源接收信号,然后分发到不同的接入口;如果按无线也算的话,输出口有无数。因为可能有偷网的N部手机。

路由机制和路由器很相似,如果说不同,唯一的不同可能在于路由器只有一个信号源,而路由机制是多个信号源的。为什么呢?因为组件之间的通信是相互的交叉通信。A需要调用B, B则是信号源;B需要A,A又成了信号源。


先说一下路由器的两大机制

注册机制

无论是插入网线,还是连接无线,都是路由器的注册机制。只有注册了(有线或无线连接),才能找到你的设备,才能进一步与你通信。

分发机制

路由器的内部肯定要查找对应的设备,并执行信号分发的。

明白了路由器的原理,路由机制就简单多了。我觉得,路由机制和EventBus很相似。不信?来看一下
首先看一下EventBus

 // 发送
 EventBus.getDefault().post("消息");


 // 注册接收器
 EventBus.getDefault().register(this);

 @Subscribe
 public void onEvent() {
        //实现接收
 }

从以上EventBus的使用可以看出:

1.消息发送以直接方式

EventBus的发送消息是直接得到实例发送的,并不需要注册。和一般通话机制不同,一般通话首先双端建立稳定连接, 进而才能实现通信。EventBus是将消息直接扔给路由, 路由去查找通信的另一端,然后传递消息。

2.消息接收先注册

试想一下上述消息的传递过程, 路由器得到消息,然后查找消息的接收方,找到之后将消息传递给接收方。
消息的接收经历先查找再分发的过程。那么通过什么查找呢?就是路由表,也就是相当于通讯录。因此消息的接收,注册是必然的。EventBus也是先要注册接收器,Router也是。

3.路由分发关系构建成表

如果你看过EventBus, 那么你肯定知道EventBus的核心就是保存有注册信息的Map。这个Map持有所有的注册的类,方法等。Router也需要一张表,它是由总表,组表多级表构成的,下面会说。

从以上分析出EventBus原理,其实EventBus就是一个小型的路由机制。它作用于一个Module, 负责同Module的通信,但是跨Module, 它无能为力。

与EventBus相似,ARouter机制就是一个跨Module的路由机制。


从ARouter的用法透析组件通信原理
// 需要接收消息的组件,要注册Router注解
@Route(group = "app", path = "/app/Component")
public class ComponentActivity extends AppCompatActivity {

// 通过Router直接发送事件

// 构造参数
Bundle bundle = new Bundle();
        bundle.putString("data", "data from other module");
        // 发送事件
        ARouter.getInstance()
                .build("/app/Component") // 跳转页面路由地址
                .with(bundle) // 传参
                .navigation();

以上是ARouter的用法。发送时,通过ARouter实例直接发送;接收时,先注册(@Router注解)。再结合EventBus,是不是明白了呢? ARouter是以路由地址来替代Activity实现进一步解耦的


明白了ARouter的通信流程,我们进一步看一下源码。

#######1. ARouter源码分为4部分:


image.png
arouter-annotation
image.png

annotation模块定义了常用的注解以及辅助解析的实体类


image.png

arouter-compiler
image.png

Processor这个关键字都不陌生吧!AnnotationProcessor是Google替代Apt用于注解解析的工具。从上图可以看出, ARouter分别定义了RouteProcessor, AutowiredProcessor, InterceptorProcessor分别解析路由机制最重要的三个注解,分别用于路由定义, 参数传值以及拦截器。


arouter-api
image.png

这个module毋庸置疑,就是给我使用的api的模块,包含核心类ARouter以及核心调用类_ARouter。


通过这些module划分,我们就可以猜到ARouter的实现思路是什么?如果明白了这个思路,我们可以自己定义我们自己的Router。

下面来分析思路

1. 添加注解@Route / @AutoWired
2.在RouteProcessor / AutoWiredProcessor中解析注解。 在process()中可以得到
添加@Route注解的所有类
@Route的注解参数
3.得到注解关联的信息后,动态生成路由表

先看一下@Route的源码

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {

    /**
     * Path of route
     */
    String path();

    /**
     * Used to merger routes, the group name MUST BE USE THE COMMON WORDS !!!
     */
    String group() default "";

    /**
     * Name of route, used to generate javadoc.
     */
    String name() default "undefined";

    /**
     * Extra data, can be set by user.
     * Ps. U should use the integer num sign the switch, by bits. 10001010101010
     */
    int extras() default Integer.MIN_VALUE;

    /**
     * The priority of route.
     */
    int priority() default -1;
}

从源码可以看出, 重要的两个参数:
path: 定义了类的注解地址
group: 定义了类的注解的分组名称

@Route(group = "app", path = "/app/Component")

以上为我的使用, group定义了分组为‘app’; route定义的路由地址一般格式为:'组名/路由名称'。加入分组的概念可以大幅度提高查找的效率。

上面说到了生成路由表,不信? 来一波源码,源码来自RouteProcessor中的process(); 这个方法是解析注解的核心方法

 /**
     * {@inheritDoc}
     *
     * @param annotations
     * @param roundEnv
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (CollectionUtils.isNotEmpty(annotations)) {
            Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
            try {
                logger.info(">>> Found routes, start... <<<");
                this.parseRoutes(routeElements);

            } catch (Exception e) {
                logger.error(e);
            }
            return true;
        }

        return false;
    }

    private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
        if (CollectionUtils.isNotEmpty(routeElements)) {
            // Perpare the type an so on.

            logger.info(">>> Found routes, size is " + routeElements.size() + " <<<");

            rootMap.clear();

            TypeMirror type_Activity = elements.getTypeElement(ACTIVITY).asType();
            TypeMirror type_Service = elements.getTypeElement(SERVICE).asType();
            TypeMirror fragmentTm = elements.getTypeElement(FRAGMENT).asType();
            TypeMirror fragmentTmV4 = elements.getTypeElement(Consts.FRAGMENT_V4).asType();

            // Interface of ARouter
            TypeElement type_IRouteGroup = elements.getTypeElement(IROUTE_GROUP);
            TypeElement type_IProviderGroup = elements.getTypeElement(IPROVIDER_GROUP);
            ClassName routeMetaCn = ClassName.get(RouteMeta.class);
            ClassName routeTypeCn = ClassName.get(RouteType.class);

            /*
               Build input type, format as :

               ```Map<String, Class<? extends IRouteGroup>>```
             */
            ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get(
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ParameterizedTypeName.get(
                            ClassName.get(Class.class),
                            WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup))
                    )
            );

            /*

              ```Map<String, RouteMeta>```
             */
            ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ClassName.get(RouteMeta.class)
            );

            /*
              Build input param name.
             */
            ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();
            ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
            ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build();  // Ps. its param type same as groupParamSpec!

            /*
              Build method : 'loadInto'
             */
            MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(rootParamSpec);

            //  Follow a sequence, find out metas of group first, generate java file, then statistics them as root.
            for (Element element : routeElements) {
                TypeMirror tm = element.asType();
                Route route = element.getAnnotation(Route.class);
                RouteMeta routeMeta = null;

                if (types.isSubtype(tm, type_Activity)) {                 // Activity
                    logger.info(">>> Found activity route: " + tm.toString() + " <<<");

                    // Get all fields annotation by @Autowired
                    Map<String, Integer> paramsType = new HashMap<>();
                    for (Element field : element.getEnclosedElements()) {
                        if (field.getKind().isField() && field.getAnnotation(Autowired.class) != null && !types.isSubtype(field.asType(), iProvider)) {
                            // It must be field, then it has annotation, but it not be provider.
                            Autowired paramConfig = field.getAnnotation(Autowired.class);
                            paramsType.put(StringUtils.isEmpty(paramConfig.name()) ? field.getSimpleName().toString() : paramConfig.name(), typeUtils.typeExchange(field));
                        }
                    }
                    routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
                } else if (types.isSubtype(tm, iProvider)) {         // IProvider
                    logger.info(">>> Found provider route: " + tm.toString() + " <<<");
                    routeMeta = new RouteMeta(route, element, RouteType.PROVIDER, null);
                } else if (types.isSubtype(tm, type_Service)) {           // Service
                    logger.info(">>> Found service route: " + tm.toString() + " <<<");
                    routeMeta = new RouteMeta(route, element, RouteType.parse(SERVICE), null);
                } else if (types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
                    logger.info(">>> Found fragment route: " + tm.toString() + " <<<");
                    routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), null);
                } else {
                    throw new RuntimeException("ARouter::Compiler >>> Found unsupported class type, type = [" + types.toString() + "].");
                }

                categories(routeMeta);
                // if (StringUtils.isEmpty(moduleName)) {   // Hasn't generate the module name.
                //     moduleName = ModuleUtils.generateModuleName(element, logger);
                // }
            }

            MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(providerParamSpec);

            // Start generate java source, structure is divided into upper and lower levels, used for demand initialization.
            for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
                String groupName = entry.getKey();

                MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                        .addAnnotation(Override.class)
                        .addModifiers(PUBLIC)
                        .addParameter(groupParamSpec);

                // Build group method body
                Set<RouteMeta> groupData = entry.getValue();
                for (RouteMeta routeMeta : groupData) {
                    switch (routeMeta.getType()) {
                        case PROVIDER:  // Need cache provider's super class
                            List<? extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces();
                            for (TypeMirror tm : interfaces) {
                                if (types.isSameType(tm, iProvider)) {   // Its implements iProvider interface himself.
                                    // This interface extend the IProvider, so it can be used for mark provider
                                    loadIntoMethodOfProviderBuilder.addStatement(
                                            "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                            (routeMeta.getRawType()).toString(),
                                            routeMetaCn,
                                            routeTypeCn,
                                            ClassName.get((TypeElement) routeMeta.getRawType()),
                                            routeMeta.getPath(),
                                            routeMeta.getGroup());
                                } else if (types.isSubtype(tm, iProvider)) {
                                    // This interface extend the IProvider, so it can be used for mark provider
                                    loadIntoMethodOfProviderBuilder.addStatement(
                                            "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                            tm.toString(),    // So stupid, will duplicate only save class name.
                                            routeMetaCn,
                                            routeTypeCn,
                                            ClassName.get((TypeElement) routeMeta.getRawType()),
                                            routeMeta.getPath(),
                                            routeMeta.getGroup());
                                }
                            }
                            break;
                        default:
                            break;
                    }

                    // Make map body for paramsType
                    StringBuilder mapBodyBuilder = new StringBuilder();
                    Map<String, Integer> paramsType = routeMeta.getParamsType();
                    if (MapUtils.isNotEmpty(paramsType)) {
                        for (Map.Entry<String, Integer> types : paramsType.entrySet()) {
                            mapBodyBuilder.append("put(\"").append(types.getKey()).append("\", ").append(types.getValue()).append("); ");
                        }
                    }
                    String mapBody = mapBodyBuilder.toString();

                    loadIntoMethodOfGroupBuilder.addStatement(
                            "atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap<String, Integer>(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                            routeMeta.getPath(),
                            routeMetaCn,
                            routeTypeCn,
                            ClassName.get((TypeElement) routeMeta.getRawType()),
                            routeMeta.getPath().toLowerCase(),
                            routeMeta.getGroup().toLowerCase());
                }

                // Generate groups
                String groupFileName = NAME_OF_GROUP + groupName;
                JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                        TypeSpec.classBuilder(groupFileName)
                                .addJavadoc(WARNING_TIPS)
                                .addSuperinterface(ClassName.get(type_IRouteGroup))
                                .addModifiers(PUBLIC)
                                .addMethod(loadIntoMethodOfGroupBuilder.build())
                                .build()
                ).build().writeTo(mFiler);

                logger.info(">>> Generated group: " + groupName + "<<<");
                rootMap.put(groupName, groupFileName);
            }

            if (MapUtils.isNotEmpty(rootMap)) {
                // Generate root meta by group name, it must be generated before root, then I can find out the class of group.
                for (Map.Entry<String, String> entry : rootMap.entrySet()) {
                    loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue()));
                }
            }

            // Wirte provider into disk
            String providerMapFileName = NAME_OF_PROVIDER + SEPARATOR + moduleName;
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(providerMapFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(type_IProviderGroup))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfProviderBuilder.build())
                            .build()
            ).build().writeTo(mFiler);

            logger.info(">>> Generated provider map, name is " + providerMapFileName + " <<<");

            // Write root meta into disk.
            String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(rootFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(elements.getTypeElement(ITROUTE_ROOT)))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfRootBuilder.build())
                            .build()
            ).build().writeTo(mFiler);

            logger.info(">>> Generated root, name is " + rootFileName + " <<<");
        }
    }

方法内容很多,但是主要做了两件事:
1) 解析注解类的注解参数与类参数,如组名, 注解地址, 注解类
2) 自动生成总表与组表。 总表存放组的信息, 组表存放组对应的路由表

如果你要追根溯源,那么给你一条黄河。自动生成类的条件是什么 ? 1) 你肯定要加@Route注解啊 2)你肯定要编译啊,因为Apt是编译时的注解解析技术

以下是注解生成的表,生成目录在


image.png

image.png

看alibaba包下的类,这些类为路由表相关的类

ARouter$$Provider$$app暂且不看,这是Provider生成的默认表
ARouter$$Root$$app 这是总表,存放的组的关系,我们看一下
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("app", ARouter$$Group$$app.class);
  }
}

看到了吧, Root表生成的代码, 表的存放形式是Map, 表的key是分组名称, value是ARouter$$Group$$app。ARouter是框架名, Group代表着分组, app代表组名,这就是类名的拼接形式。

ARouter$$Group$$app这是组路由表,存放的是该组(组通过类名区分,ARouter$$Group$$组)的路由表, 我们看一下
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$app implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put(
            "/app/Component",
            RouteMeta.build(
                    RouteType.ACTIVITY,
                    ComponentActivity.class,
                    "/app/component",
                    "app",
                    new java.util.HashMap<String, Integer>(){
                      {put("from", 8);
                      put("id", 3); }},
                    -1,
                    -2147483648));
  }
}

以上是路由表, 同样是一个Map, 表的key是路由地址/app/Component,表的value是一个Meta,包含路由类型ACTIVITY, 类名ComponentActivity.class, 路由地址, 组名‘app’, 该类下添加参数注解的参数from, id,以及优先级等。这是一个完整的Meta类,不信看一下Meta的源码

/**
 * It contains basic route information.
 *
 * @author Alex <a href="mailto:zhilong.liu@aliyun.com">Contact me.</a>
 * @version 1.0
 * @since 16/8/24 09:45
 */
public class RouteMeta {
    private RouteType type;         // Type of route
    private Element rawType;        // Raw type of route
    private Class<?> destination;   // Destination
    private String path;            // Path of route
    private String group;           // Group of route
    private int priority = -1;      // The smaller the number, the higher the priority
    private int extra;              // Extra data
    private Map<String, Integer> paramsType;  // Param type

    public RouteMeta() {
    }

    /**
     * For versions of 'compiler' less than 1.0.7, contain 1.0.7
     *
     * @param type        type
     * @param destination destination
     * @param path        path
     * @param group       group
     * @param priority    priority
     * @param extra       extra
     * @return this
     */
    public static RouteMeta build(RouteType type, Class<?> destination, String path, String group, int priority, int extra) {
        return new RouteMeta(type, null, destination, path, group, null, priority, extra);
    }

    /**
     * For versions of 'compiler' greater than 1.0.7
     *
     * @param type        type
     * @param destination destination
     * @param path        path
     * @param group       group
     * @param paramsType  paramsType
     * @param priority    priority
     * @param extra       extra
     * @return this
     */
    public static RouteMeta build(RouteType type, Class<?> destination, String path, String group, Map<String, Integer> paramsType, int priority, int extra) {
        return new RouteMeta(type, null, destination, path, group, paramsType, priority, extra);
    }

    /**
     * Type
     *
     * @param route       route
     * @param destination destination
     * @param type        type
     */
    public RouteMeta(Route route, Class<?> destination, RouteType type) {
        this(type, null, destination, route.path(), route.group(), null, route.priority(), route.extras());
    }

    /**
     * Type
     *
     * @param route      route
     * @param rawType    rawType
     * @param type       type
     * @param paramsType paramsType
     */
    public RouteMeta(Route route, Element rawType, RouteType type, Map<String, Integer> paramsType) {
        this(type, rawType, null, route.path(), route.group(), paramsType, route.priority(), route.extras());
    }

    /**
     * Type
     *
     * @param type        type
     * @param rawType     rawType
     * @param destination destination
     * @param path        path
     * @param group       group
     * @param paramsType  paramsType
     * @param priority    priority
     * @param extra       extra
     */
    public RouteMeta(RouteType type, Element rawType, Class<?> destination, String path, String group, Map<String, Integer> paramsType, int priority, int extra) {
        this.type = type;
        this.destination = destination;
        this.rawType = rawType;
        this.path = path;
        this.group = group;
        this.paramsType = paramsType;
        this.priority = priority;
        this.extra = extra;
    }

    public Map<String, Integer> getParamsType() {
        return paramsType;
    }

    public RouteMeta setParamsType(Map<String, Integer> paramsType) {
        this.paramsType = paramsType;
        return this;
    }

    public Element getRawType() {
        return rawType;
    }

    public RouteMeta setRawType(Element rawType) {
        this.rawType = rawType;
        return this;
    }

    public RouteType getType() {
        return type;
    }

    public RouteMeta setType(RouteType type) {
        this.type = type;
        return this;
    }

    public Class<?> getDestination() {
        return destination;
    }

    public RouteMeta setDestination(Class<?> destination) {
        this.destination = destination;
        return this;
    }

    public String getPath() {
        return path;
    }

    public RouteMeta setPath(String path) {
        this.path = path;
        return this;
    }

    public String getGroup() {
        return group;
    }

    public RouteMeta setGroup(String group) {
        this.group = group;
        return this;
    }

    public int getPriority() {
        return priority;
    }

    public RouteMeta setPriority(int priority) {
        this.priority = priority;
        return this;
    }

    public int getExtra() {
        return extra;
    }

    public RouteMeta setExtra(int extra) {
        this.extra = extra;
        return this;
    }

    @Override
    public String toString() {
        return "RouteMeta{" +
                "type=" + type +
                ", rawType=" + rawType +
                ", destination=" + destination +
                ", path='" + path + '\'' +
                ", group='" + group + '\'' +
                ", priority=" + priority +
                ", extra=" + extra +
                '}';
    }
}
看了以上生成的类, 实体类与分析,应该可以理解了:Router根据注解动态生成了一个对应关系表。通过这个表,就可以根据路由地址查找到对应的Meta,得到要跳转的类, 并添加跳转参数。其实跳转还是原来的跳转,还是startActivity还是Bundle与Intent。只不过是在router模块里实现的统一的跳转与传参。

接下来看我们的api。我们在使用ARouter时,首先肯定是要初始化

ARouter.init(this);

那么初始化执行了什么呢? 我先偷偷告诉你,再去看真相... init的主要任务只有一个,就是导入路由表。先看一下使用流程

首先,我们添加了@Route注解
其次, 我们要编译项目吧,这时候执行RouteProcessor, 解析并生成路由表了
然后,才是我们的init, 这时候路由表已经生成了
再然后,就可以光明正大的把路由表导入了

我们来看init的源码

/**
     * Init, it must be call before used router.
     */
    public static void init(Application application) {
        if (!hasInit) {
            logger = _ARouter.logger;
            _ARouter.logger.info(Consts.TAG, "ARouter init start.");
            hasInit = _ARouter.init(application);

            if (hasInit) {
                _ARouter.afterInit();
            }

            _ARouter.logger.info(Consts.TAG, "ARouter init over.");
        }
    }

调用了_ARouter的init方法,为什么呢? 你也不想把你的逻辑暴漏给开发者不是?这叫隐藏细节。追踪到_ARouter的init

 protected static synchronized boolean init(Application application) {
        mContext = application;
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;

        // It's not a good idea.
        // if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
        //     application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
        // }
        return true;
    }

又跑到了LogisticsCenter,这个类是做什么呢?我来翻译下

LogisticsCenter contain all of the map
所有表的逻辑处理中心

一说到表,相信都猜到了,是的,给你一条黄河再,看源码

 /**
     * LogisticsCenter init, load all metas in memory. Demand initialization
     */
    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;

        try {
            long startInit = System.currentTimeMillis();
            //billy.qi modified at 2017-12-06
            //load by plugin first
            loadRouterMap();
            if (registerByPlugin) {
                logger.info(TAG, "Load router map by arouter-auto-register plugin.");
            } else {
                Set<String> routerMap;

                // It will rebuild router map every times when debuggable.
                if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                    logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                    // These class was generated by arouter-compiler.
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                    if (!routerMap.isEmpty()) {
                        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                    }

                    PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
                } else {
                    logger.info(TAG, "Load router map from cache.");
                    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                }

                logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
                startInit = System.currentTimeMillis();

                for (String className : routerMap) {
                    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                        // This one of root elements, load root.
                        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                        // Load interceptorMeta
                        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                        // Load providerIndex
                        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                    }
                }
            }

            logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");

            if (Warehouse.groupsIndex.size() == 0) {
                logger.error(TAG, "No mapping files were found, check your configuration please!");
            }

            if (ARouter.debuggable()) {
                logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
            }
        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }

看注释就知道了, load all metas in memory 导入内存中所有的Meta,就是路由表了。


接下来分析第三部分, 发送条件事件是怎么处理呢?先看调用

  ARouter.getInstance()
                .build("/app/Component")
                .with(bundle)
                .navigation();

直接看最关键的执行方法吧, navigation最终是走了_ARouter的navigation。上面说了,ARouter是个空壳子,用于隐藏内部细节,真正的实现在_ARouter中。看一下源码

/**
     * Use router navigation.
     *
     * @param context     Activity or null.
     * @param postcard    Route metas
     * @param requestCode RequestCode
     * @param callback    cb
     */
    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        try {
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());

            if (debuggable()) { // Show friendly tips for user.
                Toast.makeText(mContext, "There's no route matched!\n" +
                        " Path = [" + postcard.getPath() + "]\n" +
                        " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
            }

            if (null != callback) {
                callback.onLost(postcard);
            } else {    // No callback for this invoke, then we use the global degrade service.
                DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
                if (null != degradeService) {
                    degradeService.onLost(context, postcard);
                }
            }

            return null;
        }

        if (null != callback) {
            callback.onFound(postcard);
        }

        if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /**
                 * Continue process
                 *
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, postcard, requestCode, callback);
                }

                /**
                 * Interrupt process, pipeline will be destory when this method called.
                 *
                 * @param exception Reson of interrupt.
                 */
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }

                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {
            return _navigation(context, postcard, requestCode, callback);
        }

        return null;
    }

跳过乱七八糟的处理,直接看两个地方。其一是对拦截器的处理,无非是在最终之前先把拦截器的处理执行了。

 if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /**
                 * Continue process
                 *
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, postcard, requestCode, callback);
                }

                /**
                 * Interrupt process, pipeline will be destory when this method called.
                 *
                 * @param exception Reson of interrupt.
                 */
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }

                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {
            return _navigation(context, postcard, requestCode, callback);
        }

看一下拦截器的实现,先在onInterrupt走拦截的处理,然后在onContinue中走正常的流程,其实拦截器的实现很简单,虽然从名字看很唬人。继续看执行的源码

private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = null == context ? mContext : context;

        switch (postcard.getType()) {
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());

                // Set flags.
                int flags = postcard.getFlags();
                if (-1 != flags) {
                    intent.setFlags(flags);
                } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

                // Navigation in main looper.
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        if (requestCode > 0) {  // Need start for result
                            ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
                        } else {
                            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
                        }

                        if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
                            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
                        }

                        if (null != callback) { // Navigation over.
                            callback.onArrival(postcard);
                        }
                    }
                });

                break;
            case PROVIDER:
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
                Class fragmentMeta = postcard.getDestination();
                try {
                    Object instance = fragmentMeta.getConstructor().newInstance();
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }

                    return instance;
                } catch (Exception ex) {
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }

        return null;
    }

看源码很清晰, 主干就是一个switch,因为@Route注解的对象有很多,Activity, Fragment, BroadCast,Provider,Service等等。直接看ACTIVITY,因为其他组件的实现都是相似的。

  final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());

                // Set flags.
                int flags = postcard.getFlags();
                if (-1 != flags) {
                    intent.setFlags(flags);
                } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

创建了一个Intent, 然后把参数拼上, 是不是很熟悉,难吗?不难,难的是中间的处理。如果是我们自己写,去掉那些复杂的考虑,实现一个简易的路由框架,其实是可以的。

 // Navigation in main looper.
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        if (requestCode > 0) {  // Need start for result
                            ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
                        } else {
                            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
                        }

                        if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
                            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
                        }

                        if (null != callback) { // Navigation over.
                            callback.onArrival(postcard);
                        }
                    }
                });

看一下以上的代码,就是执行跳转的源码,是不是也简单 ? 解析一下

首先,跳转的页面怎么得到?

不知道你还记不记得,在解析@Route注解的时候,我们就得到了ComponentActivity.class。参数也是我们在调用的时候就传入的, .with(bundle) 对就是它。

其次,Context是怎么得到的?

Context取自init。 因为在ARouter.init()时,参数是一个application。

最后,我们肯定要在主线程里执行啊
new Handler(Looper.getMainLooper()).post(new Runnable() {

解决了以上三个难题,让你写跳转,你也会,不是么?


从以上三部分的分析,你还觉得ARouter难吗?那么就来总结一下

先来看一下页面的跳转实现,都会的

   Intent i = new Intent(mContext, ComponentActivity.class);
        i.putExtra("p", "parameter");
        mContext.startActivity(i);

在非本模块的非Activity中实现跳转,难度无非就是:

1) Context怎么得到 ? 我们通过init()解决了
2) ComponentActivity.class怎么得到? 我们通过@Route注解得到了
3)参数怎么办 ? 更简单, 我们直接调用api传的
4)怎么模拟主线程? new Hanlder(Looper.mainLooper()) 这个主线程,就是最后我们集成后,Module 'app'的主线程

解决了以上4个问题,是不是简单了? 与EventBus是不是很像呢?

学会这个框架,需要我们掌握什么:
1. 反射 (在单独的module下,通过反射才能得到一些属性。这里我们的Activity是通过反射得到的)
2. Apt 编译时解析注解
3. startActivity需要的条件, 4个条件让你找,你肯定找不全
4. 还有4吗?额忽然忘了
接下来,我们梳理一下:

1、 车祸的起源来自于@Route
2、 分析车祸现场,Processor解析下车祸的相关信息
3、 init给了我们身份证

  1. 掉Api, 我们浪起来啊
  2. 模拟跳转的条件,在国外跳转

其实, Fragment或其他组件与ACTIVITY差异不是很大。但是这里说一下参数的自动传值,也就是动态生成的我们包下的类


image.png

以下是源码

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ComponentActivity$$ARouter$$Autowired implements ISyringe {
  private SerializationService serializationService;

  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);;
    ComponentActivity substitute = (ComponentActivity)target;
    substitute.from = substitute.getIntent().getStringExtra("from");
    substitute.id = substitute.getIntent().getIntExtra("id", 0);
  }
}

一看是不是明白了,这不就是依赖注入吗? 好吧,分析一下
inject()就是注入的方法。方法的参数target就是Activity的对象,看源码就知道了。上面说过,传进来Activity.class后,通过反射,我们可以轻易得到Activity的对象,这里的对象来源就清楚了。再看实现,不就是getIntent().getXXExtra()吗?别说这个你不会。这就是依赖注入,在这里给跳转的Activity的参数赋值。


image.png

所以,别看我加了个注解就有值了,就觉得,咦?好神奇,那我只能遗憾的告诉你,哎吆,我是偷偷在别的地方赋值了啦!!!


以上就是对ARouter路由机制的全解析,怎么样 ? 你还觉得神秘吗? 如果让你写,我觉得应该可以了吧?

还是那句话,如果你觉得本篇文章对你有帮助,请为我打Call!!! 还有更多精彩干货让你收获满满哦

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,815评论 25 707
  • 组件化 模块化、组件化与插件化 在项目发展到一定程度,随着人员的增多,代码越来越臃肿,这时候就必须进行模块化的拆分...
    silentleaf阅读 4,945评论 2 12
  • 茫茫人海中, 相识人无数, 却不知情为何物。 漫漫征途上, 跋涉千万里, 却不懂何谓幸福。 网络大世界, 虚拟变主...
    曹焕甫阅读 295评论 0 0
  • 梦中的温柔即是梦魇 一种梦醒时破碎的痛 在她的怀里 我的眼角旁 流过的 那是现实 与永远无法得到的虚妄
    写废纸的熊阅读 219评论 10 5
  • 001、晨间日记的起源 作者佐藤传提倡的“晨间日记’’-每天早上用3分钟写日记,作为一项革命性习惯在日本引起热烈的...
    林子Amy阅读 144评论 0 0