ARouter是阿里2017年开源的页面路由框架,是今年比较火的一个开源框架,目前在Github上已经有2.2k的小星星了。官方对这个框架的定义是ARouter是阿里巴巴开源的Android平台中对页面、服务提供路由功能的中间件,提倡的是简单且够用。为了跟上潮流,我也打算来学习下这个开源框架,整个预计会分成四五个系列。今天我们先来看下基本使用和页面注册的源码。
Google提供的原声路由主要是通过intent,可以分成显示和隐式两种。显示的方案会导致类之间的直接依赖问题,耦合严重;隐式intent需要的配置清单中统一声明,首先有个暴露的问题,另外在多模块开发中协作也比较困难。只要调用startActivity后面的环节我们就无法控制了,在出现错误时无能为力,而ARouter可以在跳转过程中进行拦截,出现错误时可以实现降级策略,这个我们今天就不涉及到,后面我们会专门讲。今天的分享会从下面几个方面。
1.ARouter配置
2.activity页面之间跳转
3.APT技术
4.SPI技术
5.页面注册源码解析
配置之前我们先看下官方宣称的ARouter的优势:
简单讲,就是很牛X,几乎包含页面跳转的所需要的所有功能。
在使用之前需要先配置gradle,目前api最新的版本是1.2.1.1,处理器的最新版本是1.1.2.1.
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [ moduleName : project.getName() ]
}
}
}
}
dependencies {
// 替换成最新版本, 需要注意的是api
// 要与compiler匹配使用,均使用最新版可以保证兼容
compile 'com.alibaba:arouter-api:1.2.1.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.1.2.1'
...
}
接着需要尽早的初始化ARouter,可以考虑在Application中进行初始化:
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
初始化工作就是这样,接着我们看下页面跳转怎么玩。
首先需要在支持路由的页面上添加注解,路径path至少需要有两级,比如我们需要跳转到Test2Activity
,那么需要在activity上面配置path,具体内容不一定是/test/activity2,可以自由发挥。
@Route(path = "/test/activity2")
public class Test2Activity extends AppCompatActivity
然后直接像下面这样就行了,build中的参数就是上面配置的路径。
ARouter.getInstance().build("/test/activity2").navigation();
如果需要跳转的时候携带参数呢?
ARouter.getInstance().build("/test/activity2").withString("key1", "value1")
.navigation();
是不是so easy?ARouter提供了很多的withXX方法,可以携带基本类型,Object,Parcelable等等,里面的原理也是通过Bundle进行携带mBundle.putXX
如果希望实现类似startActivityForResult呢?只需要在navigation中传入两个参数,第一个就是Context,第二个参数是requestCode。
ARouter.getInstance().build("/test/activity2")
.withString("key1", "value1")
.navigation(this, 999);
在Test2Activity中就可以传递结果:
setResult(int resultCode)
或者
setResult(int resultCode, Intent data)
这里为了偷懒我们只是setResult(100);
然后我们就可以在onActivityResult中得到数据,根据requestCode可以拿到数据,我们这里只是弹个Toast。
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case 999:
Toast.makeText(this, "get resultCode:" + String.valueOf(resultCode), Toast.LENGTH_LONG).show();
default:
break;
}
}
基本跳转就是上面几种情况,接下来我们来看看源码是怎么实现的。
APT,就是Annotation Processing Tool 的简称,就是可以在代码编译期间对注解进行处理,并且生成Java文件,减少手动的代码输入。这里我们就不多做介绍,这个不太清楚的可以参考我之前的分享Android模块开发之APT技术。
Java提供的SPI全名就是Service Provider Interface,其实就是为某个接口寻找服务的机制,有点类似IOC的思想,将装配的控制权移交给ServiceLoader。SPI在平时我们用到的会比较少,但是在Android模块开发中就会比较有用,不同的模块可以基于接口编程,每个模块有不同的实现service provider,然后通过SPI机制自动注册到一个配置文件中,就可以实现在程序运行时扫描加载同一接口的不同service provider。不太清楚的可以参考之前的分析Android模块开发之SPI。
分析源码之前我们需要先了解下框架的整个架构,这里直接引用官方的一张图片。
最基础的就是Compiler这个SDK,主要是用来在编译期间处理注解Router/Interceptor/Autowire三个注解,在编译期间自动注册注解标注的类,成员变量等。
API的SDK是用户在运行期使用。Launcher这一层只有ARouter和_ ARouter, ARouter就是我们需要打交道的接口,这里使用了代理模式,实际ARouter是通过_ ARouter进行工作。
我们先看下三个主要的注解,这个我删掉了很多,有需要的可以自行参考源码。可以看出来三个注解的有效期间都是编译期间,关于注解不太清楚的可以参考我们之前的分享反射注解与动态代理综合使用
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
/**
* Path of route
*/
String path();
……
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Interceptor {
/**
* The priority of interceptor, ARouter will be excute them follow the priority.
*/
int priority();
/**
* The name of interceptor, may be used to generate javadoc.
*/
String name() default "Default";
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface Autowired {
// Mark param's name or service name.
String name() default "";
// If required, app will be crash when value is null.
// Primitive type wont be check!
boolean required() default false;
// Description of the field
String desc() default "No desc.";
}
其实小伙伴们也可以猜出来,ARouter会在编译的时候将Router注解标注的activity扫描出来,并且按照定义好的模版生成装配关系的代码。这个其实就是APT技术,我先前已经做好铺垫,英明神武吧:)。简单说就是要自定义注解的处理器,继承自AbstractProcessor,需要自己实现process方法来处理注解,然后需要用到SPI技术来注册我们自己定义的注解处理器,之后JVM就能在编译期间通过serviceLoader来找到我们的注解处理器balabala。这里篇幅限制我们就不展开反复说了,不清楚的同学自行参考上面的链接。
ARouter的process方法比较长,我们就看下生成的文件放在哪里,包名就是public static final String PACKAGE_OF_GENERATE_FILE = "com.alibaba.android.arouter.routes";
那么文件名呢?还记得我们前面跳转Test2Activity的path是什么吗?@Route(path = "/test/activity2")
其中test就是group,所以我们文件名就是groupFileName = ARouter$$Group$$test
public static final String SEPARATOR = "$$";
public static final String PROJECT = "ARouter";
public static final String NAME_OF_GROUP = PROJECT + SEPARATOR + "Group" + SEPARATOR;
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv){
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);
}
那么我们来看看ARouter自动生成的activity装配关系是怎么样的。
public class ARouter$$Group$$test implements IRouteGroup {
@Override
public void loadInto(Map atlas) {
atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap(){{put("key1", 8); }}, -1, -2147483648));
}
}
有一个需要注意的地方就是put("key1", 8)
,还记得我们上面参数跳转应该是withString("key1", "value1")
, 为什么这里是8?其实在编译的时候做了处理,8代表类型String。
public enum TypeKind {
// Base type
BOOLEAN,
BYTE,
SHORT,
INT,
LONG,
CHAR,
FLOAT,
DOUBLE,
// Other type
STRING,
PARCELABLE,
OBJECT;
}
页面自动注册的源码基本就是上面这些内容,我们最后引用一张官方的图片。
首先通过注解处理器扫出被标注的类文件;然后按照不同种类的源文件进行分类,不仅仅提供了跳转功能,它也能够实现模块之间的解耦,其实ARouter中的所有组件都是自动注册的
在按照不同种类的源文件进行分类完成之后,就能够按照固定的命名格式生成映射文件,这部分完成之后就意味着编译期的部分已经结束了;
而最后一步的初始化其实是发生在运行期的,在运行期只需要通过固定的包名来加载映射文件就可以了,这就是页面自动注册的整个流程。
到这里相信小伙伴们对于ARouter页面跳转的基本使用已经很熟悉了。我们也简单分析了下页面注册的原理,ARouter可以通过注解自动注册并且在编译期间生成映射关系,在运行的时候就可以加载文件,通过path就可以顺利跳转到目标页面。
好了,今天车就开到这了。估计再说下去小伙伴们该睡觉了,关于加载配置文件和页面跳转我们就在解析二中分析,欢迎关注。
关于注解和APT技术与SPI技术不太清楚的小伙伴可自行参考下面链接:
Android模块开发之APT技术
Android模块开发之SPI