细嚼慢咽之ARouter

1,246 阅读25分钟

因为公司刚好准备开发一个公司内部使用的页面路由框架,抽空看了下目前市面上的几个开源框架。这篇文章主要分析阿里的ARuoter框架,整理成笔记,以便后面当作工具文章查阅。

文章的出发点是:分析ARouter整体框架是怎么设计,使用了哪些技术,针对使用到的技术,掌握怎么使用。 因此文章分为两个部分:

  1. 基础分析,包括Gradle基础、注解、注解处理器、JavaPoet、ASM等,这部分仅限点到为止,懂的怎么回事就好
  2. ARouter源码分析,包括总体的框架、各个子流程分析等

基础篇

Gradle 基础

1.1 自定义插件

自定义gradle插件一共有三种方式:Build script、 buildSrc project、Standalone project。

1.1.1 Build script

这种插件脚本的源码放置在模块内的 build.gradle 中,好处就是插件脚本会被自动编译并添加进模块的 classpath 中,开发者不用做其他事情。但是,插件脚本只能在声明的这个 build.gradle 中使用,其他模块是没办法复用这个插件的

class TestPlugin implements Plugin<Project> { 
    void apply(Project project) { 
        project.task('test') {//task 名字不能和其他task名字重复 
            group = "test" 
            description = "gradle build script demo,shares only in this
            build.gradle" 
            doLast { println "Hello from the TestPlugin" } 
       } 
   } 
} 

// Apply the plugin
apply plugin: TestPlugin

Build script 创建插件,其实和直接定义一个 task 没有多大区别,但这种插件对其他模块是不可见的。

1.1.2 buildSrc project

这种创建插件的方式,要求源码放置在 rootProjectDir/buildSrc/src/main/java 目录 或者 rootProjectDir/buildSrc/src/main/groovy 目录 或者 rootProjectDir/buildSrc/src/main/kotlin 目录。Gradle负责编译和测试插件,并使其在构建脚本的类路径中可用。该插件对构建使用的每个构建脚本都是可见的。但是,它在其他项目不能使用,因此你不能在定义该构建的外部重用该插件

//src/main/groovy/com/xxx/BuildSrcPlugin.groovy 
package com.xxx 
import org.gradle.api.*; 
class BuildSrcPlugin implements Plugin<Project> { 
    void apply(Project project) { 
        project.task('hi') { 
            group = "buildsrc-test" 
            description = "gradle build script demo" 
            doLast { println "Hello from the BuildSrcPlugin" } 
        } 
    } 
} 
//app项目中使用 
apply plugin: com.xxx.BuildSrcPlugin;

1.1.3 Standalone project

上面两种方法只能在当前项目中使用,如果我们想要在其他项目中使用,需要为插件创建一个独立项目,这个项目产生并发布了成一个JAR,我们可以在多个项目中使用它并与他人共享。通常,这个JAR可能包含一些插件,或将几个相关的任务类捆绑到一个库中,或两者的某种组合。 步骤如下:

  • 在As中新建一个Project, 然后新建一个Java Library类型的Module,删除除src/main和build.gradle外的其他所有文件夹
  • 新建src/main/groovy/com/xxx文件夹,存放Gradle插件,并实现具体的插件.groovy代码如下:
//TestPlugin.groovy
class TestPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.task('hello1') {
            group = "xxx"
            description = "gradle build script demo"
            doLast {
                println "Hello from the TestPlugin"
            }
        }
    }
}
  • 更改build.gradle内容如下:
apply plugin: 'groovy'

dependencies {
    //gradle sdk
    implementation gradleApi()
    //groovy sdk
    implementation localGroovy()
}
  • 新建src/main/resources/META-INF/gradle-plugins, 并且添加properties文件如下:
//com.xxx.TestPlugin.properties
implementation-class=com.xxx.TestPlugin
  • 发布插件,在上面build.gradle文件添加发布code,如下:
apply plugin: 'maven-publish'

publishing {
    publications {
        mavenJava(MavenPublication) {
            //定义插件的在本地 maven 中的 id 用于classPath,如下则classpath为:"com.xxx:TestPlugin:1.0.3"
            groupId 'com.xxx'
            artifactId 'TestPlugin' 
            //定义插件的在本地 maven 中的版本号
            version '1.0.0'
            from components.java
        }
    }
}
publishing {
    repositories {
        maven {
            // 发布位置
            url uri('../repo')
        }
    }
}
  • 在As中找到Gradle tab中该module的publish双击发布这个插件 Screen Shot 2022-04-28 at 15.39.15.png
  • 使用:
//在根目录中
buildscript {
    repositories {
        //...
        maven {
            url uri('/Users/xxx/work-place/CodeLib/repo')
        }
    }
    dependencies {
        //...
        classpath "com.xxx:TestPlugin:1.0.0"
    }
}

//在使用的要使用该插件的module中引入该插件
apply plugin: 'com.xxx.TestPlugin'

1.2 Transform

Transfrom Api 是gradle 1.5.0开始引入的,Android Gradle Plugin 7.0.0废弃,使用Transform Action和AsmClassVisitorFactory替代,Transform允许第三方在编译后的文件转换为dex文件之前做处理操作,Transfrom Api 可以让开发者专注于如何对输入的类文件进行处理,而不用关心AppPlugin的编译流程。本文是对ARouter用到的技术补充,暂时不介绍Transform Action和AsmClassVisitorFactory。

  1. 引入依赖
implementation 'com.android.tools.build:gradle:4.1.1'
  1. 自定义Transform
//InjectTransform.groovy
class InjectTransform extends Transform {

    private Project mProject

    // 构造函数,我们将Project保存下来备用
    InjectTransform(Project project) {
        this.mProject = project
    }

    // 设置我们自定义的Transform对应的Task名称
    // 类似:transformClassesWithPreDexForXXX
    // 这里应该是:transformClassesWithInjectTransformForxxx
    @Override
    String getName() {
        return 'InjectTransform'
    }

    // 指定输入的类型,通过这里的设定,可以指定我们要处理的文件类型
    //  这样确保其他类型的文件不会传入
    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    // 指定Transform的作用范围
    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    // 当前Transform是否支持增量编译
    @Override
    boolean isIncremental() {
        return false
    }

    // 核心方法
    // inputs是传过来的输入流,有两种格式:jar和目录格式
    // outputProvider 获取输出目录,将修改的文件复制到输出目录,必须执行
    @Override
    void transform(Context context, Collection<TransformInput> inputs,
                   Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider,
                   boolean isIncremental) throws IOException, TransformException, InterruptedException {
        // Transform的inputs有两种类型,一种是目录,一种是jar包,要分开遍历
        inputs.each {
            TransformInput input ->
                // 遍历文件夹
                //文件夹里面包含的是我们手写的类以及R.class、BuildConfig.class以及R$XXX.class等
                input.directoryInputs.each {
                    DirectoryInput directoryInput ->
                        // 注入代码
                        MyInjectByJavassit.injectToast(directoryInput.file.absolutePath, mProject)

                        // 获取输出目录
                        def dest = outputProvider.getContentLocation(directoryInput.name,
                                directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
                        // 将input的目录复制到output指定目录
                        FileUtils.copyDirectory(directoryInput.file, dest)
                }

                //对类型为jar文件的input进行遍历
                input.jarInputs.each {
                        //jar文件一般是第三方依赖库jar文件
                    JarInput jarInput ->
                        // 重命名输出文件(同目录copyFile会冲突)
                        def jarName = jarInput.name
                        def md5Name = DigestUtils.md5Hex(jarInput.file.absolutePath)
                        if (jarName.endsWith('.jar')) {
                            jarName = jarName.substring(0, jarName.length() - 4)
                        }
                        def dest = outputProvider.getContentLocation(jarName + md5Name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
                        FileUtils.copyFile(jarInput.file, dest)
                }
        }
    }
}

如上,是自定义InjectTransfrom,在MainActivity的oncreate()方法中插入toast提示。主要有五个方法:

//Transfrom.java
public abstract class Transform {
    public abstract String getName();
    public abstract Set<ContentType> getInputTypes();
    public Set<ContentType> getOutputTypes() {
        return getInputTypes();
    }
    public abstract Set<? super Scope> getScopes();
    public void transform(
        @NonNull Context context,
        @NonNull Collection<TransformInput> inputs,
        @NonNull Collection<TransformInput> referencedInputs,
        @Nullable TransformOutputProvider outputProvider,
        boolean isIncremental) throws IOException, TransformException, InterruptedException {
    }
}
  • Transform的命名规范 自定义Transform最终会被封装成一个TransformTask,TransformTask的名称并不雨自定义的Transform完全一样。其规范如下:
transform + getInputType() + "With" + getName() + "For" + BuildType

例如,我们这边getName() == "InjectTransform",在Debug环境下的TransformTask名称为 transformClassesWithInjectTransformForDebug.

Screen Shot 2022-04-28 at 15.56.08.png

  • Transfrom的输入类型 Transform的数据输入可以通过Scope和ContentType两个维度来过滤。 ContentType,就是数据类型,在插件开发过程中,我们一般只使用CLASSES和RESOURCES:
  • CLASSES:已经包含了class文件和jar文件
  • RESOURCES:Java资源文件 如果要处理所有的class和jar字节码,ContentType一般使用TransformManager.CONTENT_CLASS。 Scope相比ContentType是另外一个维度的过滤规则,包含如下:
PROJECT -只处理当前的项目
SUB_PROJECT -只处理只项目
PROJECT_LOCAL_DEPS -只处理当前项目的本地依赖,例如jar, aar
EXTERNAL_LIBRARIES -只处理外部的依赖库
PROVIDED_ONLY -只处理本地或远程以provided形式引入的依赖库
TESTED_CODE -测试代码
  • Transfrom的 transfrom方法 transform(...)是Transform进行数据处理的地方,TransformTasks之间是链式调用: Screen Shot 2022-04-28 at 16.02.38.png transform(...)方法,即使任何功能不实现,也是需要完成一个将input目录拷贝到output目录的动作,不然下一个Transform将会丢失待处理的class或者jar。
  1. 注册到AppExtension中
class TransformApiPlugin implements Plugin<Project> {
    void apply(Project project) {
        // 获取Android扩展
        def android = project.extensions.getByType(AppExtension)
        // 注册Transform,其实就是添加了Task
        android.registerTransform(new InjectTransform(project))
    }
}

项目编译之后,在build/intermediates/transform目录下面会出现InjectTransform目录

Screen Shot 2022-04-28 at 16.07.11.png

找到这个目录下面MainActivity,通过As Tools-Kotlin-Decompile Kotlin to Java反编译后代码如下

Screen Shot 2022-04-28 at 16.09.19.png 发现已经插入成功。

Annotation 和 Annotation Process Tool

1.1 Annotation

注解是在Java SE5引入的,通过注解我们能够编写更加干净易读的代码,可以在编译期格式类型检查,可以减少重复的样板代码,能够帮忙生成描述符文件等。

Java SE5内置了三种,定义在java.lang中的注解:

  • @Override: 表示当前的方法将覆盖超类中的方法,如果开发者不小心拼写错误,或者方法签名对不上被覆盖的方法,编译器会发出错误提示。
  • @Deprecated: 如果程序员对某个对象使用了这个注解,那么编译器会发出警告。
  • @SuppressWarning: 关闭不当的编译器警告提示。

1.1.1 注解的语法

以@Override为例:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}

除了@符号以外,注解的定义很像一个空接口,定义注解时需要一些元注解:@Target、@Retention,元注解会在下个小节介绍。

带元素的注解以SuppressWarnings为例:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}
//使用
@SuppressWarnings(value = "xxx"//带有默认值的value,如果没有申明可以省略
String desc() default ""

注解的元素在使用时表现为 名-值 对的形式并且需要置于SuppressWarnings的括号之内。

1.1.2 元注解

元注解专职负责注解其他注解,Java内置了四种元注解:

  1. @Target: 用于定义注解应用于什么地方(例如一个方法或者一个作用域)

可能的ElementType参数包含如下:

  • CONSTRUCTOR:用于构造器的声明
  • FIELD:用于域声明
  • LOCAL_VARIABLE:用于局部变量
  • METHOD:用于方法声明
  • PACKAGE:用于包声明
  • PARAMETER:用于参数声明
  • TYPE:用于类、接口、枚举声明

如果需要指定多个类型时,需要以逗号分隔并在花括号里面,例如

  1. @Retention 表示在什么级别保存该注解信息

可选的RetentionPolicy参数包括:

  • SOURCE: 注解将被编译器丢弃
  • CLASS: 注解在class文件可用,但会被VM丢弃
  • RUNTIME:VM在运行期间也保存注解,因此可以通过反射机制读取注解的信息
  1. @Documented: 将此注解包含在Javadoc中
  2. @Inherited: 允许子类继承父类中的注解

1.1.3 注解元素

前面已经提过注解元素,注解元素可用的类型如下所示:

  • 所有基本类型(int、float、boolean等)注意非包装类型
  • String
  • Class
  • enum
  • Annotation
  • 以上类型的数组

需要注意:

  • 注解元素不能有不确定的值,要么指定默认值,要么在使用注解时提供元素的值。
  • 对于非基本类型的元素,无论在源代码中声明时,或是在注解接口中定义默认值时,都不能以 null 作为它的值。
  • 注解不支持继承,也就是说,不能使用关键字 extends 来继承某个注解。但是,所有的注解类型都继承于通用的 Annotation 接口,而这一点是不能显式地写出来的。

1.1.4 注解使用

@IntDef({ITEM_TYPE_A, ITEM_TYPE_B})
@Retention(RetentionPolicy.SOURCE)
@interface ItemType {}

//使用
public void setType(@ItemType int type) {...}

1.2 Annotation Process Tool

APT(Annotation Processing Tool)是一种处理注释的工具, 被设计为操作Java原文件,而不是编译后的类。默认情况下,apt会在处理完源文件后编译他们,如果在系统构建的过程中会自动创建一些新的源文件。

1.2.1 基本使用

开发者自定义的每一个注解都需要自己的注解处理器,下面来看看如何编写注解处理器。

  1. 创建Java library: apt-annotation,并且创建文件DIView
//DIView.java
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface DIView {
    int value();
}
  1. 创建Java library: apt-processor, 添加 DIViewProcessor
  • 添加依赖
// 编译时期进行注解处理
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'

implementation project(':apt-annotation')

// 帮助我们通过类调用的方式来生成Java代码[JavaPoet]
implementation 'com.squareup:javapoet:1.13.0'

JavaPeot相关的知识点会在下个小节分享

  • 创建DIViewProcessor
//DIViewProcessor.java
@AutoService(Processor.class)
public class DIViewProcessor extends AbstractProcessor {

    private Messager mMessager;
    private Elements mElementUtils;
    private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mMessager = processingEnv.getMessager();
        mElementUtils = processingEnv.getElementUtils();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(DIView.class.getCanonicalName());
    }

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

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        //根据注解生成Java文件
        return false;
    }
}
  • init: 通过这个方法可以获得ProcessingEnvironment对象,通过这个对象就能知道APT正在处理的所有类型,并且通过它可以获取Messager对象和Filer对象,Messager可以用来向用户报告信息;Filer是一种PrintWriter,用于创建文件,需要注意的是这边必须使用Filer来创建,这样apt才能知道创建的新文件,从而对新文件的注解处理以及其他处理。
  • getSupportedAnnotationTypes: 指定注解器是注册给哪个注解。
  • getSupportedSourceVersion:指定使用的Java版本,通常返回最新的就好。
  • process: 主要处理的逻辑在这个方法里面,包括扫描注解、处理注解以及生成Java代码等。
  • AutoService:这个注解是google开发的,用于帮助自动注册注解处理器。类似以下步骤:
1、需要在 processors 库的 main 目录下新建 resources 资源文件夹;
2、在 resources文件夹下建立 META-INF/services 目录文件夹;
3、在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;
4、在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径;)
  1. 创建Android library: apt-library, 创建DIViewUtil:
public class DIViewUtil {

    public static void bind(Activity activity) {

        Class clazz = activity.getClass();
        try {
            Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
            Method method = bindViewClass.getMethod("bind", activity.getClass());
            method.invoke(bindViewClass.newInstance(), activity);
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

最终在项目中引用,会自动帮我们在build/generated/ap_generated_sources/debug 目录生成代码如下: Screen Shot 2022-04-28 at 16.37.26.png

1.2.2 Element介绍

上个小介绍了APT简单使用,这个小节介绍如何在注解处理器中处理注解元素以及相关API介绍。注解元素的处理主要在processor的process方法:

Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(DIView.class);

这行代码获取被DIView注解注解的所有节点元素Element,Element是一个接口,它只在编译期存在,可以是package, class, interface, method, field, 函数参数,泛型类型等,主要子类如下:

  • ExecutableElement: 表示类或者接口中的方法,构造函数或者初始化器
  • PackageElement: 表示包程序元素
  • TypeELement: 表示一个类或者接口元素
  • TypeParameterElement: 表示类,接口,方法的泛型类型例如T
  • VariableElement:表示字段,枚举常量,方法或者构造函数参数,局部变量,资源变量或者异常参数。

在介绍Element方法前,可以先看下前面DIView关于Element操作这块的例子

for (Element element : elements) {
    VariableElement variableElement = (VariableElement) element;
    TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
    String fullClassName = classElement.getQualifiedName().toString();
    //elements的信息保存到mProxyMap中
    ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
    if (proxy == null) {
        proxy = new ClassCreatorProxy(mElementUtils, classElement);
        mProxyMap.put(fullClassName, proxy);
    }
    DIView bindAnnotation = variableElement.getAnnotation(BView.class);
    int id = bindAnnotation.value();
    proxy.putElement(id, variableElement);
}

上面代码的工作是遍历找出所有被DIView注解的节点元素,然后把它加入到一个map中,上面的例子可以看到部分Element 接口,主要接口如下:

  • asType(): 返回元素的类型信息TypeMirror, 包括包名、类名、方法名以及参数名等,TypeMirror子类包含DeclareType、ExecutableType、NullType、PrimitType、TypeVariable等
  • getKind(): 返回枚举ElementKind, 来标示当前的类是具体什么类型,比方如果是类对应枚举为CLASS等
  • getModifiers(): 返回Set, 表示当前元素的作用域标识符, 如 public、static等
  • getSimpleName(): 获取元素名字不包括包名
  • getEnclosingElement(): 返回Element, 获取该元素的父元素,比如如果是TypeElement,则返回PackageElement
  • getEnclosedElements(): 返回List 获取当前元素的子元素
  • getAnnitation(): 返回Annotation子类,获取当前元素上的注解信息。

JavaPoet

JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件。这个框架功能非常有用,我们可以很方便的使用它根据注解、数据库模式、协议格式等来对应生成代码。通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作。

1.1 主要类说明

大多数 JavaPoet 的 API 使用普通的老式不可变 Java 对象。还有构建器、方法链和可变参数来使 API 友好。 JavaPoet 为类和接口 (TypeSpec)、字段 (FieldSpec)、方法和构造函数 (MethodSpec)、参数 (ParameterSpec) 和注释 (AnnotationSpec) 提供模型。

  • JavaFile: 用于构造输出包含一个顶级类的Java文件。
  • TypeSpec: 用于生成类、接口、或者枚举
  • MethodSpec:用于生成构造方法或者方法
  • FieldSpec:用于生成变量或者字段
  • ParameterSpec:用于创建参数
  • AnnotationSpec:用来创建注解

1.2 JavaPoet API使用

先看一个简单的例子:

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.builder("com.example.helloworld", helloWorld)
    .build();

javaFile.writeTo(System.out);

对应生成Java文件如下:

package com.example.helloworld;

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

1.2.1 类、接口、方法以及属性等定义

  1. 类、接口、枚举以及匿名内部类 通过前面已经知道通过TypeSpec来定义类、接口、枚举等,下面以接口为例:
TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC)
    .addField(FieldSpec.builder(String.class, "ONLY_THING_THAT_IS_CONSTANT")
        .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
        .initializer("$S", "change")
        .build())
    .addMethod(MethodSpec.methodBuilder("beep")
        .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
        .build())
    .build();
    
//枚举
TypeSpec.enumBuilder("Roshambo")
//匿名函数
TypeSpec.anonymousClassBuilder("")

Java文件如下:

public interface HelloWorld {
  String ONLY_THING_THAT_IS_CONSTANT = "change";

  void beep();
}
  1. 方法、属性、参数定义以及注解
ParameterSpec android = ParameterSpec.builder(String.class, "android")
    .addModifiers(Modifier.FINAL)
    .build();            
FieldSpec android1 = FieldSpec.builder(String.class, "android")
    .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
    .build();

MethodSpec flux = MethodSpec.constructorBuilder()
    .addModifiers(Modifier.PUBLIC)//作用域
    .addParameter(android1)//方法参数
    .addField(android)//作用域
    .addAnnotation(Override.class)//注解
    .build();

1.2.2 控制语句

JavaPoet为方法或者构造函数的方法体提供了标准化的API模版,addStatement()方法可以处理分号和换行符,beginControlFlow()方法结合endControlFlow()用于大括号、换行和缩紧,结合nextControlFlow()方法可以处理一个方法体中多个控制流的情况,比方if/else。例子如下:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addStatement("long now = $T.currentTimeMillis()", System.class)
    .beginControlFlow("if ($T.currentTimeMillis() < now)", System.class)
    .addStatement("$T.out.println($S)", System.class, "Time travelling, woo hoo!")
    .nextControlFlow("else if ($T.currentTimeMillis() == now)", System.class)
    .addStatement("$T.out.println($S)", System.class, "Time stood still!")
    .nextControlFlow("else")
    .addStatement("$T.out.println($S)", System.class, "Ok, time still moving forward")
    .endControlFlow()
    .build();

对应方法如下:

void main() {
  long now = System.currentTimeMillis();
  if (System.currentTimeMillis() < now)  {
    System.out.println("Time travelling, woo hoo!");
  } else if (System.currentTimeMillis() == now) {
    System.out.println("Time stood still!");
  } else {
    System.out.println("Ok, time still moving forward");
  }
}

1.2.3 占位标识符使用

JavaPoet提供了L标识符(for Literals), S标识符(for Strings), T标识符(for Types), N标识符(for Names)等标识符,用于占位替换。

  • T标识符:类型替换,例如("$T foo", List.class), JavaPoet会自动帮忙补全import文件。
  • L标识符:字面量替换,例如("Lfoo",3),用字符3直接替换L foo", 3), 用字符3直接替换L
  • S标识符:字符串替换,("$S foo", "H")
  • N标识符: 名称替换,比如一个方法中调用另外一个方法,通过("$N foo", methodSpec)调用
  1. $T使用
MethodSpec today = MethodSpec.methodBuilder("today")
    .returns(Date.class)
    .addStatement("return new $T()", Date.class)
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    .addMethod(today)
    .build();

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
    .build();

javaFile.writeTo(System.out);
  1. $N使用
MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
    .addParameter(int.class, "i")
    .returns(char.class)
    .addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
    .build();

MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
    .addParameter(int.class, "b")
    .returns(String.class)
    .addStatement("char[] result = new char[2]")
    .addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
    .addStatement("result[1] = $N(b & 0xf)", hexDigit)
    .addStatement("return new String(result)")
    .build();

1.2.4 ClassName使用

ClassName是在使用JavaPoet中比较常用的类型引用,比如: 由于在 java 工程中是没有 android 的 sdk, 所以你在 java 工程中想生成 android.app.Activity 这种类型是不能直接 Activity.class。这种情况下只能通过 ClassName 进行引用。

ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
ClassName list = ClassName.get("java.util", "List");
ClassName arrayList = ClassName.get("java.util", "ArrayList");
TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);

MethodSpec beyond = MethodSpec.methodBuilder("beyond")
    .returns(listOfHoverboards)
    .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
    .addStatement("result.add(new $T())", hoverboard)
    .addStatement("result.add(new $T())", hoverboard)
    .addStatement("result.add(new $T())", hoverboard)
    .addStatement("return result")
    .build();

对应Java代码:

package com.example.helloworld;

import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;

public final class HelloWorld {
  List<Hoverboard> beyond() {
    List<Hoverboard> result = new ArrayList<>();
    result.add(new Hoverboard());
    result.add(new Hoverboard());
    result.add(new Hoverboard());
    return result;
  }
}

1.2.5 Import static

JavaPoet支持导入static类型,例子如下:

...
ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards");

MethodSpec beyond = MethodSpec.methodBuilder("beyond")
    .returns(listOfHoverboards)
    .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
    .addStatement("result.add($T.createNimbus(2000))", hoverboard)
    .addStatement("result.add($T.createNimbus(\"2001\"))", hoverboard)
    .addStatement("result.add($T.createNimbus($T.THUNDERBOLT))", hoverboard, namedBoards)
    .addStatement("$T.sort(result)", Collections.class)
    .addStatement("return result.isEmpty() ? $T.emptyList() : result", Collections.class)
    .build();

TypeSpec hello = TypeSpec.classBuilder("HelloWorld")
    .addMethod(beyond)
    .build();

JavaFile.builder("com.example.helloworld", hello)
    .addStaticImport(hoverboard, "createNimbus")
    .addStaticImport(namedBoards, "*")
    .addStaticImport(Collections.class, "*")
    .build();

对应的Java代码:

package com.example.helloworld;

import static com.mattel.Hoverboard.Boards.*;
import static com.mattel.Hoverboard.createNimbus;
import static java.util.Collections.*;

import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;

class HelloWorld {
  List<Hoverboard> beyond() {
    List<Hoverboard> result = new ArrayList<>();
    result.add(createNimbus(2000));
    result.add(createNimbus("2001"));
    result.add(createNimbus(THUNDERBOLT));
    sort(result);
    return result.isEmpty() ? emptyList() : result;
  }
}

ASM

ASM是一个Java字节码修改框架。它能被用来动态生成类或者增强基有类的功能,ASM可以直接生产二进制class文件,也可以在类被加载入虚拟机之前动态的改变类行为。 asm.png

上图是Android Apk的打包流程,主要经过下面几个步骤:

  • 项目源文件处理
    • 打包资源文件,生成.R文件
    • 处理AIDL文件,生成.Java文件
  • 源文件经过Java Compiler编译生成.class文件
  • .class文件转换生成.dax文件
  • 打包生成APK
  • 对APK进行签名
  • 对签名后的APK文件进行对其处理

ASM是通过字节码来修改class文件,主要发生在打包流程生成.class文件之后,开始将.class文件转换成.dex文件之前。由前文可知,对于AGP版本在1.5.0以上的情况,开发者可以通过自定义Gradle插件,重写transform方法就可以获取这一流程的回调,并且做修改字节码操作。

1.1 基本使用(以ASM7为例)

在前文Gradle基础1.2 小节介绍了如何使用Transform,这以小节在transform基础上通过ASM在Java源文件中某个方法插入一个Toast, 修改 transform()方法如下:

//InjectTransform.groovy
@Override
    void transform(Context context, Collection<TransformInput> inputs,
                   Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider,
                   boolean isIncremental) throws IOException, TransformException, InterruptedException {
        inputs.each {
            TransformInput input ->
                input.directoryInputs.each { DirectoryInput directoryInput ->
                    if (directoryInput.file.isDirectory()) {
                        directoryInput.file.eachFileRecurse { File file ->
                            def name = file.name
                            if (checkClassFile(name)) {
                                //------------------ASM修改————————————————————
                                ClassReader classReader = new ClassReader(file.readBytes())
                                ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
                                ClassVisitor cv = new InjectClassVisitor(classWriter)
                                classReader.accept(cv, EXPAND_FRAMES)
                                byte[] code = classWriter.toByteArray()
                                FileOutputStream fos = new FileOutputStream(
                                        file.parentFile.absolutePath + File.separator + name)
                                fos.write(code)
                                fos.close()
                            }
                        }
                    }
                    def dest = outputProvider.getContentLocation(directoryInput.name,
                            directoryInput.contentTypes, directoryInput.scopes,
                            Format.DIRECTORY)
                    FileUtils.copyDirectory(directoryInput.file, dest)
                }
                //对类型为jar文件的input进行遍历
                input.jarInputs.each { JarInput jarInput -> //...}
        }
    }

在ASM的核心实现中,主要有以下几个类、接口(在org.objectweb.asm包中):

  • ClassReader:字节码读取和分析引擎。按照Java虚拟机规范中定义的方式来解析class文件中的内容,在遇到合适的字段时,回调ClassVisitor方法。
  • ClassVisitor:Java中的类访问者,提供一系列API由ClassReader类调用。是一个抽象类,开发者继承这个类时需要指定ASM API版本。
  • MethodVisitor:Java中的方法的访问者,定义在解析方法时会触发,作为ClassVisitor.visitMethod方法的返回值
  • ModuleVisitor:Java中的模块的访问者,作为ClassVisitor.visitModule方法的返回值
  • FieldVisitor:Java中的字段的访问者,作为ClassVisitor.visitField方法的返回值
  • AnnotationVisitor:Java中的注解的访问者,在解析注解时候会触发
  • ClassWirter:继承之ClassVisitor接口,用于拼接字节码。
  • ...

下面来看下如何实现classVisitor:

//InjectClassVisitor.java
public class InjectClassVisitor extends ClassVisitor implements Opcodes {

    public InjectClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM7, cv);
    }
    //...
    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        System.out.println("InjectClassVisitor : visitMethod : " + name);
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
        //匹配Activity
        if ("com/jaca/codelib/MainActivity".equals(this.mClassName) && "onCreate".equals(name)) {
            //处理onCreate
            System.out.println("InjectClassVisitor : change method ----> " + name);
            return new InjectOnCreateMethodVisitor(mv);
        }
        return mv;
    }
    //...
}

如上创建InjectClassVisitor类继承之 抽象类ClassVisitor,当解析某个类时ClassVisitor中对应设定回调会被回调,以Method为例,我们在transform方法中找到MainActivity类,ClassReader在accept方法被调用后会读取MainActivity对应的类信息,并且回调ClassVisitor中的visitMethod方法,以上我们拦截oncreate方法:

//InjectOnCreateMethodVisitor.java
public class InjectOnCreateMethodVisitor extends MethodVisitor {
    //...
    @Override
    public void visitInsn(int opcode) {
        //...
         super.visitInsn(opcode);
    }
}

创建InjectOnCreateMethodVisitor类继承MethodVisitor,并且在其回调方法中做字节码的修改,比方上面例子我们在MainActivity中的oncreate方法中插入调用静态方法toast。

1.2 ASM Bytecode Viewer

对于字节码的修改需要汇编语言基础,为缩短学习成本,我这边借助ASM Bytecode Viewer插件将Java/Kotlin文件转换成汇编语言。 比如上面在OnCreate中转换如下: asm11.png 因此,我们需要使用ASM在MainActivity的onCreate方法中插入以下代码:

ALOAD 0
INVOKESTATIC com/jaca/codelib/MainActivityKt.toast (Landroid/content/Context;)V

在前文InjectOnCreateMethodVisitor类的visitInsn方法中修改如下:

//InjectOnCreateMethodVisitor.java
     @Override
    public void visitInsn(int opcode) {
        //方法执行后插入
        if (opcode == Opcodes.ARETURN || opcode == Opcodes.RETURN) {
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            mv.visitMethodInsn(Opcodes.INVOKESTATIC
                    , "com/jaca/codelib/MainActivityKt"
                    , "toast"
                    , "(Landroid/content/Context;)V"
                    , false);
        }
        super.visitInsn(opcode);
    }

编译后可以在app/build/intermediates/transforms/InjectTransform/debug/73 查看: asm12.png

ARouter源码分析

ARouter是阿里巴巴开源的Android平台对页面、服务提供路由功能的中间件,提倡的是简洁且够用。 项目地址:github.com/alibaba/ARo… 目前源码分析基于ARouter 1.5.2

ARouter优势:

  • 优势一:直接解析URL路由,解析参数并赋值到对应目标字段的页面中。
  • 优势二:支持多模块项目,因为现在很少有APP是单模块的项目,一般都是多模块单工程的,由不同的团队负责不同的模块开发,这时候支持多模块项目开发就显得尤为重要。
  • 优势三:支持InstantRun,目前很多路由框架并不支持InstantRun,而InstantRun是Google在AndroidStudio2.0阿尔法版本中提供的新功能,其类似于代码的日更新,其只不过面向的是开发过程,这样做可以在开发的过程中减少开发和编译的次数,可以简单地将代码修改即时地同步到APK中,从而可以大规模降低开发复杂度。
  • 优势四:允许自定义拦截器,ARouter是支持拦截器的,而拦截器其实就是AOP的实现,可以自定义多个拦截器解决一些面向行为编程上出现的问题。
  • 优势五:ARouter可以提供IoC容器,IoC其实就是控制反转,这一部分做过服务端开发的朋友可能比较了解,因为服务端开发经常用到的Spring框架能够提供的一个非常重要的能力就是控制反转。
  • 优势六:映射关系自动注册,在页面不是很多的小型APP上面,自动注册并不会体现出太大优势,但是对于大型APP而言,可能页面数量已经达到的几十个或者数百个,在这样的情况下,自动注册就显得非常重要了,因为不可能将每一个页面都通过代码的方式进行注册。
  • 优势七:灵活的降级策略,ARouter可以提供很多种降级策略供用户自行选择,而原生的路由方案存在无法灵活降级的问题,StartActivity()一旦失败将会抛出运营级异常。

1.1 基本使用

  • Activity跳转
ARouter.getInstance().build("/test/activity2").withString("name", "老王").navigation();
  • 获取服务功能
ARouter.getInstance().navigation(HelloService.class).sayHello("mike");

((HelloService) ARouter.getInstance().build("/yourservicegroupname/hello").navigation()).sayHello("mike");

需要注意,如果在页面或者服务跳转中需要传递Object对象,需要自定义解析器,解析器继承SerializationService,官网推荐如下:

// If you need to pass a custom object, Create a new class(Not the custom object class),implement the SerializationService, And use the @Route annotation annotation, E.g:
@Route(path = "/yourservicegroupname/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);
    }
}
  • 设置拦截器
@Interceptor(priority = 8, name = "test interceptor")
public class TestInterceptor implements IInterceptor {
    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
        ...
        // No problem! hand over control to the framework
        callback.onContinue(postcard);  
        
        // Interrupt routing process
        // callback.onInterrupt(new RuntimeException("Something exception"));      

        // The above two types need to call at least one of them, otherwise it will not continue routing
    }

    @Override
    public void init(Context context) {
        // Interceptor initialization, this method will be called when sdk is initialized, it will only be called once
    }
}

1.2 项目结构

主要分为以下几个Module: arouter11.png

其中,app\module-java\module-java-export\module-kotlin为演示demo相关的demo。

  • arounter-annotation: 上层主要代码,包括入口类ARouter,主要逻辑代码类LogisticsCenter,相关辅助类ClassUtils等。
  • arouter-annotation: ARouter中支持的annotation(Autowired, Route, Interceptor等)的定义,已经RouteMeta等基础model bean的定义。
  • arouter-compiler: ARouter中定义的注解对应的注解处理器APT(AutowiredProcessor, RouteProcessor, InteceptorProcessor等)用于将被注解的类、属性自动生成Java文件。
  • arouter-gradle-plugin: 自定义Gradle插件,将APT处理后生成的Java文件在编译期间插入到注册代码中,受flag控制,如果不使用这个插件,会在运行期间扫描所有的dax文件,获取route相关信息,这种方式比较耗时。

1.3 源码流程分析

1.3.1 Init流程

入口如下:

ARouter.init(getApplication());

Arouter是外观模式的门面,实现逻辑在_ARouter类中,便于解藕。

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.");
    }
}

hasInit变量用于保证初始化代码只执行一次,接下来看看init方法:

protected static synchronized boolean init(Application application) {
    mContext = application;
    LogisticsCenter.init(mContext, executor);
    logger.info(Consts.TAG, "ARouter init success!");
    hasInit = true;
    mHandler = new Handler(Looper.getMainLooper());
    return true;
}

在_ARouter类的init方法中其实就是设置变量初始值,直接把主要工作交给LogisticsCenter

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    //...
    try {
        long startInit = System.currentTimeMillis();
        //通过Gradle插件的方式去把注解定义的类以及属性加载到内存之中
        loadRouterMap();
        if (registerByPlugin) {
            logger.info(TAG, "Load router map by arouter-auto-register plugin.");
        } else {
            Set<String> routerMap;
            //... 省略后代码如下
            //通过去遍历所有dex文件,找到经过APT + JavaPoet处理生成的文件,并且将文件存到Map中
            //这个方法返回所有满足需求的类名
            routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
            if (!routerMap.isEmpty()) {
                 context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
             }
            
            //根据全路径名去匹配生成的Java文件类型,
            //并且通过反射创建这个类的实例,并调用它的loadInto方法
            for (String className : routerMap) {
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    // This one of root elements, load root.
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    // Load interceptorMeta
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    // Load providerIndex
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }
        }
        //...
    } 
    //...
}

LogisticsCenter的init方法主要做两件事:

  • 找出 com.alibaba.android.arouter.routes包下面所有的类名,存到集合中。通过两种方式来获取,一种是在运行期间动态的遍历所有dex文件,另外一种是通过编译期间自定义插件通过ASM的方式把符合预期的类信息插入注册代码到loadRouterMap中。
  • 根据类名,通过反射创建这个类的实例并且调用这个对象的loadInto方法去初始化Warehouse里面的参数

1.3.1.1 WareHouse

WareHouse相当于ARouter的全局缓存:

//Warehouse.java
// Cache route and metas
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
static Map<String, RouteMeta> routes = new HashMap<>();

// Cache provider
static Map<Class, IProvider> providers = new HashMap<>();
static Map<String, RouteMeta> providersIndex = new HashMap<>();

// Cache interceptor
static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
static List<IInterceptor> interceptors = new ArrayList<>();
  • groupsIndex 存储数据如下:
routes.put("m2", ARouter$$Group$$m2.class);
routes.put("module", ARouter$$Group$$module.class);
routes.put("test", ARouter$$Group$$test.class);
routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);

Key为group名称,官方推荐group名与当前模块名字相同,但是也可以一个模块设置多个分组;关于Value 是一个Class类型,它继承 IRouteGroup接口,这个类是通过APT自动生成的,这个类记载了同个Module同个组名下所有被@Route注解修饰的类信息,如下:

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$module implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/module/1", RouteMeta.build(RouteType.ACTIVITY, TestModuleActivity.class, "/module/1", "module", null, -1, -2147483648));
  }
}
  • routes Key为@Route注解设置的路径,Value是RouteMeta对象,RouteMeta对象如下:
public class RouteMeta {
    private RouteType type;         // Type of route
    private Element rawType;        // Raw type of route
    private Class<?> destination;   // Destination
    private String path;            // Path of route
    private String group;           // Group of route
    private int priority = -1;      // The smaller the number, the higher the priority
    private int extra;              // Extra data
    private Map<String, Integer> paramsType;  // Param type
    private String name;
}

这个对象记录了路由类型,目标class等信息,其中路由类型分为以下几种:

public enum RouteType {
    ACTIVITY(0, "android.app.Activity"),
    SERVICE(1, "android.app.Service"),
    PROVIDER(2, "com.alibaba.android.arouter.facade.template.IProvider"),
    CONTENT_PROVIDER(-1, "android.app.ContentProvider"),
    BOARDCAST(-1, ""),
    METHOD(-1, ""),
    FRAGMENT(-1, "android.app.Fragment"),
    UNKNOWN(-1, "Unknown route type");
}

另外四个缓存暴露了和服务已经拦截器相关的缓存,原理差不多不展开,后面的内容也会再涉及到。这里我们暂时先了解下ARouter缓存涉及涉及到的统一的命名规则,协议如下:

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

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

public interface IInterceptorGroup {
    void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptor);
}

public interface IProviderGroup {
    void loadInto(Map<String, RouteMeta> providers);
}

1.3.1.2 Java文件自动生成

在init流程中介绍过,init流程回去扫遍 com.alibaba.android.arouter.routes包下自动生成的Java文件,并且将这些类信息加载到全局缓存中。

这些Java文件通过APT+JavaPoet 技术自动生成,ARuoter定义的注解有:

  • @Route: 注解在目标页面,可以定义路由的路径已经组名等
  • @Autowired:修饰在目标页面的属性上面,在页面跳转的时候自动为注解的对象赋值
  • @Interceptor:用于声明拦截器,如果设置了相同的优先级会抛出异常。

这边以RouteProcessor为例解析生成自动Java文件文件流程,RouteProcessor注解解析器主要生成以下三个文件:

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$modulekotlin implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("kotlin", ARouter$$Group$$kotlin.class);
  }
}

用于初始化WareHouse.groupsIndex.

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$kotlin implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/kotlin/java", RouteMeta.build(RouteType.ACTIVITY, TestNormalActivity.class, "/kotlin/java", "kotlin", null, -1, -2147483648));
    atlas.put("/kotlin/test", RouteMeta.build(RouteType.ACTIVITY, KotlinTestActivity.class, "/kotlin/test", "kotlin", new java.util.HashMap<String, Integer>(){{put("name", 8); put("age", 3); }}, -1, -2147483648));
  }
}

用于初始化 WareHouse.routes

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Providers$$arouterapi implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    providers.put("com.alibaba.android.arouter.facade.service.AutowiredService", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", null, -1, -2147483648));
    providers.put("com.alibaba.android.arouter.facade.service.InterceptorService", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", null, -1, -2147483648));
  }
}

用于初始化 WareHouse.providersIndex RouteProcessor注解处理器的入口函数是process:

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    if (CollectionUtils.isNotEmpty(annotations)) {
        //获取所有被@Route注解的元素,可以是是包、模块或者类等
        Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
        try {
            logger.info(">>> Found routes, start... <<<");
            //主要解析和生成逻辑
            this.parseRoutes(routeElements);
        } catch (Exception e) {
            logger.error(e);
        }
        return true;
    }
    return false;
}

通过getElementsAnnotationWith获取所有被Route注解的元素

private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
    if (CollectionUtils.isNotEmpty(routeElements)) {
        //...
        //声明特定类的类型,在后面使用
        TypeMirror type_Activity = elementUtils.getTypeElement(ACTIVITY).asType();
        TypeMirror type_Service = elementUtils.getTypeElement(SERVICE).asType();
        TypeMirror fragmentTm = elementUtils.getTypeElement(FRAGMENT).asType();
        TypeMirror fragmentTmV4 = elementUtils.getTypeElement(Consts.FRAGMENT_V4).asType();

        // Interface of ARouter
        TypeElement type_IRouteGroup = elementUtils.getTypeElement(IROUTE_GROUP);
        TypeElement type_IProviderGroup = elementUtils.getTypeElement(IPROVIDER_GROUP);
        ClassName routeMetaCn = ClassName.get(RouteMeta.class);
        ClassName routeTypeCn = ClassName.get(RouteType.class);
        //定义参数类型:Map<String, Class<? extends IRouteGroup>>
        ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(
                        ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup))
                )
        );
        //定义参数类型:Map<String, RouteMeta>
        ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ClassName.get(RouteMeta.class)
        );

        //输入参数 - routes:Map<String, Class<? extends IRouteGroup>> 
        ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();
        //输入参数 - atlas:Map<String, RouteMeta>
        ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
        //输入参数 - providers:Map<String, RouteMeta>
        ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build();  // Ps. its param type same as groupParamSpec!
        //为IRouteRoot子类定义 loadInto方法 
        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 routeMeta;

            // Activity or Fragment
            //根据type类型初始化RouteMeta
            if (types.isSubtype(tm, type_Activity) || types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
                // Get all fields annotation by @Autowired
                Map<String, Integer> paramsType = new HashMap<>();
                Map<String, Autowired> injectConfig = new HashMap<>();
                //这个方法是获取当前元素中所有子元素被Autowired注解注释的field
                //上面两个map的key都是被注解变量的变量名
                injectParamCollector(element, paramsType, injectConfig);

                if (types.isSubtype(tm, type_Activity)) {
                    // Activity
                    logger.info(">>> Found activity route: " + tm.toString() + " <<<");
                    routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
                } else {
                    // Fragment
                    logger.info(">>> Found fragment route: " + tm.toString() + " <<<");
                    routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
                }

                routeMeta.setInjectConfig(injectConfig);
            } else if (types.isSubtype(tm, iProvider)) {         // IProvider
                logger.info(">>> Found provider route: " + tm.toString() + " <<<");
                routeMeta = new RouteMeta(route, element, RouteType.PROVIDER, null);
            } else if (types.isSubtype(tm, type_Service)) {           // Service
                logger.info(">>> Found service route: " + tm.toString() + " <<<");
                routeMeta = new RouteMeta(route, element, RouteType.parse(SERVICE), null);
            } else {
                throw new RuntimeException("The @Route is marked on unsupported class, look at [" + tm.toString() + "].");
            }
            //对routeMeta根据组名进行分类存到一个map中 
            //groupMap: Map<String, Set<RouteMeta>> groupMap = new HashMap<>()
            categories(routeMeta);
        }
        //为IProvideGroup子类定义 loadInto方法 
        MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(providerParamSpec);

        Map<String, List<RouteDoc>> docSource = new HashMap<>();

        //前面将所有需要处理的元素根据group名组装成一个value是Set<RouteMeta>的map,
        //接下来根据这个map生成Java文件
        for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
            String groupName = entry.getKey();
            //为IRouteGroup子类定义 loadInto方法 
            //在map中一个组名对应一个IRouteGroup子类
            MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(groupParamSpec);

            List<RouteDoc> routeDocList = new ArrayList<>();

            //通过组名下根据RouteMeta在loadInto中插入数据
            Set<RouteMeta> groupData = entry.getValue();
            for (RouteMeta routeMeta : groupData) {
                RouteDoc routeDoc = extractDocInfo(routeMeta);

                ClassName className = ClassName.get((TypeElement) routeMeta.getRawType());
                    //...

                // Make map body for paramsType
                StringBuilder mapBodyBuilder = new StringBuilder();
                Map<String, Integer> paramsType = routeMeta.getParamsType();
                Map<String, Autowired> injectConfigs = routeMeta.getInjectConfig();
                if (MapUtils.isNotEmpty(paramsType)) {
                    List<RouteDoc.Param> paramList = new ArrayList<>();

                    for (Map.Entry<String, Integer> types : paramsType.entrySet()) {
                        mapBodyBuilder.append("put(\"").append(types.getKey()).append("\", ").append(types.getValue()).append("); ");

                        RouteDoc.Param param = new RouteDoc.Param();
                        Autowired injectConfig = injectConfigs.get(types.getKey());
                        param.setKey(types.getKey());
                        param.setType(TypeKind.values()[types.getValue()].name().toLowerCase());
                        param.setDescription(injectConfig.desc());
                        param.setRequired(injectConfig.required());

                        paramList.add(param);
                    }

                    routeDoc.setParams(paramList);
                }
                String mapBody = mapBodyBuilder.toString();
                //填充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,
                        routeMeta.getPath().toLowerCase(),
                        routeMeta.getGroup().toLowerCase());

                routeDoc.setClassName(className.toString());
                routeDocList.add(routeDoc);
            }

            // 生成IRouteGroup子类,一个set<RouteMeta>对应一个Java文件
            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 + "<<<");
            //Map<String, String> rootMap,用于记录有多少组
            rootMap.put(groupName, groupFileName);
            docSource.put(groupName, routeDocList);
        }

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

        // 生成IProviderGroup子类
        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);

        //生成IRouteRoot子类
        String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
        JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                TypeSpec.classBuilder(rootFileName)
                        .addJavadoc(WARNING_TIPS)
                        .addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITROUTE_ROOT)))
                        .addModifiers(PUBLIC)
                        .addMethod(loadIntoMethodOfRootBuilder.build())
                        .build()
        ).build().writeTo(mFiler);
        //...
    }
}

为便于后面理解,上面代码尽量不删减以及添加了注释,在process方法中主要:

  • 遍历整个项目,找到所有被Route注解的所有类,然后根据groups的命名区分将Element转换成RouteMeta并且存到groupsMap: HashMap>中
  • 根据groupsMap生成Java文件 下面来看看injectParamCollector方法
private void injectParamCollector(Element element, Map<String, Integer> paramsType, Map<String, Autowired> injectConfig) {
    //Route注解的所有类中的子元素
    for (Element field : element.getEnclosedElements()) {
        //找到是成员变量的类型且被Autowired注解的变量,将所有信息存到map中,这个map最终会被保存到RouteMeta中
        if (field.getKind().isField() && field.getAnnotation(Autowired.class) != null && !types.isSubtype(field.asType(), iProvider)) {
            // It must be field, then it has annotation, but it not be provider.
            Autowired paramConfig = field.getAnnotation(Autowired.class);
            String injectName = StringUtils.isEmpty(paramConfig.name()) ? field.getSimpleName().toString() : paramConfig.name();
            paramsType.put(injectName, typeUtils.typeExchange(field));
            injectConfig.put(injectName, paramConfig);
        }
    }

    // if has parent?
    TypeMirror parent = ((TypeElement) element).getSuperclass();
    if (parent instanceof DeclaredType) {
        Element parentElement = ((DeclaredType) parent).asElement();
        if (parentElement instanceof TypeElement && !((TypeElement) parentElement).getQualifiedName().toString().startsWith("android")) {
            injectParamCollector(parentElement, paramsType, injectConfig);
        }
    }
}

这个方法相当于对Route注解的单个类进行处理,一个被Route注解的类最终在内存中的体现是一个RouteMeta

categories方法的工作是根据组名将RouteMeta进行分类

private void categories(RouteMeta routeMete) {
    if (routeVerify(routeMete)) {
        logger.info(">>> Start categories, group = " + routeMete.getGroup() + ", path = " + routeMete.getPath() + " <<<");
        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() + " <<<");
    }
}

RouteProcessor注解处理器已经讲完。

AutowiredProcessor和InterceptorProcessor注解处理器会比RouteProcessor相对简单,这里不展开讲,生成的代码如下:

AutowiredProcessor:

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

  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);
    Test3Activity substitute = (Test3Activity)target;
    substitute.name = substitute.getIntent().getExtras() == null ? substitute.name : substitute.getIntent().getExtras().getString("name", substitute.name);
    substitute.age = substitute.getIntent().getIntExtra("age", substitute.age);
    substitute.girl = substitute.getIntent().getBooleanExtra("boy", substitute.girl);
  }
}

InterceptorProcessor:

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Interceptors$$modulejava implements IInterceptorGroup {
  @Override
  public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
    interceptors.put(7, Test1Interceptor.class);
    interceptors.put(90, TestInterceptor90.class);
  }
}

1.3.1.3 编译期间插入代码

前面分析,init流程会通过编译期间向logisticsCenter.loadRouterMap方法插入代码来初始化WareHourse全局缓存,主要通过ASM + Transform技术实现,在前面基础已经分析过。

再来看看logisticsCenter.loadRouterMap方法

/**
 * arouter-auto-register plugin will generate code inside this method
 * call this method to register all Routers, Interceptors and Providers
 */
private static void loadRouterMap() {
    registerByPlugin = false;
    // auto generate register code by gradle plugin: arouter-auto-register
    // looks like below:
    // registerRouteRoot(new ARouter..Root..modulejava());
    // registerRouteRoot(new ARouter..Root..modulekotlin());
}

预期插入代码后:

private static void loadRouterMap() {
    registerByPlugin = false;
    register("com.alibaba.android.arouter.routes.ARouter$$Root$$modulejava");
    register("com.alibaba.android.arouter.routes.ARouter$$Root$$modulekotlin");
    register("com.alibaba.android.arouter.routes.ARouter$$Root$$arouterapi");
    register("com.alibaba.android.arouter.routes.ARouter$$Root$$app");
    register("com.alibaba.android.arouter.routes.ARouter$$Interceptors$$modulejava");
    register("com.alibaba.android.arouter.routes.ARouter$$Interceptors$$app");
    register("com.alibaba.android.arouter.routes.ARouter$$Providers$$modulejava");
    register("com.alibaba.android.arouter.routes.ARouter$$Providers$$modulekotlin");
    register("com.alibaba.android.arouter.routes.ARouter$$Providers$$arouterapi");
    register("com.alibaba.android.arouter.routes.ARouter$$Providers$$app");
  }

register方法做的事情是通过反射获取当前类的实例,然后根据RouteType调用loadInto方法来初始化WareHourse。

下面来看下怎么实现的

在注册插件的时候设定需要扫描文件的类型

public class PluginLaunch implements Plugin<Project> {

    @Override
    public void apply(Project project) {
       //...
        if (isApp) {
            //...
            ArrayList<ScanSetting> list = new ArrayList<>(3)
            list.add(new ScanSetting('IRouteRoot'))
            list.add(new ScanSetting('IInterceptorGroup'))
            list.add(new ScanSetting('IProviderGroup'))
            RegisterTransform.registerList = list
            //register this plugin
            android.registerTransform(transformImpl)
        }
    }
}

ScanSetting记录要扫描文件所在目录、要扫描文件的类型、以及classList: ArrayList用于记录找到文件后的全路径名等

再来看下自定义Transfrom中的transform方法:

void transform(Context context, Collection<TransformInput> inputs
               , Collection<TransformInput> referencedInputs
               , TransformOutputProvider outputProvider
               , boolean isIncremental) throws IOException, TransformException, InterruptedException {
    //...
    inputs.each { TransformInput input ->
        // scan all jars
        input.jarInputs.each { JarInput jarInput ->
            //...
        }
        // scan class files
        input.directoryInputs.each { DirectoryInput directoryInput ->
            //..
            directoryInput.file.eachFileRecurse { File file ->
                def path = file.absolutePath.replace(root, '')
                if (!leftSlash) {
                    path = path.replaceAll("\\\\", "/")
                }
                if(file.isFile() && ScanUtil.shouldProcessClass(path)){
                    ScanUtil.scanClass(file)
                }
            }
            //...
        }
    }
   //...
   //根据ScanSettings的classList修改LogisticsCenter的loadRouterMap方法
    if (fileContainsInitClass) {
        registerList.each { ext ->
            //...        
            RegisterCodeGenerator.insertInitCodeTo(ext)
    }
    //...
}

transfrom方法主要做两件事:

  • 遍历class文件和jar文件
    • 找到符合ScanSetting中设置的文件,根据接口来判断,如果符合将文件名添加到ScanSetting的classList中
    • 找到LogisticsCenter文件
  • 根据ScanSettings的classList修改LogisticsCenter的loadRouterMap方法
  1. 遍历class文件和jar文件

主要在ScanUtil中完成

static void scanJar(File jarFile, File destFile) {
    if (jarFile) {
        def file = new JarFile(jarFile)
        Enumeration enumeration = file.entries()
        while (enumeration.hasMoreElements()) {
            JarEntry jarEntry = (JarEntry) enumeration.nextElement()
            String entryName = jarEntry.getName()
            //com/alibaba/android/arouter/routes/包下文件
            if (entryName.startsWith(ScanSetting.ROUTER_CLASS_PACKAGE_NAME)) {
                InputStream inputStream = file.getInputStream(jarEntry)
                scanClass(inputStream)
                inputStream.close()
                //com/alibaba/android/arouter/core/LogisticsCenter 文件
            } else if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) {
                // mark this jar file contains LogisticsCenter.class
                // After the scan is complete, we will generate register code into this file
                RegisterTransform.fileContainsInitClass = destFile
            }
        }
        file.close()
    }
}

扫描Jar文件,如果是routes包下的文件,直接交给scanclass方法。如果是找到logisticsCenter文件,存储到自定义Transform中后面使用

static void scanClass(File file) {
    scanClass(new FileInputStream(file))
}

static void scanClass(InputStream inputStream) {
    ClassReader cr = new ClassReader(inputStream)
    ClassWriter cw = new ClassWriter(cr, 0)
    ScanClassVisitor cv = new ScanClassVisitor(Opcodes.ASM5, cw)
    cr.accept(cv, ClassReader.EXPAND_FRAMES)
    inputStream.close()
}

static class ScanClassVisitor extends ClassVisitor {
    //...
    void visit(int version, int access, String name, String signature,
               String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces)
        //registerList == scanSettings
        //interfaceName 是在插件注册的时候设定的三个接口如: IRouteGroup
        RegisterTransform.registerList.each { ext ->
            if (ext.interfaceName && interfaces != null) {
                interfaces.each { itName ->
                    if (itName == ext.interfaceName) {
                        //fix repeated inject init code when Multi-channel packaging
                        if (!ext.classList.contains(name)) {
                            //如果符合规范,把它加入到当前ScanSetting的classList中
                            ext.classList.add(name)
                        }
                    }
                }
            }
        }
    }
}
  1. 根据ScanSettings的classList修改LogisticsCenter的loadRouterMap方法 这部分工作在RegisterCodeGenerator中完成
static void insertInitCodeTo(ScanSetting registerSetting) {
    if (registerSetting != null && !registerSetting.classList.isEmpty()) {
        RegisterCodeGenerator processor = new RegisterCodeGenerator(registerSetting)
        //这个文件是logisticsCenter文件
        File file = RegisterTransform.fileContainsInitClass
        if (file.getName().endsWith('.jar'))
            processor.insertInitCodeIntoJarFile(file)
    }
}

拿到logisticsCenter文件,创建一个RegisterCodeGenerator对它进行处理

private File insertInitCodeIntoJarFile(File jarFile) {
    if (jarFile) {
       //...
        while (enumeration.hasMoreElements()) {
            //...
            //com/alibaba/android/arouter/core/LogisticsCenter.class
            if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) {
                //这个方法会找到对应方法做插入code
                def bytes = referHackWhenInit(inputStream)
                jarOutputStream.write(bytes)
            } 
            //...
        }
        //...
    }
    return jarFile
}

找到LogisticsCenter文件流后调用referHackWhenInit方法插入代码

private byte[] referHackWhenInit(InputStream inputStream) {
    ClassReader cr = new ClassReader(inputStream)
    ClassWriter cw = new ClassWriter(cr, 0)
    ClassVisitor cv = new MyClassVisitor(Opcodes.ASM5, cw)
    cr.accept(cv, ClassReader.EXPAND_FRAMES)
    return cw.toByteArray()
}

class MyClassVisitor extends ClassVisitor {
    //...
    @Override
    MethodVisitor visitMethod(int access, String name, String desc,
                              String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions)
        //generate code into this method - loadRouterMap
        if (name == ScanSetting.GENERATE_TO_METHOD_NAME) {
            mv = new RouteMethodVisitor(Opcodes.ASM5, mv)
        }
        return mv
    }
}

class RouteMethodVisitor extends MethodVisitor {
    //...
    @Override
    void visitInsn(int opcode) {
        //generate code before return
        if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
            //之前扫描的文件
            extension.classList.each { name ->
                name = name.replaceAll("/", ".")
                mv.visitLdcInsn(name)//类名
                // generate invoke register method into LogisticsCenter.loadRouterMap()
                //com/alibaba/android/arouter/core/LogisticsCenter.register
                mv.visitMethodInsn(Opcodes.INVOKESTATIC
                        , ScanSetting.GENERATE_TO_CLASS_NAME
                        , ScanSetting.REGISTER_METHOD_NAME
                        , "(Ljava/lang/String;)V"
                        , false)
            }
        }
        super.visitInsn(opcode)
    }
    //...
}

上面代码做的工作就是在LogisticsCenter.loadRouterMap方法中根据classList遍历调用LogisticsCenter.register,参数是文件全路径名

1.3.2 navigation流程

ARouter.getInstance()
        .build("/test/activity2")
        .navigation();

获取门面ARouter,真正的实现逻辑在_ARouter类,采用外观设计模式,便于节藕。

build方法的真正实现也在_ARouter中

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

最终会调用上面这个build方法,根据传入的path构造一个明信片Postcard

Postcard继承RouteMeta, 他是一个数据bean对象添加一些跳转逻辑,Poatcard对象如下:

public final class Postcard extends RouteMeta {
    // Base
    private Uri uri;
    private Object tag;             // A tag prepare for some thing wrong.
    private Bundle mBundle;         // Data to transform
    private int flags = -1;         // Flags of route
    private int timeout = 300;      // Navigation timeout, TimeUnit.Second
    private IProvider provider;     // It will be set value, if this postcard was provider.
    private boolean greenChannel;
    private SerializationService serializationService;

    // Animation
    private Bundle optionsCompat;    // The transition animation of activity
    private int enterAnim = -1;
    private int exitAnim = -1;
    ...
}

而navigation最终会调用到_ARouter中的navigation方法:

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    //...
    try {
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        //...
    }
    //...
    if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            /**
             * Continue process
             *
             * @param postcard route meta
             */
            @Override
            public void onContinue(Postcard postcard) {
                _navigation(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(postcard, requestCode, callback);
    }

    return null;
}

这个方法主要做两件事:

  • 根据path调用LogisticsCenter.completion方法去从WareHouse中获取缓存数据填充当前的Postcard
  • 根据Postcard执行真正的跳转逻辑

先来看下LogisticsCenter.completion方法:

public synchronized static void completion(Postcard postcard) {
    //...
    //init流程会初始化Warehouse.groupsIndex
    //但第一次调用Warehouse.routes还没有初始化,所以此时获取的routeMeta为空
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {
        // Maybe its does't exist, or didn't load.
        if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
            throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
        } else {
            //...
            //根据path的groupName到Warehouse.groupsIndex初始化Warehouse.routes
            addRouteGroupDynamic(postcard.getGroup(), null);
            //初始化完会routes重新再调用这个方法走另外一个分支
            completion(postcard);   // Reload
        }
    } else {
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());

        Uri rawUri = postcard.getUri();
        if (null != rawUri) {   // Try to set params into bundle.
            Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
            Map<String, Integer> paramsType = routeMeta.getParamsType();
            //初始化传参信息,最终会记录到Postcard的bundle中
            if (MapUtils.isNotEmpty(paramsType)) {
                for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                    setValue(postcard,
                            params.getValue(),
                            params.getKey(),
                            resultMap.get(params.getKey()));
                }

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

            // Save raw uri
            postcard.withString(ARouter.RAW_URI, rawUri.toString());
        }

        switch (routeMeta.getType()) {
            case PROVIDER:  // if the route is provider, should find its instance
                // Its provider, so it must implement IProvider
                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
                        Warehouse.providers.put(providerMeta, provider);
                        instance = provider;
                    } catch (Exception e) {
                        logger.error(TAG, "Init provider failed!", e);
                        throw new HandlerException("Init provider failed!");
                    }
                }
                postcard.setProvider(instance);
                postcard.greenChannel();    // Provider should skip all of interceptors
                break;
            case FRAGMENT:
                postcard.greenChannel();    // Fragment needn't interceptors
            default:
                break;
        }
    }
}

在init流程中会初始化Warehouse.groupsIndex,这个map中根据groupName去将RouteMate分类,但是还没初始化Warehouse.routes, 所以第一次调用completion方法会首先调用addRouteGroupDynamic方法初始化routes这个map。

public synchronized static void addRouteGroupDynamic(String groupName, IRouteGroup group) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    if (Warehouse.groupsIndex.containsKey(groupName)){
        //根据groupName获取当前组的IRouteGroup的实例
        //调用实例的loadInto方法初始化routes这个map,key是path
        Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);
        Warehouse.groupsIndex.remove(groupName);
    }

    // cover old group.
    if (null != group) {
        group.loadInto(Warehouse.routes);
    }
}

另外,需要注意的是,Fragment以及IProvider默认为绿色通道,拦截器不能拦截

Postcard已经准备完毕,下面来看看真正的跳转:

private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    //...
    switch (postcard.getType()) {
        case ACTIVITY:
            // Build intent
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            intent.putExtras(postcard.getExtras());

            // Set flags.
            int flags = postcard.getFlags();
            if (0 != flags) {
                intent.setFlags(flags);
            }

            // Non activity, need FLAG_ACTIVITY_NEW_TASK
            if (!(currentContext instanceof Activity)) {
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }

            // Set Actions
            String action = postcard.getAction();
            if (!TextUtils.isEmpty(action)) {
                intent.setAction(action);
            }

            // Navigation in main looper.
            runInMainThread(new Runnable() {
                @Override
                public void run() {
                    startActivity(requestCode, currentContext, intent, postcard, callback);
                }
            });

            break;
        case PROVIDER:
            //获取Provider实例
            return postcard.getProvider();
        case BOARDCAST:
        case CONTENT_PROVIDER:
        case FRAGMENT:
            Class<?> fragmentMeta = postcard.getDestination();
            try {
                Object instance = fragmentMeta.getConstructor().newInstance();
                if (instance instanceof Fragment) {
                    ((Fragment) instance).setArguments(postcard.getExtras());
                } else if (instance instanceof android.support.v4.app.Fragment) {
                    ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                }

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

    return null;
}

这边没啥好将的,如果是Activity就调用StartActivity,如果是Provider以及Fragment就返回当前的实例。

1.3.3 Inject流程

在每个目标页面创建时候必须要Inject当前的实例,如下:

ARouter.getInstance().inject(this);

最终会调用到_ARouter中的如下方法:

static void inject(Object thiz) {
    AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());
    if (null != autowiredService) {
        autowiredService.autowire(thiz);
    }
}

path 是/arouter/service/autowired对应的实例是AutowiredServiceImpl

@Route(path = "/arouter/service/autowired")
public class AutowiredServiceImpl implements AutowiredService {}

public interface AutowiredService extends IProvider {

    /**
     * Autowired core.
     * @param instance the instance who need autowired.
     */
    void autowire(Object instance);
}

实际上这个对象是继承IProvider接口,由上面navigation流程知道,如果是IProider会直接返回当前的实例,也就是AutowiredServiceImpl。

最终inject方法会调用到AutowiredServiceImpl.doInject

private void doInject(Object instance, Class<?> parent) {
    Class<?> clazz = null == parent ? instance.getClass() : parent;

    ISyringe syringe = getSyringe(clazz);
    if (null != syringe) {
        syringe.inject(instance);
    }

    Class<?> superClazz = clazz.getSuperclass();
    // has parent and its not the class of framework.
    if (null != superClazz && !superClazz.getName().startsWith("android")) {
        doInject(instance, superClazz);
    }
}

private ISyringe getSyringe(Class<?> clazz) {
    String className = clazz.getName();

    try {
        if (!blackList.contains(className)) {
            ISyringe syringeHelper = classCache.get(className);
            if (null == syringeHelper) {  // No cache.
            //目标页面名$$ARouter$$Autowired
                syringeHelper = (ISyringe) Class.forName(clazz.getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();
            }
            classCache.put(className, syringeHelper);
            return syringeHelper;
        }
    } catch (Exception e) {
        blackList.add(className);    // This instance need not autowired.
    }

    return null;
}

根据inject传入的class对象,构造AutowiredProcessor注解处理器自动生成的Java文件名,然后通过反射方式获取这个类的实例。

构造类名规则如下:

目标类名$$ARouter$$Autowired

例如Test1Activity对应Java文件如下:

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

  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);
    Test1Activity substitute = (Test1Activity)target;
    substitute.age = substitute.getIntent().getIntExtra("age", substitute.age);
    //...
    if (null != serializationService) {
      substitute.obj = serializationService.parseObject(substitute.getIntent().getStringExtra("obj"), new com.alibaba.android.arouter.facade.model.TypeWrapper<TestObj>(){}.getType());
    } else {
      Log.e("ARouter::", "You want automatic inject the field 'obj' in class 'Test1Activity' , then you should implement 'SerializationService' to support object auto inject!");
    }
    //...
    substitute.url = substitute.getIntent().getExtras() == null ? substitute.url : substitute.getIntent().getExtras().getString("url", substitute.url);
    substitute.helloService = ARouter.getInstance().navigation(HelloService.class);
  }
}

获取这个对象之后调用对象的inject方法对这个对象中被Autowired注解的变量设置传入的参数。

1.3.4 interceptor流程

在前面init流程初始化完成之后会调用afterInit去初始化拦截器

static void afterInit() {
    // Trigger interceptor init, use byName.
    interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}

path:/arouter/service/interceptor对应目标类 InterceptorServiceImpl

@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {}

public interface InterceptorService extends IProvider {

    /**
     * Do interceptions
     */
    void doInterceptions(Postcard postcard, InterceptorCallback callback);
}

InterceptorServiceImpl也是继承IProvider接口,因此上面afterInit调用navigation实际会返回InterceptorServiceImpl实例。在navigation流程在创建IProvider对象时候会调用它的init方法,下面先看看init方法:

public void init(final Context context) {
    LogisticsCenter.executor.execute(new Runnable() {
        @Override
        public void run() {
            //Warehouse.interceptorsIndex在init流程已经初始化,前文由分析
            if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
                for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
                    Class<? extends IInterceptor> interceptorClass = entry.getValue();
                    try {
                        IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
                        iInterceptor.init(context);
                        Warehouse.interceptors.add(iInterceptor);
                    } catch (Exception ex) {
                        throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]");
                    }
                }
                interceptorHasInit = true;
                synchronized (interceptorInitLock) {
                    interceptorInitLock.notifyAll();
                }
            }
        }
    });
}

init方法主要根据Warehouse.interceptorsIndex去初始化Warehouse.interceptors. 经过上面init 之后Warehouse.interceptors存在两个对象Test1Interceptor和TestInterceptor90。

在navigation流程,构建完Postcard之后如果是非IProvider或者Fragment会去尝试判断是否拦截:

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    //...
    try {
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        //...
    }
    //...
    if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
        //interceptorService其实就是InterceptorServiceImpl实例
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            @Override
            public void onContinue(Postcard postcard) {}
            @Override
            public void onInterrupt(Throwable exception) {}
        });
    } 
    //...
}

调用了InterceptorServiceImpl.doInterceptions方法:

@Override
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
    if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
        //...
        LogisticsCenter.executor.execute(new Runnable() {
            @Override
            public void run() {
                CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                try {
                    _execute(0, interceptorCounter, postcard);
                    interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                    //...
                } catch (Exception e) {
                    callback.onInterrupt(e);
                }
            }
        });
    } else {
        callback.onContinue(postcard);
    }
}

private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
        if (index < Warehouse.interceptors.size()) {
            IInterceptor iInterceptor = Warehouse.interceptors.get(index);
            iInterceptor.process(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    // Last interceptor excute over with no exception.
                    counter.countDown();
                    _execute(index + 1, counter, postcard);  // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
                }

                @Override
                public void onInterrupt(Throwable exception) {
                    //...
                }
            });
        }
    }

首先会创建一个与拦截器数量相同的CancelableCountDownLatch初始计数值,每放行一个拦截器就countDown,并交给后一个拦截器,如果拦截则清0计数,并将拦截的Throwable存入postcard的tag字段,interceptorCounter.await();阻塞直到计数归0或者阻塞超时(默认是300秒),最后通过interceptorCounter.getCount()判断是否是超时,还是拦截或者放行。

可以看到拦截的过程都是在子线程中处理,包括Interceptor的process也是在子线程调用的,因此,如果想要在拦截过程中展示dialog等都需要切换到主线程。

引用

开源最佳实践:Android平台页面路由框架ARouter

可能是最详细的ARouter源码分析

可能是最好理解的ARouter源码分析

比反射更快!使用ASM获取class信息(ClassReader)

一文让你明白Java字节码

gradle自定义插件与上传本地仓库

JavaPoet

Android中apk打包流程