因为公司刚好准备开发一个公司内部使用的页面路由框架,抽空看了下目前市面上的几个开源框架。这篇文章主要分析阿里的ARuoter框架,整理成笔记,以便后面当作工具文章查阅。
文章的出发点是:分析ARouter整体框架是怎么设计,使用了哪些技术,针对使用到的技术,掌握怎么使用。 因此文章分为两个部分:
- 基础分析,包括Gradle基础、注解、注解处理器、JavaPoet、ASM等,这部分仅限点到为止,懂的怎么回事就好
- 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双击发布这个插件
- 使用:
//在根目录中
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。
- 引入依赖
implementation 'com.android.tools.build:gradle:4.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.
- 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之间是链式调用:
transform(...)方法,即使任何功能不实现,也是需要完成一个将input目录拷贝到output目录的动作,不然下一个Transform将会丢失待处理的class或者jar。
- 注册到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目录
找到这个目录下面MainActivity,通过As Tools-Kotlin-Decompile Kotlin to Java反编译后代码如下
发现已经插入成功。
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内置了四种元注解:
- @Target: 用于定义注解应用于什么地方(例如一个方法或者一个作用域)
可能的ElementType参数包含如下:
- CONSTRUCTOR:用于构造器的声明
- FIELD:用于域声明
- LOCAL_VARIABLE:用于局部变量
- METHOD:用于方法声明
- PACKAGE:用于包声明
- PARAMETER:用于参数声明
- TYPE:用于类、接口、枚举声明
如果需要指定多个类型时,需要以逗号分隔并在花括号里面,例如
- @Retention 表示在什么级别保存该注解信息
可选的RetentionPolicy参数包括:
- SOURCE: 注解将被编译器丢弃
- CLASS: 注解在class文件可用,但会被VM丢弃
- RUNTIME:VM在运行期间也保存注解,因此可以通过反射机制读取注解的信息
- @Documented: 将此注解包含在Javadoc中
- @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 基本使用
开发者自定义的每一个注解都需要自己的注解处理器,下面来看看如何编写注解处理器。
- 创建Java library: apt-annotation,并且创建文件DIView
//DIView.java
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface DIView {
int value();
}
- 创建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 文件写入注解处理器的全称,包括包路径;)
- 创建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 目录生成代码如下:
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 类、接口、方法以及属性等定义
- 类、接口、枚举以及匿名内部类 通过前面已经知道通过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();
}
- 方法、属性、参数定义以及注解
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标识符:字面量替换,例如("L
- S标识符:字符串替换,("$S foo", "H")
- N标识符: 名称替换,比如一个方法中调用另外一个方法,通过("$N foo", methodSpec)调用
- $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);
- $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文件,也可以在类被加载入虚拟机之前动态的改变类行为。
上图是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中转换如下:
因此,我们需要使用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 查看:
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:
其中,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方法
- 遍历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)
}
}
}
}
}
}
}
- 根据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等都需要切换到主线程。