标签: ARouter

ARouter原理剖析及手动实现

ARouter原理剖析及手动实现

 

前言

路由跳转在项目中用了一段时间了,*近对Android中的ARouter路由原理也是研究了一番,于是就给大家分享一下自己的心得体会,并教大家如何实现一款简易的路由框架。

本篇文章分为两个部分,*部分着重剖析ARouter路由的原理,第二部分会带着大家仿照ARouter撸一个自己的路由框架,我们自己撸的路由框架可能没有Arouter众多的功能如过滤器、provider等,但是却实现了ARouter*核心的功能:路由跳转,同时你也能学会如何去设计一个框架等等。

*部分:ARouter原理剖析

说到路由便不得不提一下Android中的组件化开发思想,组件化是*近比较流行的架构设计方案,它能对代码进行高度的解耦、模块分离等,能*大地提高开发效率(如有同学对组件化有不理解,可以参考网上众多的博客等介绍,然后再阅读demo源码中的组件化配置进行熟悉)。路由和组件化本身没有什么联系,因为路由的责任是负责页面跳转,但是组件化中两个单向依赖的module之间需要互相启动对方的Activity,因为没有相互引用,startActivity()是实现不了的,必须需要一个协定的通信方式,此时类似ARouter和ActivityRouter等的路由框架就派上用场了。

  • *节:ARouter路由跳转的原理

<img src=”http://pcayc3ynm.bkt.clouddn.com/module_1.png” />

如上图,在组件化中,为了业务逻辑的彻底解耦,同时也为了每个module都可以方便的单独运行和调试,上层的各个module不会进行相互依赖(只有在正式联调的时候才会让app壳module去依赖上层的其他组件module),而是共同依赖于base module,base module中会依赖一些公共的第三方库和其他配置。那么在上层的各个module中,如何进行通信呢?

我们知道,传统的Activity之间通信,通过startActivity(intent),而在组件化的项目中,上层的module没有依赖关系(即便两个module有依赖关系,也只能是单向的依赖),那么假如login module中的一个Activity需要启动pay_module中的一个Activity便不能通过startActivity来进行跳转。那么大家想一下还有什么其他办法呢? 可能有同学会想到隐式跳转,这当然也是一种解决方法,但是一个项目中不可能所有的跳转都是隐式的,这样Manifest文件会有很多过滤配置,而且非常不利于后期维护。当然你用反射拿到Activity的class文件也可以实现跳转,但是*:大量的使用反射跳转对性能会有影响,第二:你需要拿到Activity的类文件,在组件开发的时候,想拿到其他module的类文件是很麻烦的,因为组件开发的时候组件module之间是没有相互引用的,你只能通过找到类的路径去反射拿到这个class,那么有没有一种更好的解决办法呢?办法当然是有的。下面看图:

<img src=”http://pcayc3ynm.bkt.clouddn.com/module_2.png” />

在组件化中,我们通常都会在base_module上层再依赖一个router_module,而这个router_module就是负责各个模块之间页面跳转的。

用过ARouter路由框架的同学应该都知道,在每个需要对其他module提供调用的Activity中,都会声明类似下面@Route注解,我们称之为路由地址

@Route(path = "/main/main")
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}


@Route(path = "/module1/module1main")
public class Module1MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_module1_main);
    }
}

那么这个注解有什么用呢,路由框架会在项目的编译期通过注解处理器扫描所有添加@Route注解的Activity类,然后将Route注解中的path地址和Activity.class文件映射关系保存到它自己生成的java文件中。为了让大家理解,我这里来使用近乎伪代码给大家简单演示一下。

public class MyRouters{

    //项目编译后通过apt生成如下方法
    public static HashMap<String, ClassBean> getRouteInfo(HashMap<String, ClassBean> routes) {
        route.put("/main/main", MainActivity.class);
        route.put("/module1/module1main", Module1MainActivity.class);
        route.put("/login/login", LoginActivity.class);
    }
}

这样我们想在app模块的MainActivity跳转到login模块的LoginActivity,那么便只需调用如下:

//不同模块之间启动Activity
public void login(String name, String password) {
    HashMap<String, ClassBean> route = MyRouters.getRouteInfo(new HashMap<String, ClassBean>);
    LoginActivity.class classBean = route.get("/login/login");
    Intent intent = new Intent(this, classBean);
    intent.putExtra("name", name);
    intent.putExtra("password", password);
    startActivity(intent);
}

这样是不是很简单就实现了路由的跳转,既没有隐式意图的繁琐,也没有反射对性能的损耗。用过ARouter的同学应该知道,用ARouter启动Activity应该是下面这个写法

// 2. Jump with parameters
ARouter.getInstance().build("/test/login")
            .withString("password", 666666)
            .withString("name", "小三")
            .navigation();

那么ARouter背后是怎么样实现跳转的呢?实际上它的核心思想跟上面讲解是一样的,我们在代码里加入的@Route注解,会在编译时期通过apt生成一些存储path和activity.class映射关系的类文件,然后app进程启动的时候会加载这些类文件,把保存这些映射关系的数据读到内存里(保存在map里),然后在进行路由跳转的时候,通过build()方法传入要到达页面的路由地址,ARouter会通过它自己存储的路由表找到路由地址对应的Activity.class(activity.class = map.get(path)),然后new Intent(context, activity.Class),当调用ARouter的withString()方法它的内部会调用intent.putExtra(String name, String value),调用navigation()方法,它的内部会调用startActivity(intent)进行跳转,这样便可以实现两个相互没有依赖的module顺利的启动对方的Activity了。

  • 第二节:ARouter映射关系如何生成

通过上节我们知道在Activity类上加上@Route注解之后,便可通过apt生成对应的路由表。那么现在我们来搞清楚,既然路由和Activity的映射关系我们可以很容易地得到(因为代码都是我们写的,当然很容易得到),那么为什么我们要繁琐的通过apt来生成类文件而不是自己直接写一个契约类来保存映射关系呢。如果站在一个框架开发者的角度去理解,就不难明白了,因为框架是给上层业务开发者调用的,如果业务开发者在开发页面的过程中还要时不时的更新或更改契约类文件,不免过于麻烦?如果有自动根据路由地址生成映射表文件的技术该多好啊!

技术当然是有的,那就是被众多框架使用的apt及javapoet技术,那么什么是apt,什么是javapoet呢?我们先来看下图:

<img src=”http://pcayc3ynm.bkt.clouddn.com/apt_javapoet.png” />

APT是Annotation Processing Tool的简称,即注解处理工具。由图可知,apt是在编译期对代码中指定的注解进行解析,然后做一些其他处理(如通过javapoet生成新的Java文件)。我们常用的ButterKnife,其原理就是通过注解处理器在编译期扫描代码中加入的@BindView、@OnClick等注解进行扫描处理,然后生成XXX_ViewBinding类,实现了view的绑定。javapoet是鼎鼎大名的squareup出品的一个开源库,是用来生成java文件的一个library,它提供了简便的api供你去生成一个java文件。可以如下引入javapoet

implementation 'com.squareup:javapoet:1.7.0'

下面我通过demo中的例子带你了解如何通过apt和javapoet技术生成路由映射关系的类文件:

首先*步,定义注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Route {
    /**
     * 路由的路径
     * @return
     */
    String path();

    /**
     * 将路由节点进行分组,可以实现动态加载
     * @return
     */
    String group() default "";

}

这里看到Route注解里有path和group,这便是仿照ARouter对路由进行分组。因为当项目变得越来越庞大的时候,为了便于管理和减小首次加载路由表过于耗时的问题,我们对所有的路由进行分组。在ARouter中会要求路由地址至少需要两级,如”/xx/xx”,一个模块下可以有多个分组,这里我们就将路由地址定为必须大于等于两级,其中*级是group。

第二步,在Activity上使用注解

@Route(path = "/main/main")
public class MainActivity extends AppCompatActivity {

}

@Route(path = "/main/main2")
public class Main2Activity extends AppCompatActivity {

}

@Route(path = "/show/info")
public class ShowActivity extends AppCompatActivity {

}

第三步,编写注解处理器,在编译器找到加入注解的类文件,进行处理,这里我只展示关键代码,具体的细节还需要你去demo中仔细研读:

@AutoService(Processor.class)
/**
  处理器接收的参数 替代 {@link AbstractProcessor#getSupportedOptions()} 函数
 */
@SupportedOptions(Constant.ARGUMENTS_NAME)
/**
 * 注册给哪些注解的  替代 {@link AbstractProcessor#getSupportedAnnotationTypes()} 函数
 */
@SupportedAnnotationTypes(Constant.ANNOTATION_TYPE_ROUTE)

public class RouterProcessor extends AbstractProcessor {
    /**
     * key:组名 value:类名
     */
    private Map<String, String> rootMap = new TreeMap<>();
    /**
     * 分组 key:组名 value:对应组的路由信息
     */
    private Map<String, List<RouteMeta>> groupMap = new HashMap<>();

    /**
     *
     * @param set 使用了支持处理注解的节点集合
     * @param roundEnvironment 表示当前或是之前的运行环境,可以通过该对象查找找到的注解。
     * @return true 表示后续处理器不会再处理(已经处理)
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (!Utils.isEmpty(set)) {
            //被Route注解的节点集合
            Set<? extends Element> rootElements = roundEnvironment.getElementsAnnotatedWith(Route.class);
            if (!Utils.isEmpty(rootElements)) {
                processorRoute(rootElements);
            }
            return true;
        }
        return false;
    }


    //...

}

如代码中所示,要想在编译期对注解做处理,就需要RouterProcessor继承自AbstractProcessor并通过@AutoService注解进行注册,然后实现process()方法。还没有完,你还需要通过@SupportedAnnotationTypes(Constant.ANNOTATION_TYPE_ROUTE)指定要处理哪个注解,Constant.ANNOTATION_TYPE_ROUTE便是我们的Route注解的路径。看process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)方法,set集合就是编译期扫描代码得到的加入了Route注解的文件集合,然后我们就可以在process方法生成java文件了。

这里的@AutoService是为了注册注解处理器,需要我们引入一个google开源的自动注册工具AutoService,如下依赖(当然也可以手动进行注册,不过略微麻烦,这里不太推荐):

implementation 'com.google.auto.service:auto-service:1.0-rc2'

第四步:通过javapoet生成java类:
在第三步中process()方法里有一句代码:processorRoute(rootElements),这个就是生成java文件的方法了,下面我贴出代码:

private void processorRoute(Set<? extends Element> rootElements) {

    //...

    //生成Group记录分组表
    generatedGroup(iRouteGroup);

    //生成Root类 作用:记录<分组,对应的Group类>
    generatedRoot(iRouteRoot, iRouteGroup);
}

processorRoute()方法内容很多,这里我只贴出生成java文件相关,其他代码我会在第二部分手动实现路由框架中详细介绍。如上,generatedGroup(iRouteGroup)和generatedRoot(iRouteRoot, iRouteGroup)就是生成java文件的核心了。这里我只贴出generatedRoot()方法,因为生成类文件的原理都是一样的,至于生成什么功能的类,只要你会一个,举一反三,这便没有什么难度。

/**
 * 生成Root类  作用:记录<分组,对应的Group类>
 * @param iRouteRoot
 * @param iRouteGroup
 */
private void generatedRoot(TypeElement iRouteRoot, TypeElement iRouteGroup) {
    //创建参数类型 Map<String,Class<? extends IRouteGroup>> routes>
    //Wildcard 通配符
    ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(
            ClassName.get(Map.class),
            ClassName.get(String.class),
            ParameterizedTypeName.get(
                    ClassName.get(Class.class),
                    WildcardTypeName.subtypeOf(ClassName.get(iRouteGroup))
            ));
    //生成参数 Map<String,Class<? extends IRouteGroup>> routes> routes
    ParameterSpec parameter = ParameterSpec.builder(parameterizedTypeName, "routes").build();

    //生成函数 public void loadInfo(Map<String,Class<? extends IRouteGroup>> routes> routes)
    MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constant.METHOD_LOAD_INTO)
            .addModifiers(Modifier.PUBLIC)
            .addAnnotation(Override.class)
            .addParameter(parameter);
    //生成函数体
    for (Map.Entry<String, String> entry : rootMap.entrySet()) {
        methodBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(Constant.PACKAGE_OF_GENERATE_FILE, entry.getValue()));
    }
    //生成$Root$类
    String className = Constant.NAME_OF_ROOT + moduleName;
    TypeSpec typeSpec = TypeSpec.classBuilder(className)
            .addSuperinterface(ClassName.get(iRouteRoot))
            .addModifiers(Modifier.PUBLIC)
            .addMethod(methodBuilder.build())
            .build();
    try {
      //生成java文件,PACKAGE_OF_GENERATE_FILE就是生成文件需要的路径
        JavaFile.builder(Constant.PACKAGE_OF_GENERATE_FILE, typeSpec).build().writeTo(filerUtils);
        log.i("Generated RouteRoot:" + Constant.PACKAGE_OF_GENERATE_FILE + "." + className);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

如上,我把每一块代码的作用注释了出来,相信大家很容易就能理解每一个代码段的作用。可见,其实生成文件只是调用一些api而已,只要我们熟知api的调用,生成java文件便没有什么难度。

第二部分:动手实现一个路由框架

通过*部分的讲述,我相信大家对于ARouter的原理已经有了整体轮廓的理解,这一部分,我便会通过代码带你去实现一个自己的路由框架。要实现这个路由框架,我们先来实现生成路由映射文件这一块,因为这一块是路由框架能够运行起来的核心。

  • *节:生成路由映射文件

通过*部分的讲述我们知道在Activity类上加上@Route注解之后,便可通过apt来生成对应的路由表,那么现在我们就来生成这些路由映射文件。首先,我们要理解一个问题,就是我们的路由映射文件是在编译期间生成的,那么在程序的运行期间我们要统一调用这些路由信息,便需要一个统一的调用方式。我们先来定义这个调用方式:

public interface IRouteGroup {
    void loadInto(Map<String, RouteMeta> atlas);
}

public interface IRouteRoot {
    void loadInto(Map<String, Class<? extends IRouteGroup>> routes);
}

我们定义两个接口来对生成的java文件进行约束,IRouteGroup是生成的分组关系契约,IRouteRoot是单个分组路由信息契约,只要我们生成的java文件继承自这个接口并实现loadInto()方法,在运行期间我们就可以统一的调用生成的java文件,获取路由映射信息。

现在我们来把RouterProcessor生成路由映射文件相关的代码补全:

@AutoService(Processor.class)
/**
  处理器接收的参数 替代 {@link AbstractProcessor#getSupportedOptions()} 函数
 */
@SupportedOptions(Constant.ARGUMENTS_NAME)
/**
 * 指定使用的Java版本 替代 {@link AbstractProcessor#getSupportedSourceVersion()} 函数
 */
@SupportedSourceVersion(SourceVersion.RELEASE_7)
/**
 * 注册给哪些注解的  替代 {@link AbstractProcessor#getSupportedAnnotationTypes()} 函数
 */
@SupportedAnnotationTypes(Constant.ANNOTATION_TYPE_ROUTE)

public class RouterProcessor extends AbstractProcessor {
    /**
     * key:组名 value:类名
     */
    private Map<String, String> rootMap = new TreeMap<>();
    /**
     * 分组 key:组名 value:对应组的路由信息
     */
    private Map<String, List<RouteMeta>> groupMap = new HashMap<>();

    /**
     * 节点工具类 (类、函数、属性都是节点)
     */
    private Elements elementUtils;

    /**
     * type(类信息)工具类
     */
    private Types typeUtils;

    /**
     * 文件生成器 类/资源
     */
    private Filer filerUtils;

    private String moduleName;

    private Log log;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        //获得apt的日志输出
        log = Log.newLog(processingEnvironment.getMessager());
        elementUtils = processingEnvironment.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        filerUtils = processingEnvironment.getFiler();

        //参数是模块名 为了防止多模块/组件化开发的时候 生成相同的 xx$$ROOT$$文件
        Map<String, String> options = processingEnvironment.getOptions();
        if (!Utils.isEmpty(options)) {
            moduleName = options.get(Constant.ARGUMENTS_NAME);
        }
        if (Utils.isEmpty(moduleName)) {
            throw new RuntimeException("Not set processor moudleName option !");
        }
        log.i("init RouterProcessor " + moduleName + " success !");
    }

    /**
     *
     * @param set 使用了支持处理注解的节点集合
     * @param roundEnvironment 表示当前或是之前的运行环境,可以通过该对象查找找到的注解。
     * @return true 表示后续处理器不会再处理(已经处理)
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (!Utils.isEmpty(set)) {
            //被Route注解的节点集合
            Set<? extends Element> rootElements = roundEnvironment.getElementsAnnotatedWith(Route.class);
            if (!Utils.isEmpty(rootElements)) {
                processorRoute(rootElements);
            }
            return true;
        }
        return false;
    }


    //...

}

我们通过@SupportedOptions(Constant.ARGUMENTS_NAME)拿到每个module的名字,用来生成对应module下存放路由信息的类文件名。这里变量Constant.ARGUMENTS_NAME的值就是moduleName,在这之前,我们需要在每个组件module的gradle下配置如下

javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName()]
            }
        }

@SupportedAnnotationTypes(Constant.ANNOTATION_TYPE_ROUTE)指定了需要处理的注解的路径地址,在此就是Route.class的路径地址。

RouterProcessor中我们实现了init方法,拿到log apt日志输出工具用以输出apt日志信息,并通过以下代码得到上面提到的每个module配置的moduleName

//参数是模块名 为了防止多模块/组件化开发的时候 生成相同的 xx$$ROOT$$文件
Map<String, String> options = processingEnvironment.getOptions();
if (!Utils.isEmpty(options)) {
    moduleName = options.get(Constant.ARGUMENTS_NAME);
}
if (Utils.isEmpty(moduleName)) {
    throw new RuntimeException("Not set processor moudleName option !");
}

然后在process()方法里开始生成文件名以EaseRouter_Route_moduleName和EaseRouter_Group_moduleName命名的文件。(这里的moduleName指具体的module名,demo中apt相关的代码实现都在easy-compiler module中),生成EaseRouter_Route_moduleName相关文件存储的就是分组关系,生成EaseRouter_Group_moduleName相关文件里存储的就是分组下的路由映射关系。

好了,我们终于可以生成文件了,在process()方法里有如下代码,

if (!Utils.isEmpty(set)) {
    //被Route注解的节点集合
    Set<? extends Element> rootElements = roundEnvironment.getElementsAnnotatedWith(Route.class);
    if (!Utils.isEmpty(rootElements)) {
        processorRoute(rootElements);
    }
    return true;
}
return false;

set就是扫描得到的支持处理注解的节点集合,然后得到rootElements,即被@Route注解的节点集合,此时就可以调用
processorRoute(rootElements)方法去生成文件了。processorRoute(rootElements)方法实现如下:

private void processorRoute(Set<? extends Element> rootElements) {
    //获得Activity这个类的节点信息
    TypeElement activity = elementUtils.getTypeElement(Constant.ACTIVITY);
    TypeElement service = elementUtils.getTypeElement(Constant.ISERVICE);
    for (Element element : rootElements) {
        RouteMeta routeMeta;
        //类信息
        TypeMirror typeMirror = element.asType();
        log.i("Route class:" + typeMirror.toString());
        Route route = element.getAnnotation(Route.class);
        if (typeUtils.isSubtype(typeMirror, activity.asType())) {
            routeMeta = new RouteMeta(RouteMeta.Type.ACTIVITY, route, element);
        } else if (typeUtils.isSubtype(typeMirror, service.asType())) {
            routeMeta = new RouteMeta(RouteMeta.Type.ISERVICE, route, element);
        } else {
            throw new RuntimeException("Just support Activity or IService Route: " + element);
        }
        categories(routeMeta);
    }
    TypeElement iRouteGroup = elementUtils.getTypeElement(Constant.IROUTE_GROUP);
    TypeElement iRouteRoot = elementUtils.getTypeElement(Constant.IROUTE_ROOT);

    //生成Group记录分组表
    generatedGroup(iRouteGroup);

    //生成Root类 作用:记录<分组,对应的Group类>
    generatedRoot(iRouteRoot, iRouteGroup);
}

上面提到的生成的EaseRouter_Route_moduleName文件和EaseRouter_Group_moduleName文件分别实现了IRouteRoot和IRouteGroup接口,就是通过下面这两行代码拿到IRootGroup和IRootRoot的字节码信息,然后传入generatedGroup(iRouteGroup)和generatedRoot(iRouteRoot, iRouteGroup)方法,这两个方法内部会通过javapoet api生成java文件,并实现这两个接口。

TypeElement iRouteGroup = elementUtils.getTypeElement(Constant.IROUTE_GROUP);
TypeElement iRouteRoot = elementUtils.getTypeElement(Constant.IROUTE_ROOT);

generatedGroup(iRouteGroup)和generatedRoot(iRouteRoot, iRouteGroup)就是生成上面提到的EaseRouter_Root_app和EaseRouter_Group_main等文件的具体实现,生成的方法我在*部分已经贴出来过了,这里不再阐述。

好了,现在我们编译下项目就会在每个组件module的build/generated/source/apt目录下生成相关映射文件。这里我把app module编译后生成的文件贴出来,app module编译后会生成EaseRouter_Root_app文件和EaseRouter_Group_main、EEaseRouter_Group_show等文件,EaseRouter_Root_app文件对应于app module的分组,里面记录着本module下所有的分组信息,EaseRouter_Group_main、EaseRouter_Group_show文件分别记载着当前分组下的所有路由地址和ActivityClass映射信息。如下所示:

public class EaseRouter_Root_app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("main", EaseRouter_Group_main.class);
    routes.put("show", EaseRouter_Group_show.class);
  }
}


public class EaseRouter_Group_main implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/main/main",RouteMeta.build(RouteMeta.Type.ACTIVITY,Main2\Activity.class,"/main/main","main"));
    atlas.put("/main/main2",RouteMeta.build(RouteMeta.Type.ACTIVITY,Main2\Activity.class,"/main/main2","main"));
  }
}

public class EaseRouter_Group_show implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/show/info",RouteMeta.build(RouteMeta.Type.ACTIVITY,ShowActivity.class,"/show/info","show"));
  }
}

大家会看到生成的类分别实现了IRouteRoot和IRouteGroup接口,并且实现了loadInto()方法,而loadInto方法通过传入一个特定类型的map就能把分组信息放入map里,只要分组信息存入到特定的map里后,我们就可以随意的从map里取路由地址对应的Activity.class做跳转使用。那么如果我们在login_module中想启动app_module中的MainActivity类,首先,我们已知MainActivity类的路由地址是”/main/main”,*个”/main”代表分组名,那么我们岂不是可以像下面这样调用去得到MainActivity类文件,然后startActivity()跳转到MainActivity。(这里的RouteMeta只是存有Activity class文件的封装类,先不用理会)。

public void test() {
    EaseRouter_Root_app rootApp = new EaseRouter_Root_app();
    HashMap<String, Class<? extends IRouteGroup>> rootMap = new HashMap<>();
    rootApp.loadInto(rootMap);

    //得到/main分组
    Class<? extends IRouteGroup> aClass = rootMap.get("main");
    try {
        HashMap<String, RouteMeta> groupMap = new HashMap<>();
        aClass.newInstance().loadInto(groupMap);
        //得到MainActivity
        RouteMeta main = groupMap.get("/main/main");
        Class<?> mainActivityClass = main.getDestination();

        Intent intent = new Intent(this, mainActivityClass);
        startActivity(intent);
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

}

可以看到,只要有了这些附带路由映射信息的类文件,并将其保存的映射关系存入map里,我们便能轻易的启动其他module的Activity了。

  • 第二节 路由框架的初始化

上节我们已经通过apt生成了映射文件,并且知道了如何通过映射文件去调用Activity,然而我们要实现一个路由框架,就要考虑在合适的时机拿到这些映射文件中的信息,以供上层业务做跳转使用。那么在什么时机去拿到这些映射文件中的信息呢?首先我们需要在上层业务做路由跳转之前把这些路由映射关系拿到手,但我们不能事先预知上层业务会在什么时候做跳转,那么拿到这些路由关系*好的时机就是应用程序初始化的时候。

知道了在什么时机去拿到映射关系,接下来就要考虑如何拿了。我们在上面已经介绍过实现IRouteRoot接口的所有类文件里保存着各个module的分组文件(分组文件就是实现了IRouteGroup接口的类文件),那么只要拿到所有实现IRouteGroup接口的类的集合,便能得到左右的路由信息了。下面看初始化的代码:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        EasyRouter.init(this);
    }
}

//我们手动实现的路由框架,我们就叫它EasyRouter
public class EasyRouter {

  private static final String TAG = "EasyRouter";
   private static final String ROUTE_ROOT_PAKCAGE = "com.xsm.easyrouter.routes";
   private static final String SDK_NAME = "EaseRouter";
   private static final String SEPARATOR = "_";
   private static final String SUFFIX_ROOT = "Root";

   private static EasyRouter sInstance;
   private static Application mContext;
   private Handler mHandler;

   private EasyRouter() {
       mHandler = new Handler(Looper.getMainLooper());
   }

   public static EasyRouter getsInstance() {
       synchronized (EasyRouter.class) {
           if (sInstance == null) {
               sInstance = new EasyRouter();
           }
       }
       return sInstance;
   }

   public static void init(Application application) {
       mContext = application;
       try {
           loadInfo();
       } catch (Exception e) {
           e.printStackTrace();
           Log.e(TAG, "初始化失败!", e);
       }
   }

   //...
}

可以看到,进程启动的时候我们调用EasyRouter.init()方法,init()方法中调用了loadInfo()方法,而这个loadInfo()便是我们初始化的核心。我把loadInfo的代码贴出来:

private static void loadInfo() throws PackageManager.NameNotFoundException, InterruptedException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    //获得所有 apt生成的路由类的全类名 (路由表)
    Set<String> routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
    for (String className : routerMap) {
        if (className.startsWith(ROUTE_ROOT_PAKCAGE + "." + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
            //root中注册的是分组信息 将分组信息加入仓库中
            ((IRouteRoot) Class.forName(className).getConstructor().newInstance()).loadInto(Warehouse.groupsIndex);
        }
    }
    for (Map.Entry<String, Class<? extends IRouteGroup>> stringClassEntry : Warehouse.groupsIndex.entrySet()) {
        Log.d(TAG, "Root映射表[ " + stringClassEntry.getKey() + " : " + stringClassEntry.getValue() + "]");
    }

}

我们首先通过ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE)得到apt生成的所有实现IRouteRoot接口的类文件集合,通过上面的讲解我们知道,拿到这些类文件便可以得到所有的路由地址和Activity映射关系。

这个ClassUtils.getFileNameByPackageName()方法就是具体的实现了,下面我们看具体的代码:

   /**
     * 得到路由表的类名
     * @param context
     * @param packageName
     * @return
     * @throws PackageManager.NameNotFoundException
     * @throws InterruptedException
     */
    public static Set<String> getFileNameByPackageName(Application context, final String packageName)
            throws PackageManager.NameNotFoundException, InterruptedException {
        final Set<String> classNames = new HashSet<>();
        List<String> paths = getSourcePaths(context);
        //使用同步计数器判断均处理完成
        final CountDownLatch countDownLatch = new CountDownLatch(paths.size());
        ThreadPoolExecutor threadPoolExecutor = DefaultPoolExecutor.newDefaultPoolExecutor(paths.size());
        for (final String path : paths) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    DexFile dexFile = null;
                    try {
                        //加载 apk中的dex 并遍历 获得所有包名为 {packageName} 的类
                        dexFile = new DexFile(path);
                        Enumeration<String> dexEntries = dexFile.entries();
                        while (dexEntries.hasMoreElements()) {
                            String className = dexEntries.nextElement();
                            if (!TextUtils.isEmpty(className) && className.startsWith(packageName)) {
                                classNames.add(className);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        if (null != dexFile) {
                            try {
                                dexFile.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                        //释放一个
                        countDownLatch.countDown();
                    }
                }
            });
        }
        //等待执行完成
        countDownLatch.await();
        return classNames;
    }

这个方法会通过开启子线程,去扫描apk中所有的dex,遍历找到所有包名为packageName的类名,然后将类名再保存到classNames集合里。

List<String> paths = getSourcePaths(context)这句代码会获得所有的apk文件(instant run会产生很多split apk),这个方法的具体实现大家看demo即可,不再阐述。这里用到了CountDownLatch类,会分path一个文件一个文件的检索,等到所有的类文件都找到后便会返回这个Set<String>集合。所以我们可以知道,初始化时找到这些类文件会有一定的耗时,如果你已经看过ARouter的源码便会知道ARouter这里会有一些优化,只会遍历找一次类文件,找到之后就会保存起来,下次app进程启动会检索是否有保存这些文件,如果有就会直接调用保存后的数据去初始化。

  • 第三节 路由跳转实现

经过上节的介绍,我们已经能够在进程初始化的时候拿到所有的路由信息,那么实现跳转便好做了。直接看代码:

@Route(path = "/main/main")
public class MainActivity extends AppCompatActivity {

  public void startModule1MainActivity(View view) {
    EasyRouter.getsInstance().build("/module1/module1main").navigation();
  }

}

在build的时候,传入要跳转的路由地址,build()方法会返回一个Postcard对象,我们称之为跳卡。然后调用Postcard的navigation()方法完成跳转。用过ARouter的对这个跳卡都应该很熟悉吧!Postcard里面保存着跳转的信息。下面我把Postcard类的代码实现粘下来:

public class Postcard extends RouteMeta {
    private Bundle mBundle;
    private int flags = -1;
    //新版风格
    private Bundle optionsCompat;
    //老版
    private int enterAnim;
    private int exitAnim;

    public Postcard(String path, String group) {
        this(path, group, null);
    }

    public Postcard(String path, String group, Bundle bundle) {
        setPath(path);
        setGroup(group);
        this.mBundle = (null == bundle ? new Bundle() : bundle);
    }

    public Bundle getExtras() {return mBundle;}

    public int getEnterAnim() {return enterAnim;}

    public int getExitAnim() {return exitAnim;}

    /**
     * 跳转动画
     * @param enterAnim
     * @param exitAnim
     * @return
     */
    public Postcard withTransition(int enterAnim, int exitAnim) {
        this.enterAnim = enterAnim;
        this.exitAnim = exitAnim;
        return this;
    }

    /**
     * 转场动画
     * @param compat
     * @return
     */
    public Postcard withOptionsCompat(ActivityOptionsCompat compat) {
        if (null != compat) {
            this.optionsCompat = compat.toBundle();
        }
        return this;
    }

    public Postcard withString(@Nullable String key, @Nullable String value) {
        mBundle.putString(key, value);
        return this;
    }


    public Postcard withBoolean(@Nullable String key, boolean value) {
        mBundle.putBoolean(key, value);
        return this;
    }

    public Postcard withInt(@Nullable String key, int value) {
        mBundle.putInt(key, value);
        return this;
    }

    //还有许多给intent中bundle设置值得方法我就不一一列出来了,可以看demo里所有的细节

    public Bundle getOptionsBundle() {
        return optionsCompat;
    }

    public Object navigation() {
        return EasyRouter.getsInstance().navigation(null, this, -1, null);
    }

    public Object navigation(Context context) {
        return EasyRouter.getsInstance().navigation(context, this, -1, null);
    }


    public Object navigation(Context context, NavigationCallback callback) {
        return EasyRouter.getsInstance().navigation(context, this, -1, callback);
    }

    public Object navigation(Context context, int requestCode) {
        return EasyRouter.getsInstance().navigation(context, this, requestCode, null);
    }

    public Object navigation(Context context, int requestCode, NavigationCallback callback) {
        return EasyRouter.getsInstance().navigation(context, this, requestCode, callback);
    }


}

如果你是一个Android开发,Postcard类里面的东西就不用我再给你介绍了吧!(哈哈)我相信你一看就明白了。我们只介绍一个方法navigation(),他有好几个重载方法,方法里面会调用EasyRouter类的navigation()方法。EaseRouter的navigation()方法,就是跳转的核心了。下面请看:

protected Object navigation(Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    try {
        prepareCard(postcard);
    }catch (NoRouteFoundException e) {
        e.printStackTrace();
        //没找到
        if (null != callback) {
            callback.onLost(postcard);
        }
        return null;
    }
    if (null != callback) {
        callback.onFound(postcard);
    }

    switch (postcard.getType()) {
        case ACTIVITY:
            final Context currentContext = null == context ? mContext : context;
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            intent.putExtras(postcard.getExtras());
            int flags = postcard.getFlags();
            if (-1 != flags) {
                intent.setFlags(flags);
            } else if (!(currentContext instanceof Activity)) {
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    //可能需要返回码
                    if (requestCode > 0) {
                        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) {
                        //老版本
                        ((Activity) currentContext).overridePendingTransition(postcard
                                        .getEnterAnim()
                                , postcard.getExitAnim());
                    }
                    //跳转完成
                    if (null != callback) {
                        callback.onArrival(postcard);
                    }
                }
            });
            break;
        default:
            break;
    }
    return null;
}

这个方法里先去调用了prepareCard(postcard)方法,prepareCard(postcard)代码我贴出来,

private void prepareCard(Postcard card) {
    RouteMeta routeMeta = Warehouse.routes.get(card.getPath());
    if (null == routeMeta) {
        Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(card.getGroup());
        if (null == groupMeta) {
            throw new NoRouteFoundException("没找到对应路由:分组=" + card.getGroup() + "   路径=" + card.getPath());
        }
        IRouteGroup iGroupInstance;
        try {
            iGroupInstance = groupMeta.getConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException("路由分组映射表记录失败.", e);
        }
        iGroupInstance.loadInto(Warehouse.routes);
        //已经准备过了就可以移除了 (不会一直存在内存中)
        Warehouse.groupsIndex.remove(card.getGroup());
        //再次进入 else
        prepareCard(card);
    } else {
        //类 要跳转的activity 或IService实现类
        card.setDestination(routeMeta.getDestination());
        card.setType(routeMeta.getType());
        switch (routeMeta.getType()) {
            case ISERVICE:
                Class<?> destination = routeMeta.getDestination();
                IService service = Warehouse.services.get(destination);
                if (null == service) {
                    try {
                        service = (IService) destination.getConstructor().newInstance();
                        Warehouse.services.put(destination, service);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                card.setService(service);
                break;
            default:
                break;
        }
    }
}

注意,Warehouse就是专门用来存放路由映射关系的类,里面保存着存路由信息的map,这在ARouter里面也是一样的。这段代码Warehouse.routes.get(card.getPath())通过path拿到对应的RouteMeta,这个RouteMeta里面保存了activityClass等信息。继续往下看,如果判断拿到的RouteMeta是空,说明这个路由地址还没有加载到map里面(初始化时为了节省性能,只会加载所有的分组信息,而每个分组下的路由映射关系,会使用懒加载,在首次用到的时候去加载),只有在*次用到当前路由地址的时候,会去Warehouse.routes里面拿routeMeta,如果拿到的是空,会根据当前路由地址的group拿到对应的分组,通过反射创建实例,然后调用实例的loadInfo方法,把它里面保存的映射信息添加到Warehouse.routes里面,并且再次调用prepareCard(card),这时再通过Warehouse.routes.get(card.getPath())就可以顺利拿到RouteMeta了。进入else{}里面,调用了card.setDestination(routeMeta.getDestination()),这个setDestination就是将RouteMeta里面保存的activityClass放入Postcard里面,下面switch代码块可以先不用看,这是实现ARouter中通过依赖注入实现Provider 服务的逻辑,有心研究的同学可以去读一下demo。

好了,prepareCard()方法调用完成后,我们的postcard里面就保存了activityClass,然后switch (postcard.getType()){}会判断postcard的type为ACTIVITY,然后通过ActivityCompat.startActivity启动Activity。到这里,路由跳转的实现已经讲解完毕了。

小结

EaseRouter本身只是参照ARouter手动实现的路由框架,并且剔除掉了很多东西,如过滤器等,如果想要用在项目里,建议还是用ARouter更好,毕竟这只是个练手项目,功能也不够全面,当然有同学想对demo扩展后使用那当然更好,遇到什么问题可以及时联系我。我的目的是通过自己手动实现路由框架来加深对知识的理解,如这里面涉及到的知识点apt、javapoet和组件化思路、编写框架的思路等。看到这里,如果感觉干货很多,欢迎关注我的github,里面会有更多干货!

阿里ARouter使用及源码解析(一)

在app的开发中,页面之间的相互跳转是*基本常用的功能。在Android中的跳转一般通过显式intent和隐式intent两种方式实现的,而Android的原生跳转方式会存在一些缺点:

  • 显式intent的实现方式,因为会存在直接的类依赖的问题,导致耦合严重;
  • 隐式intent的实现方式,则会出现规则集中式管理,导致协作变得困难;
  • 可配置性较差,一般而言配置规则都是在Manifest中的,这就导致了扩展性较差;
  • 跳转过程无法控制,一旦使用了StartActivity()就无法插手其中任何环节了,只能交给系统管理;
  • 当多组件化开发,使用原生的路由方式很难实现完全解耦;

而阿里的ARouter路由框架具有解耦、简单易用、支持多模块项目、定制性较强、支持拦截逻辑等诸多优点,很好的解决了上述的问题。关于ARouter具体实现功能,典型应用以及相应技术方案实现的介绍不在这详细介绍,具体可参见开源*佳实践:Android平台页面路由框架ARouter。

阿里ARouter的分析计划

  • 阿里ARouter使用及源码解析(一)
  • 阿里ARouter拦截器使用及源码解析(二)
  • 阿里ARouter参数自动装载使用及源码解析(三)
基本功能使用

1.添加依赖和配置

android {
    defaultConfig {
    ...
    javaCompileOptions {
        annotationProcessorOptions {
        arguments = [ moduleName : project.getName() ]
        }
    }
    }
}

dependencies {
    compile 'com.alibaba:arouter-api:1.2.1.1'
    annotationProcessor 'com.alibaba:arouter-compiler:1.1.2.1'
    ...
}

2.添加注解

// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/test1")
public class Test1Activity extends AppCompatActivity{

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test1);
    }
}

3.初始化SDK

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btn1,btn2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn1 = (Button) findViewById(R.id.btn1);
        btn2 = (Button) findViewById(R.id.btn2);

        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn1) {
            // 如果使用了InstantRun,必须在初始化之前开启调试模式,但是上线前需要关闭,InstantRun仅用于开发阶段,
            // 线上开启调试模式有安全风险,可以使用BuildConfig.DEBUG来区分环境
            ARouter.openDebug();
            ARouter.init(getApplication()); // 尽可能早,推荐在Application中初始化
        }
    }
}

4.发起跳转操作

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btn1,btn2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn1 = (Button) findViewById(R.id.btn1);
        btn2 = (Button) findViewById(R.id.btn2);

        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn1) {
            ....
        } else if (v.getId() == R.id.btn2){
            ARouter.getInstance().build("/test/test1").navigation();
        }
    }
}

以上相关代码就是ARouter的*基本功能使用的步骤,下面来分析跳转功能是如何实现的。

原理分析
1.ARouter编译的过程

ARouter在编译期的时候,利用自定义注解完成了页面的自动注册。相关注解源码参见arouter-annotation,编译处理器源码参见arouter-compiler

下面是注解@Route的源码介绍:

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

    /**
     *路由的路径,标识一个路由节点
     */
    String path();

    /**
     * 将路由节点进行分组,可以实现按组动态加载
     */
    String group() default "";

    /**
     * 路由节点名称,可用于生成javadoc文档
     */
    String name() default "undefined";

    /**
     * 用32位int类型标示,可用于页面的一些配置
     */
    int extras() default Integer.MIN_VALUE;

    /**
     * 路由的优先级
     */
    int priority() default -1;
}

Route中的extra值是个int值,由32位表示,即转换成二进制后,一个int中可以配置31个1或者0,而每一个0或者1都可以表示一项配置(排除符号位),如果从这31个位置中随便挑选出一个表示是否需要登录就可以了,只要将标志位置为1,就可以在声明的拦截器中获取到这个标志位,通过位运算的方式判断目标页面是否需要登录。所以可以通过extra给页面配置30多个属性,然后在拦截器中去进行处理。
ARouter在拦截器中会把目标页面的信息封装一个类Postcard,这个类就包含了目标页面注解上@Route标识的各种信息。关于拦截器的使用以及源码分析,后续会有介绍。

将代码编译一遍,可以看到ARouter生成下面几个源文件:

%title插图%num

上面三个文件均是通过注解处理器RouteProcessor生成的,关于如何自定义注解处理器,可以阅读Android编译时注解APT实战(AbstractProcessor),同时也需要学习JavaPoet的基本使用。下面我们看RouteProcessor是如何生成相关文件的。

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //判断被注解了的元素集合是否为空
        if (CollectionUtils.isNotEmpty(annotations)) {
            //获取所有被@Route注解的元素集合,Element可以是类、方法、变量等
            Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
            try {
                logger.info(">>> Found routes, start... <<<");
                //具体处理注解,生成java文件的方法
                this.parseRoutes(routeElements);

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

        return false;
    }

process()方法相当于处理器的主函数main(),可以在这个方法中扫描、评估和处理注解的代码,以及生成Java文件。RouteProcessor中调用了parseRoutes(),用来处理所有被@Route注解的元素。在分析上述三个java文件如何生成之前,先看看生成文件的具体代码。

  • ARouter$$Root$$app类
public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("test", ARouter$$Group$$test.class);
  }
}
  • ARouter$$Group$$test类
public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/test1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/test1", "test", null, -1, -2147483648));
  }
}
  • ARouter$$Providers$$app类
public class ARouter$$Providers$$app implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
  }
}

我们接着分析上述三个文件是如何生成的

1.首先获取生成方法的参数的类型和参数名称

 private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
        if (CollectionUtils.isNotEmpty(routeElements)) {
          
            logger.info(">>> Found routes, size is " + routeElements.size() + " <<<");

            rootMap.clear();
             // TypeElement 表示一个类或接口元素
            // public static final String ACTIVITY = "android.app.Activity";
            //得到类activity元素
            TypeElement type_Activity = elementUtil.getTypeElement(ACTIVITY);
            // public static final String SERVICE = "android.app.Service";
            //得到类service的元素
            TypeElement type_Service = elementUtil.getTypeElement(SERVICE);
            // public static final String SERVICE = "android.app.Fragment";
            TypeMirror fragmentTm = elements.getTypeElement(FRAGMENT).asType();
             // public static final String SERVICE = "android.support.v4.app.Fragment";
            TypeMirror fragmentTmV4 = elements.getTypeElement(Consts.FRAGMENT_V4).asType();

            // public static final String IROUTE_GROUP = "com.alibaba.android.arouter.facade.template.IRouteGroup";
            //得到接口IRouteGroup元素
            TypeElement type_IRouteGroup = elementUtil.getTypeElement(IROUTE_GROUP);
          // public static final String IROUTE_GROUP = "com.alibaba.android.arouter.facade.template.IProviderGroup";
            //得到接口IProviderGroup元素
            TypeElement type_IProviderGroup = elementUtil.getTypeElement(IPROVIDER_GROUP);
            //获取RouteMeta,RouteType类名
            ClassName routeMetaCn = ClassName.get(RouteMeta.class);
            ClassName routeTypeCn = ClassName.get(RouteType.class);

            //下面代码是获取生成java文件中方法的参数类型名称和参数名称。
            /*
              获取获取ARouter$$Root$$app 类中方法参数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))
                    )
            );

            /*
              获取ARouter$$Group$$test,ARouter$$Providers$$app类中方法参数 Map<String, RouteMeta>类型的名称
             */
            ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ClassName.get(RouteMeta.class)
            );

            /*
             获取相关的参数
             */
            //获取ARouter$$Root$$app 类中方法的参数Map<String, Class<? extends IRouteGroup>> routes
            ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();
           //获取ARouter$$Group$$test类中方法的参数Map<String, RouteMeta> atlas
            ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
             //获取ARouter$$Providers$$app类中方法的参数Map<String, RouteMeta> providers
            ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build();  

          
          .....
        }
    }

2.获取了方法的参数的类型和参数名称后,下面便是生成相应的方法

 private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
        if (CollectionUtils.isNotEmpty(routeElements)) {
            ........

            /*
              首先创建ARouter$$Root$$xxx 类中的loadInto()方法
              @Override
              public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {}
             */
            MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(rootParamSpec);

            //  遍历所有被@Route注解的元素
            for (Element element : routeElements) {
                TypeMirror tm = element.asType();
                Route route = element.getAnnotation(Route.class);
                RouteMeta routeMete = null;
                
                //判断该元素否为 Activity 、IProvider 、 Service 的子类,然后创建相应的RouteMeta 对象
                if (typeUtil.isSubtype(tm, type_Activity.asType())) {                 // Activity
                    logger.info(">>> Found activity route: " + tm.toString() + " <<<");

                    // 如果是acitiviy类型,获取所有被@Autowired的属性
                    //关于@Autowired的注解,我们之后再进行分析
                    Map<String, Integer> paramsType = new HashMap<>();
                    for (Element field : element.getEnclosedElements()) {
                        if (field.getKind().isField() && field.getAnnotation(Autowired.class) != null && !typeUtil.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.asType()));
                        }
                    }
                    // ACTIVITY类型节点
                    routeMete = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
                } else if (typeUtil.isSubtype(tm, iProvider)) {         // IProvider
                    logger.info(">>> Found provider route: " + tm.toString() + " <<<");
                    //从该判断可看出,如果要想成功注册一个 PROVIDER 类型的路由节点,
                    //一定要实现 com.alibaba.android.arouter.facade.template.IProvider 这个接口
                    routeMete = new RouteMeta(route, element, RouteType.PROVIDER, null);
                } else if (typeUtil.isSubtype(tm, type_Service.asType())) {           // Service
                    logger.info(">>> Found service route: " + tm.toString() + " <<<");
                     //SERVICE类型节点
                    routeMete = 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() + " <<<");
                   //FRAGMENT类型节点
                    routeMete = new RouteMeta(route, element, RouteType.parse(FRAGMENT), null);
                }
                
                //routeMete包含了每个路由节点的各种信息,下面的方法的主要功能就是根据@Route注解信息对节点进行分组,保存在groupMap集合中。
               //关于方法的具体实现,后面会有解析
                categories(routeMete);
          
            }

            .........
        }
    }

以上代码主要功能就是遍历所有被@Route注解的元素,然后将每个路由节点的信息按照类型(ACTIVITY类型,实现了IProvider 接口类型以及SERVICE类型)封装到RouteMeta中,*后调用categories(routeMete)方法将节点分组,保存在groupMap集合。

继续往下分析

 private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
        if (CollectionUtils.isNotEmpty(routeElements)) {
            ........

             /*
              然后创建ARouter$$Providers$$xxx 类中的loadInto()方法
             @Override
             public void loadInto(Map<String, RouteMeta> providers) {}
             */
            MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(providerParamSpec);

            //遍历分组的集合,生成相应的java文件
           //因为本文使用的例子没有对页面进行分组,所以只生成了一个组文件ARouter$$Group$$xxx
            for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
                String groupName = entry.getKey();
               /*
                  创建ARouter$$Group$$xxx 类中的loadInto()方法
                 @Override
                 public void loadInto(Map<String, RouteMeta> atlas) {}
             */
                MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                        .addAnnotation(Override.class)
                        .addModifiers(PUBLIC)
                        .addParameter(groupParamSpec);

                // 生成loadInto()方法体
                Set<RouteMeta> groupData = entry.getValue();
                //遍历每个组里面的路由节点
                for (RouteMeta routeMeta : groupData) {
                    switch (routeMeta.getType()) {
                        //如果节点类型是PROVIDER,
                        case PROVIDER:  
                          //获取路由节点元素的接口集合
                            List<? extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces();
                            for (TypeMirror tm : interfaces) {
                             if (types.isSameType(tm, iProvider)) {   // Its implements iProvider interface himself.
                                   //路由节点元素其中一个接口是 com.alibaba.android.arouter.facade.template.IProvider 
                                  //给ARouter$$Providers$$xxx 类中的loadInto()添加方法体
                                    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)) {
                                   //路由节点元素其中一个接口是com.alibaba.android.arouter.facade.template.IProvider 接口的子类型
                                    loadIntoMethodOfProviderBuilder.addStatement(
                                            "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                            tm.toString(),   //IProvider子类型的全名
                                            routeMetaCn,
                                            routeTypeCn,
                                            ClassName.get((TypeElement) routeMeta.getRawType()),
                                            routeMeta.getPath(),
                                            routeMeta.getGroup());
                                }
                            //上面方法体的代码为:
                          //providers.put("实现接口的名称", RouteMeta.build(RouteType.PROVIDER, 类名.class,   "@Route.path", "@Route.group", null, @Route.priority, @Route.extras));
                            }
                            break;
                        default:
                            break;
                    }

                    // 将路由节点中被@Autowired注解的属性集合转换成字符串
                    StringBuilder mapBodyBuilder = new StringBuilder();
                    //获取路由节点中被@Autowired注解的属性集合
                    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();
                    
                    //给ARouter$$Group$$xxx 类中的loadInto()添加方法体
                    //注意:有多个分组就会创建多个组文件
                    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());
                }

                  // 真正生成ARouter$$Group$$test JAVA文件
                 //NAME_OF_GROUP = ARouter$$Group$$
                //  groupName = test; 关于groupname的值在方法categories(routeMete)中会有讲解
                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集合中去,为下面生成ARouter$$Root$$xxx文件做准备
                rootMap.put(groupName, groupFileName);
            }

         .......
        }
    }

以上代码主要功能由几点:

  • 遍历groupmap集合给ARouter$$Group$$xxx类中的loadInto()添加方法体,并且生成ARouter$$Group$$xxx JAVA文件,而文件命名为ARouter$$Group$$+groupname,其中有多个分组就会创建多个组文件。比如AROUTER源码中的样例就生成了多个分组文件
%title插图%num
两个分组文件

关于生成的loadInto()中的方法体的例子,来自 AROUTER源码中的样例:

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    //存在被@Autowired注解参数生成的代码
    atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("name", 18); put("boy", 0); put("age", 3); put("url", 18); }}, -1, -2147483648));
    .....
   //没有被@Autowired注解参数生成的代码
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
    ....
  }
}
  • 遍历每个组里面的路由节点,查找节点类型是否为PROVIDER类型,如果是就向给ARouter$$Providers$$xxx类中的loadInto()添加方法,其文件命名ARouter$$Providers$$+modulename。关于生成的loadInto()中的方法体的例子,来自 AROUTER源码中的样例:
public class ARouter$$Providers$$app implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
    //路由节点元素其中一个接口是IProvider的子类型
    providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json", "service", null, -1, -2147483648));
     //路由节点元素其中一个接口是IProvider接口
    providers.put("com.alibaba.android.arouter.demo.testservice.SingleService", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/service/single", "service", null, -1, -2147483648));
  }
}
  • 将生成的组文件放在rootmap集合中去,为下面生成ARouter$$Root$$xxx文件做准备,其文件命名ARouter$$Root$$+modulename。

我们接着分析parseRoutes()方法*后一段代码,这段代码其实很简单,主要目的就是给ARouter$$Root$$xxx的loadInto()添加方法体,*后生成Router$$Providers$$xxx,ARouter$$Root$$xxx文件

 private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
        if (CollectionUtils.isNotEmpty(routeElements)) {
            ........
            //遍历rootMap集合,给ARouter$$Root$$xxx的`loadInto()`添加方法体
            if (MapUtils.isNotEmpty(rootMap)) {
                // Generate root meta by group name, it must be generated before root, then I can findout 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()));
                }
            }

            // 生成Router$$Providers$$xxx文件
            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 + " <<<");

            // 生成ARouter$$Root$$xxx文件
            String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(rootFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(elementUtil.getTypeElement(ITROUTE_ROOT)))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfRootBuilder.build())
                            .build()
            ).build().writeTo(mFiler);

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

关于生成的loadInto()中的方法体的例子,来自 AROUTER源码中的样例:

public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("service", ARouter$$Group$$service.class);
    routes.put("test", ARouter$$Group$$test.class);
  }
}

上面分析的便是parseRoutes()方法所有代码的解析

3.*后我们看下categories()方法是如何分组的

   private void categories(RouteMeta routeMete) {
        //如果路由路径合法,且有groupname进行执行
        if (routeVerify(routeMete)) {
            logger.info(">>> Start categories, group = " + routeMete.getGroup() + ", path = " + routeMete.getPath() + " <<<");
             //根据groupname获取该组的路由节点集合,如果集合为空,则创建一个新的组,将该节点添加进去,并将组集合保存在groupmap中;
          //不为空,则添加到所属的组集合中去
            Set<RouteMeta> routeMetas = groupMap.get(routeMete.getGroup());
            if (CollectionUtils.isEmpty(routeMetas)) {
                Set<RouteMeta> routeMetaSet = new TreeSet<>(new Comparator<RouteMeta>() {
                    @Override
                    public int compare(RouteMeta r1, RouteMeta r2) {
                        try {
                            return r1.getPath().compareTo(r2.getPath());
                        } catch (NullPointerException npe) {
                            logger.error(npe.getMessage());
                            return 0;
                        }
                    }
                });
                routeMetaSet.add(routeMete);
                groupMap.put(routeMete.getGroup(), routeMetaSet);
            } else {
                routeMetas.add(routeMete);
            }
        } else {
            logger.warning(">>> Route meta verify error, group is " + routeMete.getGroup() + " <<<");
        }
    }

//判断路由路径是否合法,并且设置groupname
 private boolean routeVerify(RouteMeta meta) {
        String path = meta.getPath();
        //如果路径为空,或者不是由'/'开头,返回false
        if (StringUtils.isEmpty(path) || !path.startsWith("/")) {   // The path must be start with '/' and not empty!
            return false;
        }

         //如果在@Route注解中没有设置group标识,那么就默认取path路径*段路径名作为groupname
        if (StringUtils.isEmpty(meta.getGroup())) { // Use default group(the first word in path)
            try {
                String defaultGroup = path.substring(1, path.indexOf("/", 1));
                if (StringUtils.isEmpty(defaultGroup)) {
                    return false;
                }

                meta.setGroup(defaultGroup);
                return true;
            } catch (Exception e) {
                logger.error("Failed to extract default group! " + e.getMessage());
                return false;
            }
        }

        return true;
    }

通过分析,如果@Route注解中有设置group标识,作为groupname,如果没有就取/xxx1/xxx2,xxx1作为groupname,并将同一组的路由节点放到同一个集合中去。

至此关于@Route注解在编译期时生成ARouter$$Root$$xxx,Router$$Providers$$xxx,ARouter$$Group$$xxx三种映射文件的源码分析完毕。

2.ARouter初始化过程

ARouter经过代码编译后,生成了相应的映射文件,我们可以断定,ARouter 的初始化会将这些文件加载到内存中去,形成一个路由表,以供后面路由查找跳转之用。其相关源码可参见 arouter-api

  • ARouterinit()方法
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 种的方法。

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

        return true;
    }

_ARouter中又调用了LogisticsCenter.init(),继续追踪下去,其中传入了一个线程池executor,这个线程池在拦截器的时候会使用到。

    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;

        try {
             //ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes"
            // 获取ROUTE_ROOT_PAKCAGE 包里面的所有文件
            List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

            //遍历所有ROUTE_ROOT_PAKCAGE 包里的文件
            for (String className : classFileNames) {
                //文件名以“com.alibaba.android.arouter.routes.ARouter$$Root”开头执行下面代码
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    // 通过反射实例化,并且调用loadInto(),目的即是将编译生成的ARouter$$Group$$xxx文件加载到内存中,保存在Warehouse.groupsIndex;
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    //文件名以“com.alibaba.android.arouter.routes.ARouter$$Interceptors”开头执行下面代码
                    //  执行编译生成的ARouter$$Interceptors$$xxx的loadInto(),将自定义拦截器类存放在Warehouse.interceptorsIndex中
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                     //文件名以“com.alibaba.android.arouter.routes.ARouter$$Providers”开头执行下面代码
                   //  执行编译生成的ARouter$$Interceptors$$xxx的loadInto()
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }

            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() + "]");
        }
    }
  • _ARouterafterInit()方法
static void afterInit() {
        // 通过路由机制,初始化路由拦截机制。关于路由拦截机制的使用和原理,后续文章会有分析
        interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
    }

以上就是ARouter初始化的所有代码,关于如何查找到com.alibaba.android.arouter.routes包内所有文件这里便不做过多分析,大家可以去阅读 arouter-api中ClassUtils这个类的源码。
总结下来,其实ARouter 的初始化只做了一件事,找到自己编译期产生的清单文件,把 Group 、Interceptor 、Provider 三种清单加载到 Warehouse 内存仓库中。即下面这些文件,来源自AROUTER源码中的样例

%title插图%num

值得注意的是,在初始化阶段,ARouter 仅载入了 Group 清单,并没有具体载入每个 Group 中包含的具体的路由节点清单,只有当使用到具体的 Group 时,才会加载对应的 Group 列表。这种分组管理,按需加载,大大的降低了初始化时的内存压力。并且Warehouse类中保存了路由清单,并且将使用过的路由对象缓存起来,之后查找都是直接使用缓存的对象 。

3.ARouter调用过程分析

页面跳转*基本方法

ARouter.getInstance().build(“/test/activity2”).navigation();

获取Provider服务(实现了IProvider接口以及IProvider子类接口的服务类)的方法有两种:

1.byName方式
ARouter.getInstance().build(“/service/hello”).navigation()

2.byType方式
ARouter.getInstance().navigation(HelloService.class)

ARouter路由跳转采用链式调用,ARouter.getInstance()其中采用的单例模式,获取ARouter的实例,这个就不作过多分析,主要分析build()navigation()

build()方法
ARouter的build(String path)init()方法一样,调用的是_ARouterbuild(String path)方法。

  protected Postcard build(String path) {
        if (TextUtils.isEmpty(path)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            return build(path, extractGroup(path));
        }
    }

其中extractGroup(String path)就是根据path获取分组名,即path*段“/”符号之间的值

  private String extractGroup(String path) {
        if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
            throw new HandlerException(Consts.TAG + "Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!");
        }

        try {
            //    /xxx1/xxx2   ===>  defaulGroup = xxx1
            String defaultGroup = path.substring(1, path.indexOf("/", 1));
            if (TextUtils.isEmpty(defaultGroup)) {
                throw new HandlerException(Consts.TAG + "Extract the default group failed! There's nothing between 2 '/'!");
            } else {
                return defaultGroup;
            }
        } catch (Exception e) {
            logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage());
            return null;
        }
    }

build(String path)方法*终调用的是build(String path, String group)

    protected Postcard build(String path, String group) {
        if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            return new Postcard(path, group);
        }
    }

值得注意的是其中ARouter.getInstance().navigation(PathReplaceService.class)就是得到实现PathReplaceService接口的一个服务对象,对原始path进行处理后,生成新的path路径。而这个类需要我们自己自定义去实现,如果没有实现,pService=null,原始path不做任何处理。
下面是PathReplaceService接口,我们可以通过实现forString()forUri()方法,对某些url进行替换处理,跳转到其他的目标页面。

public interface PathReplaceService extends IProvider {

    /**
     * For normal path.
     *
     * @param path raw path
     */
    String forString(String path);

    /**
     * For uri type.
     *
     * @param uri raw uri
     */
    Uri forUri(Uri uri);
}

*后返回一个Postcard实例对象,里面封装了路由节点的路径,分组等节点信息。其实build()方法的目的只有一个就是根据路由,封装成Postcard对象,其对象贯穿之后整个路由过程。Postcard 包含了众多的属性值,提供了路由过程中所有的控制变量。

public final class Postcard extends RouteMeta {
    private Uri uri;
    private Object tag;             // A tag prepare for some thing wrong.
    private Bundle mBundle;         // 传递的参数
    private int flags = -1;         // intent 的flag标志
    private int timeout = 300;      // Navigation timeout, TimeUnit.Second !
    private IProvider provider;     // IProvider服务对象
    private boolean greenChannal;
    private SerializationService serializationService;//序列化服务对象

     // 跳转动画
    private Bundle optionsCompat;    // The transition animation of activity
    private int enterAnim;
    private int exitAnim;

    // copy from RouteMeta 
    private RouteType type;         // 路由节点类型
    private Element rawType;        
    private Class<?> destination;  //需要跳转到的页面
    private String path;            // 路径
    private String group;           // 分组
    private int priority = -1;      // 优先级
    private int extra;              // 配置标识
    private Map<String, Integer> paramsType;  // 路由页面被@Autowired注解属性
    // ......
}

navigation()方法
关于页面跳转的navigation()方法有多个重载的方法,但*终都会调用_ARouter下面这个方法

    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        try {
            //首先对postcard进行一些处理,设置postcard的destination,type,priority 等一些属性值,completion()后面会有分析
            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();
            }
            // 如果处理postcard失败,通过 callback 回调失败结果
           // callback为空的情况下,如果有定义全局的降级处理(DegradeService),则使用全局处理
           //降级处理也需要我们自己实现DegradeService接口
            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;
        }
         //路由处理成功,回调callback.onFound()
        if (null != callback) {
            callback.onFound(postcard);
        }
        
        //目前来说,PROVIDER服务类型,以及FRAGMENT类型不需要通过拦截器外,其他类型均需要通过拦截器
        //关于拦截器相关用法及原理分析在后续的文章中会讲解到,大家去可以关注下
        if (!postcard.isGreenChannel()) {   
            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;
    }

值得注意的是,当跳转路由处理失败的时候,会获取一个降级服务,我们可以实现DegradeService接口,实现onLost()方法,对路由处理失败的情况进行处理,比如跳转到一个信息提示页面,让用户去更新版本等操作等。下面是DegradeService接口:

public interface DegradeService extends IProvider {

    /**
     * Router has lost.
     *
     * @param postcard meta
     */
    void onLost(Context context, Postcard postcard);
}

通过上面代码的分析,不管是否通过拦截器进行处理,*后都会调用_navigation()达到路由的目的:

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:
                //下面就是*基本的使用intent进行activity进行跳转
                // 创建intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                //设置传参
                intent.putExtras(postcard.getExtras());

                //activity启动标志
                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);
                }

                // 在主线程中进行跳转
                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());
                        }
                        //跳转成功,回调callback.onArrival()
                        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 {
                     //实例化fragment,并传递参数
                    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;
    }

目前仅ARouter实现了 ACTIVITY , PROVIDER ,FRAGMENT三种种类型。上面关于postcard的provider,destination的值都是在completion()中设置的。我们接着看LogisticsCentercompletion(Postcard postcard)

    public synchronized static void completion(Postcard postcard) {
        if (null == postcard) {
            throw new NoRouteFoundException(TAG + "No postcard!");
        }
      
        // 查找Warehouse仓库的路由节点缓存,看是否已在缓存中
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) {   
          // 如果没有,查找仓库的组别清单中是否存在该组别,组别清单已经在初始化的时候加载到仓库中去了
            Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  
            //如果没有抛出异常
            if (null == groupMeta) {
                throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
            } else {
                // Load route and cache it into memory, then delete from metas.
                try {
                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                    // 实例化个组别的类,调用loadInto(),将组别中所有的路由节点加载进仓库Warehouse.routes,缓存
                    IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                    iGroupInstance.loadInto(Warehouse.routes);
                     // 从组别清单中删除已加载的组别,防止重复加载
                    Warehouse.groupsIndex.remove(postcard.getGroup());

                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                } catch (Exception e) {
                    throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                }
                //当路由节点加载到缓存中去后,重新查找执行else代码,对postcard进行处理
                completion(postcard);   // Reload
            }
        } else {
            //给postcard设置destination,type,priority等值,供上面讲解到的_navigation()进行使用
            // 其中routeMeta是在ARouter$$Group$$xxx的loadInto中创建的
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());

            //如果通过build(Uri url) 进行跳转的话 通过解析url ,将传参保存进bundle中
            Uri rawUri = postcard.getUri();
            if (null != rawUri) {  
                //splitQueryParameters()就是在uri中携带的参数进行解析
                Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
                Map<String, Integer> paramsType = routeMeta.getParamsType();

                if (MapUtils.isNotEmpty(paramsType)) {
                    // Set value by its type, just for params which annotation by @Param
                    for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                        setValue(postcard,
                                params.getValue(),
                                params.getKey(),
                                resultMap.get(params.getKey()));
                    }

                    // Save params name which need autoinject.
                    postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
                }

                // Save raw uri
                postcard.withString(ARouter.RAW_URI, rawUri.toString());
            }
            
            //从这里也可以看出PROVIDER,FRAGMENT不需要通过拦截器
            switch (routeMeta.getType()) {
                case PROVIDER:  
                    // 如果是PROVIDER节点类型,从服务节点列表中获取,如果没有,则实例化,并保存在服务节点列表Warehouse.providers中
                  //并将实例化的对象设置给postcard的provider属性
                    Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                    IProvider instance = Warehouse.providers.get(providerMeta);
                    if (null == instance) { // There's no instance of this provider
                        IProvider provider;
                        try {
                            provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                        } catch (Exception e) {
                            throw new HandlerException("Init provider failed! " + e.getMessage());
                        }
                    }
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
            }
        }
    }

分析到这里,关于页面基本跳转的原理分析就已经结束了。*后就是关于获取Provider服务两种方法的源码分析。其中byName方式,和页面跳转是一模一样的。我们只需要看看byType方式即可。byType方式*后调用的是_ARouternavigation(Class<? extends T> service)

  protected <T> T navigation(Class<? extends T> service) {
        try {
            // 通过 className 获取 Postcard 对象
            Postcard postcard = LogisticsCenter.buildProvider(service.getName());

            // 兼容1.0.5 compiler sdk版本.
            if (null == postcard) { // No service, or this service in old version.
                postcard = LogisticsCenter.buildProvider(service.getSimpleName());
            }
           // 对 Postcard 对象进行处理
            LogisticsCenter.completion(postcard);
             //返回 Postcard 中的 provider 属性值
            return (T) postcard.getProvider();
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());
            return null;
        }
    }

上面代码中的completion()方法之前已经分析过了,只需要看下LogisticsCenter.buildProvider(service.getName())即可。

  public static Postcard buildProvider(String serviceName) {
        RouteMeta meta = Warehouse.providersIndex.get(serviceName);

        if (null == meta) {
            return null;
        } else {
            return new Postcard(meta.getPath(), meta.getGroup());
        }
    }

这个方法非常的简单,就是根据服务类名去仓库Warehouse.providersIndex中获去路由节点元素,然后封装在Postcard对象中。服务类清单列表Warehouse.providersIndex中的值是在初始化时缓存的。值得注意的是,PROVIDER 类型的路由节点既存在于对应的分组中,也存在于服务类清单列表中。所以,ARouter 可通过byType,byName两种方式来获取

补充

关于ARouter的基本用法上面只有*基本跳转的介绍,下面对其他一些基本使用进行下补充

  • 带参数跳转
//1.传递参数
 ARouter.getInstance().build("/test/activity1")
                        .withString("name", "老王")
                        .withInt("age", 18)
                        .withBoolean("boy", true)
                        .withLong("high", 180)
                        .withString("url", "https://a.b.c")
                        .withParcelable("pac", testParcelable)
                        .withObject("obj", testObj)
                        .navigation();

//2.直接传递Bundle
  Bundle params = new Bundle();
  ARouter.getInstance()
          .build("/test/activity1")
          .with(params)
          .navigation();

这些传参都是保存在生成的postcard对象中的mBundle属性里,然后在跳转的时候通过intent.putExtras(postcard.getExtras())达到传送参数的目的。
值得注意的是,关于对象的传递有两种,一种是withParcelable()方法,不过此方法需要传递的对象实现Parcelable接口,达到序列化的目的;另外一种是withObject()方法,此方法的原理是将实体类转换成json字符串,通过String的方式进行传递,而且使用这种方式需要实现 SerializationService,并使用@Route注解标注,下面是ARouter样例:

@Route(path = "/service/json")
public class JsonServiceImpl implements SerializationService {
    @Override
    public void init(Context context) {

    }

    @Override
    public <T> T json2Object(String text, Class<T> clazz) {
        return JSON.parseObject(text, clazz);
    }

    @Override
    public String object2Json(Object instance) {
        return JSON.toJSONString(instance);
    }
}

而且,需要在跳转到的页面获取JsonServiceImpl服务,将json字符串转换成对象。

SerializationService serializationService = ARouter.getInstance().navigation(SerializationService.class);
TestObj obj = serializationService.json2Object(getIntent().getString("obj"), TestObj.class);
  • 带返回结果跳转
ARouter.getInstance().build("/test/activity2").navigation(this, 666);

值得注意的是,这时候的 navigation需要传递activit和requestCode。

  • 获取Fragment的实例

定义一个fragment

@Route(path = "/test/fragment")
public class BlankFragment extends Fragment {
    public BlankFragment() {
        //必须要一个空的构造器
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        TextView textView = new TextView(getActivity());
        return textView;
    }

}

获取frament

Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
  • 带转场动画跳转
// 转场动画(常规方式)
 ARouter.getInstance() .build("/test/activity2")
                      .withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom)
                      .navigation(this);

// 转场动画(API16+)
 ActivityOptionsCompat compat = ActivityOptionsCompat.makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2, 0, 0);
ARouter.getInstance().build("/test/activity2").withOptionsCompat(compat) .navigation();
  • 获取服务

服务是全局单例的,只有在*次使用到的时候才会被初始化。
暴露服务,必须实现IProvider 接口 或者其子类型

// 声明接口,其他组件通过接口来调用服务
public interface HelloService extends IProvider {
    String sayHello(String name);
}

// 实现接口
@Route(path = "/service/hello", name = "测试服务")
public class HelloServiceImpl implements HelloService {

    @Override
    public String sayHello(String name) {
    return "hello, " + name;
    }

    @Override
    public void init(Context context) {

    }
}

获取服务

//bytype
HelloService helloService1 = ARouter.getInstance().navigation(HelloService.class);
//byname
HelloService helloService2 = (HelloService) ARouter.getInstance().build("/service/hello").navigation();
  • 多模块结构
%title插图%num

app中可能存在多个模块,每个模块下面都有一个root结点,每个root结点都会管理整个模块中的group节点,每个group结点则包含了该分组下的所有页面,而每个模块允许存在多个分组,每个模块中都会有一个拦截器节点就是Interceptor结点,除此之外每个模块还会有控制拦截反转的provider结点

*后

到此,关于ARouter的基本用法以及原理分析的就全部结束了,如果有不清楚或者错误的地方,希望各位同学指出。关于ARouter拦截器,各种服务,依赖注入等更多进阶用法及源码分析会更新在后续的文章。

如果各位同学认为本文对你有一些帮助,希望能点个喜欢,谢谢!

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速