Android 组件化基础(二)—— 仿 ARouter 实现一个路由框架

本篇文章还是从理解 ARouter 的架构思想和实现细节的角度出发,仿照 ARouter 实现一个简单的路由框架。如果对 ARouter 框架和组件化概念不是很熟悉,可以先看前置文章:Android 组件化基础(一)—— 概述与基本配置

由于能力有限,就仅实现三个基本功能:

  1. 路由扫描、建立路由表
  2. 通过路由跳转到目标页面
  3. 通过路由调用其它模块对外暴露的服务

拦截器等功能暂不打算实现。

一、概述

1.1 项目结构

我们计划用三个模块来完成路由框架:

  1. arouter-annotation:路由中使用的注解以及数据模型,如 @Route、@AutoWired
  2. arouter-compiler:注解处理器,扫描被注解标记的程序元素,并生成路由功能所需代码
  3. arouter-api:包含路由的功能接口、路由跳转的执行等等

画一个丑丑的示意图帮助理解:


上图通过标记依赖关系的方式梳理了项目结构,依赖就是指通过 implementation 方法依赖其它模块,api 依赖是指用 api 方法依赖其它模块,而 annotationProcessor 就是指定要使用的注解处理器模块。

关于业务模块内部的依赖关系,组件化模式中,app 不依赖 order、personal 等其它功能子模块,但是在集成化模式中,app 是需要依赖它们的,上图只是画出了组件化模式的情形。

在路由框架内部,arouter-compiler 和 arouter-api 都会用到 arouter-annotation 中定义的注解,因此都要依赖后者。而 app、order、personal、common 都会用到 arouter-annotation 中的注解,所以 arouter-api 需要通过 api 依赖的方式引入 arouter-annotation 模块。

三个业务模块需要通过注解处理器扫描被 @Route、@AutoWired 注解的程序元素,所以每个模块都要用 annotationProcessor 引入 arouter-compiler。

1.2 路由架构设计

路由表可以用一个 Map 来表示,key 是页面的 @Route 注解中指定的路径,而 value 则是该路径对应的 class 对象(或者是封装了该 class 对象的 JavaBean)。

此外,如果所有被 @Route 注解的页面全部都添加进一个 Map 中,当 Map 中的元素数量较多时,加载进内存可能占用较大的空间。考虑到这一点,将一个 Map 按照模块划分成多个并采用懒加载策略。比如 app 模块下的所有页面添加到一个 Map,而 order 模块下的所有页面添加到另一个 Map,用到哪一个模块就加载哪一个 Map,不用就不加载,一定程度上能节省内存开销。

按照以上思路设计出的路由架构图如下:

通过组文件的 Map 可以得到组名对应的 Path 路径文件的 Class 对象,通过反射可以生成 Path 路径文件,其内部 Map 记录着 <路径名,该路径对应的 Class 对象> 这个键值对,这个 class 就是我们的跳转目标,拿到它后再做进一步处理即可完成跳转,当然实际编码时 value 位置存的是一个封装了该 class 的 JavaBean。

1.3 预备知识

实现路由架构的过程中,我们需要用到 APT 和 JavaPoet 两项技术,简单介绍下。

1.3.1 AutoService

APT(Annotation Processing Tool) 是一种处理注解的工具,它对源代码文件进行检测并找出指定的注解,根据注解自动生成代码。它实际上是 javac 的一个工具:

如果想自定义的注解处理器能够正常运行,必须要通过 APT 来进行处理。也可以这样理解,只有通过声明 APT 后,程序在编译期间自定义的注解处理器才能执行。

通俗理解就是 APT 会根据规则在编译期帮我们生成代码、生成类文件。自定义 APT 需要实现 javax.annotation.processing 包下的 Processing 接口。不过实现该接口必须实现它里面所有的方法,因此通常会采用继承 AbstractProcessor 的方式来实现 APT。Google 推出的 AutoService 就是这样的一个 APT,可以帮助我们自定义一个注解处理器。它的使用方法也比较简单,首先引入依赖:

dependencies {
    compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
}

然后新建一个处理注解的类,需要继承 AbstractProcessor:

// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 支持的注解类型,参数传注解全类名的字符串即可
@SupportedAnnotationTypes(Constants.AROUTER_ANNOTATIONS_TYPES)
// 指定JDK编译版本,这里是 1.7
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 注解处理器接收的参数,这里是模块名和自动生成文件所在的包名
@SupportedOptions({Constants.MODULE_NAME, Constants.APT_PACKAGE})
public class RouteProcessor extends AbstractProcessor {
	
	/**
	* 进行初始化、接收参数等操作。
	*/
	@Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // 通过 ProcessingEnvironment 去获取 @SupportedOptions 中支持的参数
        Map<String, String> options = processingEnv.getOptions();
        if (!EmptyUtils.isEmpty(options)) {
            String moduleName = options.get(Constants.MODULE_NAME);
            String packageNameForAPT = options.get(Constants.APT_PACKAGE);
        }
    }

	/**
     * 相当于main函数,开始处理注解
     * 注解处理器的核心方法,处理具体的注解,生成Java文件
     *
     * @param annotations 使用了支持处理的注解集合,比如说本文件支持处理 @Route、@AutoWired 
     * 					  这两个注解,但是使用本处理器的模块只用了 @Route 这一个注解,那么 
     * 					  annotations 中就只有 Route。
     * @param roundEnv    当前或是之前的运行环境,可以通过该对象查找的注解。
     * @return true 表示后续处理器不会再处理(已经处理完成的意思)
     */
	@Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
		return false;
	}

	// 以下三个方法已经用类上的注解代替,所以注释掉了
    // 获取支持的注解类型,可被 @SupportedAnnotationTypes 代替
    /*@Override
    public Set<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }*/

    // 通过哪个 jdk 的版本进行编译生成 class,必填项,可被 @SupportedSourceVersion 代替
    /*@Override
    public SourceVersion getSupportedSourceVersion() {
        return super.getSupportedSourceVersion();
    }*/

    // 接收外面传进来的参数,可被 @SupportedOptions 代替
    /*@Override
    public Set<String> getSupportedOptions() {
        return super.getSupportedOptions();
    }*/
}

AutoService 会帮助我们创建 build/classes/java/main/META-INF/services/javax.annotation.processing.Processor 文件,并将自定义的注解处理器注册到该文件中:

在这里插入图片描述

以上是注解处理器模块的配置与基本使用,那么一个像 app 这样的功能模块,如果想让注解处理器工作,需要在依赖中添加如下代码:

dependencies {
	...
    // 声明要使用的注解处理器,这样 arouter-compiler 才会扫描当前模块
    annotationProcessor project(':arouter-compiler')
}

此外如果 app 模块需要给注解处理器传递参数,那么它需要:

android {
	...

    defaultConfig {
        ...
        // 传递参数给注解处理器的配置
        javaCompileOptions {
            annotationProcessorOptions {
            	// 参数 moduleName 取当前模块的名字,packageNameForAPT 是指定自动生成的 Java 代码的包名
                arguments = [moduleName: project.getName(), packageNameForAPT: packageNameForAPTString]
            }
        }
    }
}

1.3.2 JavaPoet

AutoService 使得注解处理器可以去扫描模块并获得它们的注解信息,但“生成代码”的工作其实是由 Square 公司的 JavaPoet 框架完成的。该框架十分实用,是我们习惯的 Java 面向对象的 OOP 语法,可以很方便的通过它根据注解生成对应的代码,从而以简洁优雅的方式替代繁琐冗杂的重复工作。

JavaPoet 中的常用类和符号如下:

JavaPoet 是倒序生成代码,是从较小的程序单元先开始生成,即以方法 -> 类 -> 包的顺序生成代码,比如说要生成如下的代码:

package com.example.helloworld;

public final class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, JavaPoet!");
  }
}

利用 JavaPoet 的生成方式就是:

// 生成方法
MethodSpec main = MethodSpec.methodBuilder("main")
    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
    .returns(void.class)
    .addParameter(String[].class, "args")
    .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
    .build();

// 生成类
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    .addMethod(main)
    .build();

// 添加包名,生成 JavaFile 对象
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
    .build();

// 生成文件
javaFile.writeTo(System.out);

1.3.3 Element 接口

Element 接口在 javax.lang.model.element 包下,它表示一个程序元素,如:包、类/接口、属性变量、方法/方法形参、泛型参数。常用的类与方法如下:


此外还有一个TypeMirror 接口,在 javax.lang.model.type 包下,表示的是元素的类型信息。

二、扫描并建立路由表

下面就开始结合代码看看怎样参考 ARouter 实现一个路由框架的基本功能(主要目的是增进对 ARouter 框架的理解,一些进阶功能没有实现,细节也一定会有不足之处)。

2.1 arouter-annotation 模块

首先要创建注解模块 arouter-annotation,这是一个 Java Library 模块。模块内先定义 @Route 注解:

// 该注解是编译期注解
@Retention(RetentionPolicy.CLASS)
// 该注解作用在类之上
@Target(ElementType.TYPE) 
public @interface Route {

	// 路径
    String path();

	// 路径中的组,默认为空字符串
    String group() default "";
}

@Route 的 path 就是路径信息,如 /app/MainActivity。接着再新建一个枚举类型 RouteType,它表示路由事件的类型:

/**
 * 表示路由的类型,如 ACTIVITY 表示 Activity 间跳转,PROVIDER 表示跨模块调用服务
 * 也可以扩展 SERVICE、FRAGMENT 出等类型
 */
public enum RouteType {

	// PACKAGE_NAME_ACTIVITY 是 "android.app.Activity",即 Activity 的包名
    ACTIVITY(0, Constants.PACKAGE_NAME_ACTIVITY);

    int id;
    String className;

    RouteType(int id, String className) {
        this.id = id;
        this.className = className;
    }

	// getters and setters...
}

路由框架可以处理多种类型的事件,比如 Activity 的跳转、Fragment 的跳转、启动一个 Service 或者跨模块调用服务,当前我们只处理 Activity 跳转,因此暂时只有 ACTIVITY 一种类型。

最后封装一个包含跳转的目标 class 对象的 JavaBean:

public class RouteMeta {

    // 事件类型,如 ACTIVITY 表示跳转,PROVIDER 表示跨模块调用服务
    private RouteType mRouteType;
    // 节点类型
    private Element mElement;
    // 路由组
    private String mGroup;
    // 路由地址
    private String mPath;
    // 被注解标记的类对象
    private Class<?> mTargetClass;

    private RouteMeta(RouteType routeType, String group, String path, Class<?> targetClass) {
        mRouteType = routeType;
        mGroup = group;
        mPath = path;
        mTargetClass = targetClass;
    }

    private RouteMeta(Builder builder) {
        mRouteType = builder.mRouteType;
        mElement = builder.mElement;
        mGroup = builder.mGroup;
        mPath = builder.mPath;
        mTargetClass = builder.mTargetClass;
    }

    // 对外提供简易版构造方法,主要是为了方便 APT 生成代码
    public static RouteMeta create(RouteType routeType, String group, String path, Class<?> targetClass) {
        return new RouteMeta(routeType, group, path, targetClass);
    }

    // getters and setters...

    public static class Builder {

        private RouteType mRouteType;
        private Element mElement;
        private String mGroup;
        private String mPath;
        private Class<?> mTargetClass;

        // setters...

        public RouteMeta build() {
            if (mPath == null || mPath.length() == 0) {
                throw new IllegalArgumentException("Path 为必填项,不能为空!如:/app/MainActivity");
            }
            return new RouteMeta(this);
        }
    }
}

我们在路由表中存的是这个 RouteMeta,在需要跳转时会从中取出 mTargetClass 进行反射创建出实例,再跳转。

2.2 arouter-api 模块

新建 Android Library 模块 arouter-api,先引入依赖:

dependencies {
    support.each { k, v -> implementation v }
    api project(':arouter-annotation')
}

arouter-api 是路由框架的功能模块,在当前阶段,只需要定义两个接口:

/**
 * 路由组 Group 加载数据接口
 */
public interface ILoadGroup {

    /**
     * 加载路由组 Group 数据
     * 比如:"app", ARouter$$Path$$app.class(实现了 ILoadPath 接口)
     *
     * @return key:"app", value:"app" 分组对应的路由详细对象类
     */
    Map<String, Class<? extends ILoadPath>> loadGroup();
}
/**
 * 路由组 Group 对应的详细 Path 加载数据接口
 * 比如:app 分组对应有哪些类需要加载
 */
public interface ILoadPath {

    /**
     * 加载路由组 Group 中的 Path 详细数据
     * 比如:"app" 分组下有这些信息:
     *
     * @return key:"/app/MainActivity", value:封装了MainActivity信息的RouteMeta
     */
    Map<String, RouteMeta> loadPath();
}

两个接口分别用来加载路由组 Group 的路由数据、某个 Group 下详细的 Path 路由信息。

2.3 arouter-compiler 模块

准备就绪,下面正式进入注解处理器部分。

2.3.1 模块配置

新建 Java Library 模块 arouter-compiler,build.gradle 配置如下:

apply plugin: 'java-library'

dependencies {
    // AutoService
    compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'

    // 注解模块
    implementation project(":arouter-annotation")

    // JavaPoet
    implementation 'com.squareup:javapoet:1.9.0'

    // 工具类
    implementation 'org.apache.commons:commons-lang3:3.5'
    implementation 'org.apache.commons:commons-collections4:4.1'
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}

先建一个基类处理器 BaseProcessor,把一些公共操作抽出来:

public abstract class BaseProcessor extends AbstractProcessor {

    // 操作Element工具类(类、函数、属性都是Element)
    Elements elementUtils;

    // type(信息类)工具类 包含用于操作TypeMirror的工具方法
    Types typeUtils;

    // 用来输出警告、错误等日志
    Messager messager;

    // 文件生成器 类/资源,Filter用来创建新的类文件,class文件以及辅助文件
    Filer filer;

    // 模块名,由模块在 build.gradle 中通过配置 annotationProcessorOptions 传进来
    String moduleName;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        elementUtils = processingEnv.getElementUtils();
        typeUtils = processingEnv.getTypeUtils();
        messager = processingEnv.getMessager();
        filer = processingEnv.getFiler();

        // 获取模块名
        Map<String, String> options = processingEnv.getOptions();
        if (MapUtils.isNotEmpty(options)) {
            moduleName = options.get(Constants.KEY_MODULE_NAME);
        }

        if (StringUtils.isNotEmpty(moduleName)) {
            // 将模块名中的字符清理掉,并输出编译信息
            moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", "");
            messager.printMessage(Diagnostic.Kind.NOTE,
                    "The user has configuration the module name, it was [\" + moduleName + \"]");
        } else {
            // 如果没有配置模块名,就输出错误信息并抛异常。
            // messager 输出类型为 ERROR 的话编译会直接停掉。
            messager.printMessage(Diagnostic.Kind.ERROR, Constants.NO_MODULE_NAME_TIPS);
            throw new RuntimeException("\"ARouter::Compiler >>> No module name, for more information, look at gradle log.\"");
        }
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public Set<String> getSupportedOptions() {
        // 返回支持的注解参数的 key
        HashSet<String> hashSet = new HashSet<>();
        hashSet.add(Constants.KEY_MODULE_NAME);
        return hashSet;
    }
}

2.3.2 RouteProcessor 收集并解析 @Route 信息

然后建一个 RouteProcessor 专门对 @Route 进行处理:

@AutoService(Processor.class)
@SupportedAnnotationTypes(Constants.ANNOTATION_TYPE_ROUTE)
public class RouteProcessor extends BaseProcessor {

    // 扫描注解后临时保存 RouteMeta 的 Map,key 是组名
    private Map<String, List<RouteMeta>> mTempPathMap = new HashMap<>();

    // 存放生成 Group 文件所需信息的临时 Map,<组名,Path 文件名>
    private Map<String, String> mTempGroupMap = new HashMap<>();

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 如果被扫描的模块没有使用本处理器支持的注解,就无需处理了
        if (CollectionUtils.isEmpty(annotations)) return false;

        // 拿到所有被 Route 注解修饰的程序元素,由于 @Route 只作用在
        // 类上,所以 Set 中其实都是类元素
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Route.class);
        if (CollectionUtils.isNotEmpty(elements)) {
            parseElement(elements);
        }
        return true;
    }
}

解析 elements 的工作有二:

  1. 为被 @Route 修饰的 Activity 生成 RouteMeta 并按组分类存入临时 Map 中
  2. 从上一步的 Map 中取出信息生成 Group 与 Path 路由文件
	// 扫描注解后临时保存 RouteMeta 的 Map,key 是组名
    private Map<String, List<RouteMeta>> mTempPathMap = new HashMap<>();

	/**
     * 解析 elements 集合,先将每个 element 解析成 RouteMeta 存入临时
     * Map 中,然后再遍历临时 Map,生成路由信息
     */
    private void parseElement(Set<? extends Element> elements) {
        // 获取 Activity 类对应的 Element 和 TypeMirror 对象
        TypeElement activityType = elementUtils.getTypeElement(RouteType.ACTIVITY.getClassName());
        TypeMirror activityMirror = activityType.asType();

        for (Element element : elements) {
            TypeMirror elementMirror = element.asType();
            Route route = element.getAnnotation(Route.class);
            RouteMeta.Builder builder = new RouteMeta.Builder();

            // 判断 element 的类型,如 RouteType.ACTIVITY 等
            if (typeUtils.isSubtype(elementMirror, activityMirror)) {
                messager.printMessage(Diagnostic.Kind.NOTE, "Found activity route:" + elementMirror.toString());
                builder.setRouteType(RouteType.ACTIVITY);
            } else {
                throw new RuntimeException("The @Route is marked on unsupported class, look at [" + elementMirror.toString() + "].");
            }

            RouteMeta routeMeta = builder.setElement(element)
                    .setPath(route.path())
                    .setGroup(route.group())
                    .build();

            addToTempMap(routeMeta);
        }

        // 扫描结束后,从临时 Map 里取出 RouteMeta 创建路由文件。
        // 注意,这里需要 app、order 等功能模块依赖 arouter-api,否则就找不到这些接口
        TypeElement pathLoadType = elementUtils.getTypeElement(Constants.IROUTE_PATH);
        TypeElement groupLoadType = elementUtils.getTypeElement(Constants.IROUTE_GROUP);

        try {
            createPathFile(pathLoadType);
            createGroupFile(groupLoadType, pathLoadType);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

将 RouteMeta 添加到 Map 时需要对 path 的合法性进行检查:

	private void addToTempMap(RouteMeta routeMeta) {
        if (checkRouterPath(routeMeta)) {
            String group = routeMeta.getGroup();
            List<RouteMeta> metaList = mTempPathMap.get(group);
            if (metaList == null) {
                metaList = new ArrayList<>();
                metaList.add(routeMeta);
                mTempPathMap.put(group, metaList);
            } else {
                metaList.add(routeMeta);
            }
        }
    }

    /**
     * 判断路径的合法性,并确定组名(因为 @Route 中组名可以不填,但是到这步要确定)
     */
    private boolean checkRouterPath(RouteMeta routeMeta) {
        String path = routeMeta.getPath();
        String group = routeMeta.getGroup();
        if (StringUtils.isEmpty(path) || !path.startsWith("/")) {
            messager.printMessage(Diagnostic.Kind.ERROR,
                    "The value of path in @Route is empty or not start with /");
            return false;
        }

        if (path.lastIndexOf("/") == 0) {
            messager.printMessage(Diagnostic.Kind.ERROR,
                    "The value of path in @Route is Illegal,such as /app/MainActivity.");
            return false;
        }

        String finalGroup = path.substring(1, path.indexOf("/", 1));
        if (finalGroup.contains("/")) {
            messager.printMessage(Diagnostic.Kind.ERROR,
                    "The value of path in @Route is Illegal,such as /app/MainActivity.");
            return false;
        }

        // 如果 @Route 通过 group() 设置了组名,该组名必须与 path() 中截取的组名相同。
        if (StringUtils.isNotEmpty(group) && !group.equals(finalGroup)) {
            messager.printMessage(Diagnostic.Kind.ERROR,
                    "Group name in group() and path() should be same!");
            return false;
        }

        routeMeta.setGroup(finalGroup);
        return true;
    }

2.3.3 生成 Path 路由文件

要生成的 Path 路由文件模板如下:

public class ARouter$$Path$$app implements ILoadPath {
  @Override
  public Map<String, RouteMeta> loadPath() {
    Map<String,RouteMeta> pathMap = new HashMap<>();
    pathMap.put("/app/MainActivity",RouteMeta.create(RouteType.ACTIVITY,"app","/app/MainActivity",MainActivity.class));
    return pathMap;
  }
}

生成 Path 路由文件的方法:

	/**
     * 创建 Path 路由文件
     */
    private void createPathFile(TypeElement pathLoadType) throws IOException {
        if (MapUtils.isEmpty(mTempPathMap)) return;

        for (Map.Entry<String, List<RouteMeta>> entry : mTempPathMap.entrySet()) {
            // 方法返回值类型 Map<String, RouterBean>
            TypeName methodReturns = ParameterizedTypeName.get(
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ClassName.get(RouteMeta.class));

            // 配置方法以及第一个语句 Map<String, RouteMeta> pathMap = new HashMap<>();
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constants.METHOD_LOAD_PATH)
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Override.class)
                    .returns(methodReturns)
                    .addStatement("$T<$T,$T> $N = new $T<>()",
                            ClassName.get(Map.class),
                            ClassName.get(String.class),
                            ClassName.get(RouteMeta.class),
                            Constants.VARIABLE_PATH_MAP,
                            ClassName.get(HashMap.class));

            // 遍历 List<RouteMeta> 生成 pathMap.put("/app/MainActivity",
            // RouteMeta.create(RouteType.ACTIVITY,"app","/app/MainActivity",MainActivity.class));
            for (RouteMeta routeMeta : entry.getValue()) {
                methodBuilder.addStatement("$N.put($S,$T.create($T.$L,$S,$S,$T.class))",
                        Constants.VARIABLE_PATH_MAP,
                        routeMeta.getPath(),
                        ClassName.get(RouteMeta.class),
                        ClassName.get(RouteType.class),
                        routeMeta.getRouteType(),
                        routeMeta.getGroup(),
                        routeMeta.getPath(),
                        ClassName.get((TypeElement) routeMeta.getElement()));
            }

            methodBuilder.addStatement("return $N", Constants.VARIABLE_PATH_MAP);

            // 生成类节点
            String pathFileName = Constants.PREFIX_OF_PATH_NAME + entry.getKey();
            TypeSpec typeSpec = TypeSpec.classBuilder(pathFileName)
                    .addModifiers(Modifier.PUBLIC)
                    .addSuperinterface(ClassName.get(pathLoadType))
                    .addMethod(methodBuilder.build())
                    .build();

            // 生成文件,包名由一个常量指定
            JavaFile.builder(Constants.PACKAGE_OF_GENERATE_FILE, typeSpec)
                    .build()
                    .writeTo(filer);

            // 非常重要一步!!!路径文件生成后,才能赋值路由组 mTempGroupMap
            mTempGroupMap.put(entry.getKey(), pathFileName);
        }
    }

2.3.4 生成 Group 路由文件

要生成的 Group 路由文件模板如下:

public class ARouter$$Group$$app implements ILoadGroup {
  @Override
  public Map<String, Class<? extends ILoadPath>> loadGroup() {
    Map<String,Class<? extends ILoadPath>> pathMap = new HashMap<>();
    pathMap.put("app",ARouter$$Path$$app.class);
    return pathMap;
  }
}

生成 Group 路由文件的方法:

	/**
     * 创建 Group 路由文件
     */
    private void createGroupFile(TypeElement groupLoadType, TypeElement pathLoadType) throws IOException {
        // 返回值类型 Map<String, Class<? extends ILoadPath>>
        TypeName returnType = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(pathLoadType))));

        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constants.METHOD_LOAD_GROUP)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .returns(returnType);

        // Map<String,Class<? extends ILoadPath>> pathMap = new HashMap<>();
        methodBuilder.addStatement("$T<$T,$T> $N = new $T<>()",
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(pathLoadType))),
                Constants.VARIABLE_PATH_MAP,
                ClassName.get(HashMap.class));

        // pathMap.put("app",ARouter$$Path$$app.class);
        for (Map.Entry<String, String> entry : mTempGroupMap.entrySet()) {
            methodBuilder.addStatement("$N.put($S,$T.class)",
                    Constants.VARIABLE_PATH_MAP,
                    entry.getKey(),
                    ClassName.get(Constants.PACKAGE_OF_GENERATE_FILE,entry.getValue()));
        }

        methodBuilder.addStatement("return $N", Constants.VARIABLE_PATH_MAP);

        // 创建类对象
        String groupFileName = Constants.PREFIX_OF_GROUP_NAME + moduleName;
        TypeSpec typeSpec = TypeSpec.classBuilder(groupFileName)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(methodBuilder.build())
                .addSuperinterface(ClassName.get(groupLoadType))
                .build();

        // 创建文件
        JavaFile.builder(Constants.PACKAGE_OF_GENERATE_FILE, typeSpec)
                .build()
                .writeTo(filer);
    }

两个路由文件生成的路径是在模块下的 build/generated/ap_generated_sources/debug/out/[packageName] 下:


这样路由表就建好了。

三、通过路由跳转到目标页面

接下来我们要实现页面的跳转了,先回顾一下 ARouter 框架是如何进行跳转的:

	ARouter.getInstance().build("/order/Order_MainActivity")
                .withString("key1", "TestString")
                .withInt("key2", 66)
                .navigation();

我们也要仿照这种调用形式。在 arouter-api 模块下新建单例类 ARouter:

public class ARouter {
	
	private static volatile ARouter sInstance;
	// 缓存
	private LruCache<String, ILoadGroup> mGroupCache;
    private LruCache<String, ILoadPath> mPathCache;

	private ARouter() {
		mGroupCache = new LruCache<>(100);
        mPathCache = new LruCache<>(100);
    }

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

build() 要对传入的路径进行检查,并从路径中截取出组名:

	private String mGroup;
    private String mPath;

	public BundleManager build(String path) {
        if (TextUtils.isEmpty(path)) {
            throw new IllegalArgumentException("Path should not be null!");
        }

        mPath = path;
        mGroup = extractGroup(path);

        return new BundleManager();
    }

    private String extractGroup(String path) {
        if (path.lastIndexOf("/") == 0) {
            throw new IllegalArgumentException("Please input a correct path ,such as:/app/MainActivity");
        }

        String group = path.substring(1, path.indexOf("/", 1));
        if (TextUtils.isEmpty(group)) {
            throw new IllegalArgumentException("Please input a correct path ,such as:/app/MainActivity");
        }
        return group;
    }

BundleManager 负责维护参数,在最终调用 navigation() 执行跳转操作时,要回到 ARouter 中:

public class BundleManager {

    private Bundle mBundle;

    public BundleManager() {
        mBundle = new Bundle();
    }

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

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

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

    public BundleManager withBundle(@NonNull Bundle bundle) {
        mBundle = bundle;
        return this;
    }

    // 更多类型自己扩展...

    public Bundle getBundle() {
        return mBundle;
    }

    public Object navigation(Context context, int requestCode) {
        return ARouter.getInstance().navigation(context, this, requestCode);
    }

    public Object navigation(Context context) {
        return navigation(context, -1);
    }
}

ARouter 的 navigation() 来执行寻址和跳转工作,其实就是根据 build() 传进来的路径去路由表中找到对应的 RouteMeta,再拿出 Class 对象通过 startActivity() 执行跳转:

	// 拼接文件全类名时用到的常量
	public static final String SEPARATOR = "$$";
    public static final String PROJECT = "ARouter";
    public static final String PREFIX_OF_GROUP_NAME = PROJECT + SEPARATOR + "Group" + SEPARATOR;
    public static final String PACKAGE_OF_GENERATE_FILE = "com.demo.arouter.routes";

	// 缓存
	private Map<String, ILoadGroup> mGroupMap = new HashMap<>();
    private Map<String, ILoadPath> mPathMap = new HashMap<>();

	public BundleManager build(String path) {
        if (TextUtils.isEmpty(path)) {
            throw new IllegalArgumentException("Path should not be null!");
        }

        mPath = path;
        mGroup = extractGroup(path);

        return new BundleManager();
    }

    /**
     * 从 path 中提取出默认的 group,如果 @Route 中没填 group(),
     * 那么默认 group 就作为最终的路由组
     */
    private String extractGroup(String path) {
        if (!path.startsWith("/") || path.lastIndexOf("/") == 0) {
            throw new IllegalArgumentException("Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!");
        }

        String defaultGroup = path.substring(1, path.indexOf("/", 1));
        if (TextUtils.isEmpty(defaultGroup)) {
            throw new IllegalArgumentException("Extract the default group failed! There's nothing between 2 '/'!");
        }
        return defaultGroup;
    }

    /**
     * 根据组名去 Group Map 里找到对应的 Path 的 Class 对象,通过反射拿到该 Class
     * 对应的实例,再从 Map 中根据路径找到对应的 RouteMeta,拿出 Class 做跳转
     */
    public Object navigation(Context context, BundleManager bundleManager, int requestCode) {
        try {
            // Group 路由文件的全类名
            String groupFileName = PACKAGE_OF_GENERATE_FILE + "." + PREFIX_OF_GROUP_NAME + mGroup;
            // 先去缓存中找 Group 路由文件,缓存没有则通过反射获取
            ILoadGroup loadGroup = mGroupCache.get(groupFileName);
            if (loadGroup == null) {
                loadGroup = (ILoadGroup) Class.forName(groupFileName).newInstance();
                mGroupCache.put(groupFileName, loadGroup);
            }

            if (loadGroup.loadGroup() == null) {
                throw new RuntimeException("Group route table loads failed.");
            }

            // 类似的过程,去获取 Path 路由文件
            ILoadPath loadPath = mPathCache.get(mPath);
            if (loadPath == null) {
                loadPath = loadGroup.loadGroup().get(mGroup).newInstance();
                mPathCache.put(mPath, loadPath);
            }

            Map<String, RouteMeta> pathMap = loadPath.loadPath();
            if (pathMap == null) {
                throw new RuntimeException("Path route table loads failed.");
            }

            RouteMeta routeMeta = pathMap.get(mPath);
            if (routeMeta != null) {
                switch (routeMeta.getRouteType()) {
                    case ACTIVITY:
                        Intent intent = new Intent(context, routeMeta.getTargetClass());
                        intent.putExtras(bundleManager.getBundle());
                        if (requestCode > 0) {
                            ((Activity) context).startActivityForResult(intent, requestCode);
                        } else {
                            context.startActivity(intent);
                        }
                        break;
                    // todo 其它类型处理待添加...
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

到这里 Activity 的跳转功能就基本完成了,可以增加几个子模块帮助测试,新模块基本配置大致如下:

// 切换集成化环境与组件化环境
if (isRelease) {
    apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}

def androidId = rootProject.ext.androidId
def appId = rootProject.ext.appId
def support = rootProject.ext.dependencies

android {
	compileSdkVersion androidId.compileSdkVersion
    buildToolsVersion androidId.buildToolsVersion

    defaultConfig {
        // 只有组件化环境时才需要赋予 applicationId
        if (!isRelease) {
            applicationId appId.order
        }
        minSdkVersion androidId.minSdkVersion
        targetSdkVersion androidId.targetSdkVersion
        versionCode androidId.versionCode
        versionName androidId.versionName

		// 给注解处理器传参
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
	}
	
	// 源集区分不同环境下使用的 AndroidManifest
	sourceSets {
        main {
            if (!isRelease) {
                // 测试版本,组件化环境,让 /main/debug/ 下的 AndroidManifest 生效
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            } else {
                // 正式版本,集成化环境,让 main 下的 AndroidManifest 生效
                manifest.srcFile 'src/main/AndroidManifest.xml'

                java {
                    // release 时 debug 目录下的文件不需要合并到主工程中,减小 apk 体积
                    exclude '**/debug/**'
                }
            }
        }
    }
}
    
dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    support.each { k, v -> implementation v }
    // 依赖 common 库,common 已经通过 api 依赖 arouter-api
    implementation project(":lib-common")
    // 声明所使用的注解处理器
    annotationProcessor project(":arouter-compiler")
}

跳转代码:

	public void jumpToOrder(View view) {
        ARouter.getInstance().build("/order/Order_MainActivity")
                .withString("key","value")
                .navigation(this);
    }

四、参数自动赋值

@AutoWired 注解可以自动为成员赋值,其实也是通过注解处理器生成代码,在代码中替开发者做了成员赋值这件事。实现方法其实是参考了 ButterKnife 框架的实现思想,先来个注解的定义:

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface Autowired {
    String name() default "";
}

注解处理器还是要根据注解生成一个文件,在该文件中给使用了 @Autowired 注解的字段进行赋值操作。比如说被启动 Activity 这样添加注解:

@Route(path = "/order/Order_MainActivity")
public class Order_MainActivity extends AppCompatActivity {

    @Autowired
    String name;

    @Autowired(name = "agex")
    int age = 6;

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

		// 给当前 Activity 执行依赖注入
        ARouter.getInstance().inject(this);
        Log.d("Test", "name: " + name + ",age: " + age);
    }
}

那么我们要生成的文件大概是这个样子的:

public class Order_MainActivity$$Parameter implements ILoadParameter {
  @Override
  public void loadParameter(Object target) {
    Order_MainActivity t = (Order_MainActivity) target;
    t.name = t.getIntent().getStringExtra("name");
    t.age = t.getIntent().getIntExtra("agex",t.age);
  }
}

ILoadParameter 接口还是定义在功能模块 arouter-api 中,其中的 loadParameter() 就是负责给字段赋值的:

public interface ILoadParameter {

    /**
     * 通过以下方式完成赋值:
     * 目标对象.属性名 = getIntent().属性类型("注解值or属性名");
     *
     * @param target 目标对象,如:MainActivity
     */
    void loadParameter(Object target);
}

新建一个注解处理器类 ParameterProcessor:

@AutoService(Processor.class)
@SupportedAnnotationTypes(Constants.ANNOTATION_TYPE_AUTOWIRED)
public class ParameterProcessor extends BaseProcessor {

    // key:类节点,value:被 @Autowired 注解的属性集合
    private Map<TypeElement, List<Element>> mTempParameterMap = new HashMap<>();

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (CollectionUtils.isEmpty(annotations)) return false;

        // 获取所有被 @Autowired 注解的属性集合
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Autowired.class);
        if (CollectionUtils.isNotEmpty(elements)) {
            try {
                // 解析元素,存入 mTempParameterMap 这个临时 Map 中
                parseElements(elements);
                // 根据 mTempParameterMap 的信息生成文件
                createParameterFile();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return true;
    }

    /**
     * 把被 @Autowired 注解的成员信息存入临时 Map
     */
    private void parseElements(Set<? extends Element> elements) {
        for (Element element : elements) {
            // element 是被 @Autowired 修饰的属性,获取属性的上级元素,
            // 即类, TypeElement 作为 Map 的 key
            TypeElement typeElement = (TypeElement) element.getEnclosingElement();
            if (mTempParameterMap.containsKey(typeElement)) {
                mTempParameterMap.get(typeElement).add(element);
            } else {
                List<Element> fieldList = new ArrayList<>();
                fieldList.add(element);
                mTempParameterMap.put(typeElement, fieldList);
            }
        }
    }

    private void createParameterFile() throws IOException {
        if (MapUtils.isEmpty(mTempParameterMap)) return;

        TypeElement activityElement = elementUtils.getTypeElement(Constants.ACTIVITY);
        TypeElement parameterElement = elementUtils.getTypeElement(Constants.IPARAMETER);
        // 方法参数
        ParameterSpec parameterSpec = ParameterSpec.builder(TypeName.OBJECT, Constants.PARAMETER_NAME).build();

        for (Map.Entry<TypeElement, List<Element>> entry : mTempParameterMap.entrySet()) {
            TypeElement typeElement = entry.getKey();

            // 限制 @Autowired 当前只能用在 Activity 内的属性之上
            if (!typeUtils.isSubtype(typeElement.asType(), activityElement.asType())) {
                throw new RuntimeException("@Autowired only works on Activity now!");
            }

            ClassName className = ClassName.get(typeElement);
            // 通过 ParameterFactory 生成方法体内容
            ParameterFactory parameterFactory = new ParameterFactory.Builder(parameterSpec)
                    .setClassName(className)
                    .setMessager(messager)
                    .build();

            // 添加方法体内容第一行
            parameterFactory.addFirstStatement();

            // 遍历所有属性,生成对应的语句
            for (Element element : entry.getValue()) {
                parameterFactory.buildStatement(element);
            }

            // 生成类
            String fileName = typeElement.getSimpleName() + Constants.SUFFIX_OF_PARAMETER_FILE;
            messager.printMessage(Diagnostic.Kind.NOTE, "APT生成获取参数类文件:" +
                    className.packageName() + "." + fileName);

            TypeSpec typeSpec = TypeSpec.classBuilder(fileName)
                    .addModifiers(Modifier.PUBLIC)
                    .addSuperinterface(ClassName.get(parameterElement))
                    .addMethod(parameterFactory.build())
                    .build();

            JavaFile.builder(typeElement.getEnclosingElement().toString(), typeSpec)
                    .build()
                    .writeTo(filer);
        }
    }
}

ParameterFactory 用来生成方法内的各个语句:

public class ParameterFactory {

    private static final String CONTENT = "$T t = ($T) target";

    private MethodSpec.Builder methodBuilder;
    private ClassName className;
    private Messager messager;
    private ParameterSpec parameterSpec;

    private ParameterFactory(Builder builder) {
        this.className = builder.className;
        this.messager = builder.messager;
        this.parameterSpec = builder.parameterSpec;

        methodBuilder = MethodSpec.methodBuilder(Constants.METHOD_LOAD_PARAMETER)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(parameterSpec)
                .addAnnotation(Override.class);
    }

    /**
     * 添加方法体内容的第一行:MainActivity t = (MainActivity) target;
     */
    public void addFirstStatement() {
        methodBuilder.addStatement(CONTENT, className, className);
    }

    /**
     * 构建方体内容,如:t.s = t.getIntent.getStringExtra("s");
     *
     * @param element 被注解的属性元素
     */
    public void buildStatement(Element element) {
        TypeMirror typeMirror = element.asType();
        // 获取 TypeKind 枚举类型的序列号
        int type = typeMirror.getKind().ordinal();
        // 获取属性名
        String fieldName = element.getSimpleName().toString();
        // 获取属性值,如果 @Autowired 的 name() 为空,则采用用属性名
        Autowired autowired = element.getAnnotation(Autowired.class);
        String annotationValue = StringUtils.isEmpty(autowired.name()) ?
                fieldName : autowired.name();
        // 最终拼接的前缀
        String finalValue = "t." + fieldName;
        String methodContent = finalValue + " = t.getIntent().";

        // 根据成员类型拼接 methodContent 后面的内容
        if (TypeKind.INT.ordinal() == type) {
            // finalValue 字符串作为 getIntExtra() 中的默认值部分,因为被
            // 注解的属性可能有初始值。
            methodContent += "getIntExtra($S," + finalValue + ")";
        } else if (TypeKind.BOOLEAN == typeMirror.getKind()) {
            // t.fieldName = t.getIntent.getStringExtra("s",t.fieldName);
            methodContent += "getBooleanExtra($S," + finalValue + ")";
        } else {
            // TypeKind 中没有定义字符串类型,需要做特殊处理
            if (typeMirror.toString().equalsIgnoreCase(Constants.STRING)) {
                // t.s = t.getIntent.getStringExtra("s");
                methodContent += "getStringExtra($S)";
            }
        }

        if (methodContent.endsWith(")")) {
            methodBuilder.addStatement(methodContent, annotationValue);
        } else {
            messager.printMessage(Diagnostic.Kind.ERROR, "Just support String、int、boolean type temporarily.");
        }
    }

    public MethodSpec build() {
        return methodBuilder.build();
    }

    public static class Builder {
    	// ...
    }
}

最后在 ARouter 中添加 inject 方法,执行依赖注入:

	public static final String SUFFIX_OF_PARAMETER_FILE = "$$Parameter";

	private LruCache<String, ILoadParameter> mParameterCache;

	private ARouter() {
        ...
        mParameterCache = new LruCache<>(100);
    }
	
	public void inject(Activity activity) {
        String activityName = activity.getClass().getName();
        ILoadParameter iLoadParameter = mParameterCache.get(activityName);
        try {
            if (iLoadParameter == null) {
                // 缓存中没有,就要通过反射拿到接口实现类
                String parameterFileName = activityName + SUFFIX_OF_PARAMETER_FILE;
                iLoadParameter = (ILoadParameter) Class.forName(parameterFileName).newInstance();
                mParameterCache.put(activityName, iLoadParameter);
            }

            iLoadParameter.loadParameter(activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

五、跨模块调用服务

先上一幅图帮助梳理思路:

图中黑色线表示项目的静态配置,如接口的继承实现关系,还有编译时生成路由表等操作;红色线表示调用者跨模块调用服务的整个过程,已经用序号标出。

图中示例是 personal 模块要调用 order 模块的服务,由于组件化后各个业务模块之间没有依赖,那么想要直接调用其它模块的服务是不可能的,只能迂回操作,让 order 模块主动暴露要对外提供的服务,并将其用 @Route 修饰,在编译时添加到路由表中。而 personal 模块作为调用方需要知道调用服务的路径,并将该路径传给 ARouter 去路由表中查找从而得到服务的实现类,最后执行服务调用。

以上是整体思路,下面来看实际操作。

5.1 增加接口与 RouteMeta 类型

如图所示,arouter-api 模块中要新建一个 IProvider 接口:

public interface IProvider {
}

它就是一个空接口,表示一种可以被跨模块调用的能力。common 模块中定义的具体的业务接口都需要继承 IProvider,最后再由业务模块中的实现类去实现那个接口,这个过程在后面的测试代码中会有体现。

接下来要处理实现类被添加到路由表中的过程。其实前面在做 Activity 跳转时已经将大部分逻辑都处理了,现在只需要针对跨模块调用服务这种事件类型增加相应的处理即可。先增加事件类型 PROVIDER:

public enum RouteType {
	ACTIVITY(0, Constants.PACKAGE_NAME_ACTIVITY),
    PROVIDER(1,Constants.PACKAGE_NAME_IPROVIDER);
}

RouteProcessor 在扫描被 @Route 修饰的类时,如果其类型是 RouteType.PROVIDER,那么需要给 RouteMeta 的 routeType 属性设置为 RouteType.PROVIDER:

	private void parseElement(Set<? extends Element> elements) {
		// 获取 Activity 类对应的 Element 和 TypeMirror 对象
        TypeElement activityType = elementUtils.getTypeElement(RouteType.ACTIVITY.getClassName());
        TypeElement providerType = elementUtils.getTypeElement(RouteType.PROVIDER.getClassName());
        TypeMirror activityMirror = activityType.asType();

        for (Element element : elements) {
            TypeMirror elementMirror = element.asType();
            Route route = element.getAnnotation(Route.class);
            RouteMeta.Builder builder = new RouteMeta.Builder();

            // 判断 element 的类型,如 RouteType.ACTIVITY、RouteType.PROVIDER 等
            if (typeUtils.isSubtype(elementMirror, activityMirror)) {
                messager.printMessage(Diagnostic.Kind.NOTE, "Found activity route:" + elementMirror.toString());
                builder.setRouteType(RouteType.ACTIVITY);
            } else if (typeUtils.isSubtype(elementMirror, providerType.asType())) {
            	// 新增代码,对 RouteType.PROVIDER 类型的处理
                messager.printMessage(Diagnostic.Kind.NOTE, "Found activity route:" + elementMirror.toString());
                builder.setRouteType(RouteType.PROVIDER);
            } else {
                throw new RuntimeException("The @Route is marked on unsupported class, look at [" + elementMirror.toString() + "].");
            }

            RouteMeta routeMeta = builder.setElement(element)
                    .setPath(route.path())
                    .setGroup(route.group())
                    .build();

            addToTempMap(routeMeta);
	}

5.2 从路由表取出信息并执行

ARouter 去路由表中查找路径对应的 RouteMeta,这个逻辑在处理 Activity 跳转时已经实现了,现在 ARouter 只需要把拿到的 RouteMeta 的 Class 对象的实例返回给调用者即可:

	/**
     * 根据组名去 Group Map 里找到对应的 Path 的 Class 对象,通过反射拿到该 Class
     * 对应的实例,再从 Map 中根据路径找到对应的 RouteMeta,拿出 Class 做跳转
     */
    public Object navigation(Context context, BundleManager bundleManager, int requestCode) {
        try {
            // 从路由中拿到 RouteMeta 的过程前面写过,省略了...
            RouteMeta routeMeta = pathMap.get(mPath);
            if (routeMeta != null) {
                switch (routeMeta.getRouteType()) {
                    case ACTIVITY:
                        // ...
                        break;
                    // 返回 Class 对象实例
                    case PROVIDER:
                        return routeMeta.getTargetClass().newInstance();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

这样调用方拿到接口实现类的实例后就可以调用相应服务了,为了调用方使用更方便,可以让 @Autowired 修饰接口成员,在 ParameterProcessor 生成的代码中执行接口赋值操作:

public class ParameterFactory {

	private Types typeUtils;
    private TypeMirror providerType;

	private ParameterFactory(Builder builder) {
        this.className = builder.className;
        this.messager = builder.messager;
        this.typeUtils = builder.typeUtils;

        methodBuilder = MethodSpec.methodBuilder(Constants.METHOD_LOAD_PARAMETER)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(builder.parameterSpec)
                .addAnnotation(Override.class);
		// Constants.IPROVIDER 是 IProvider 接口的全类名 
        providerType = builder.elementUtils.getTypeElement(Constants.IPROVIDER).asType();
    }
    
    public void buildStatement(Element element) {
		//....
		if (TypeKind.INT.ordinal() == type) {
            methodContent += "getIntExtra($S," + finalValue + ")";
        } else if (TypeKind.BOOLEAN == typeMirror.getKind()) {
            methodContent += "getBooleanExtra($S," + finalValue + ")";
        } else {
            if (typeMirror.toString().equalsIgnoreCase(Constants.STRING)) {
                methodContent += "getStringExtra($S)";
            }
			// 增加接口赋值语句的处理
            if (typeUtils.isSubtype(typeMirror, providerType)) {
                // t.orderService = (IOrderService) ARouter.getInstance().build("/order/OrderServiceImpl").navigation(t);
                methodContent = finalValue+" = ($T) $T.getInstance().build($S).navigation(t)";
                methodBuilder.addStatement(methodContent,
                        ClassName.get(typeMirror),
                        ClassName.get(Constants.MANAGER_PACKAGE,Constants.PROJECT),
                        annotationValue);
                return;
            }
        }
	}
}

这样生成的参数赋值文件如下:

public class Personal_MainActivity$$Parameter implements ILoadParameter {
  @Override
  public void loadParameter(Object target) {
    Personal_MainActivity t = (Personal_MainActivity) target;
    t.orderService = (IOrderService) ARouter.getInstance().build("/order/OrderServiceImpl").navigation(t);
  }
}

到这针对跨模块调用的修改就基本完成了。

5.3 测试

在 common 模块中定义一个 IOrderService 接口,这是具体的模块要对外暴露的业务接口:

public interface IOrderService extends IProvider {

    /**
     * @return 订单数量
     */
    int getOrderCount();
}

然后 Order 模块要实现这个接口,并且要将该实现类通过 @Route 添加到路由中:

@Route(path = "/order/OrderServiceImpl")
public class OrderServiceImpl implements IOrderService {

    @Override
    public int getOrderCount() {
        return 666;
    }
}

最后在调用者模块中给接口成员添加 @Autowired 注解并标明要调用接口的路径:

@Route(path = "/personal/Personal_MainActivity")
public class Personal_MainActivity extends AppCompatActivity {

    @Autowired(name = "/order/OrderServiceImpl")
    IOrderService orderService;

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

        ARouter.getInstance().inject(this);
        Toast.makeText(this, "订单数量:" + orderService.getOrderCount(), Toast.LENGTH_LONG).show();
    }
}

测试效果:


使用这种方式不仅可以像上面这样传输一个简单的 Int 值,也可以传资源 ID 或者一个引用类型对象等。

项目代码已经上传 GitHub:ARouterDemo

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值