上集回顾
上一篇文章解说了模块化以及组件,插件化的概念。模块化是一种解决项目分层的思想,组件化和插件化分别是其不同的实现。其中组件化以Module的形式拆分一个项目,在调试时独立为组件, 在集成打包时则又承担了库的角色,可谓一会儿排成人字, 一会儿排成一字。插件化则以apk插件的形式划分模块,在最终打包时动态集成到一起。
除了说到三种概念以及对比, 还提前说了一下组件化的核心思路,以及配置实现。那么接下来本篇内容说什么呢?
1. 总结一下组件的配置(前篇回顾,亦是本篇需要)
2. 带着ARouter揭秘组件之间的通讯
这里先总结也是简单提一下前一篇的组件化流程,不作深入剖析。如果还是不了解或不清楚,回去再看一下
模块化,组件化傻傻分不清?附带组件化福利
Case1: 通过config.gradle实现多组件实现统一配置
项目大了组件必然划分极多,不然也用不到组件化的程度。不同的组件gradle如果单独配置,很难做到统一,并且会出现很多奇怪难解的问题;为了保证配置的统一性,采用单独的统一配置文件是势在必得,也即config.gradle
Case2: 组件角色变换带来的配置变更
角色变换必然意味着配置的更新。在Library模式下, Manifest.xml 注册文件, Application应用类, applicationId是不需要的。而在application模式下, 这些又都是需要的。因为我们就知道了两种模式切换下,需要配置哪些东西。我们必然要根据isModule变量来分别配置gralde;以及通过一个单独的module(名字随意)文件夹存放application模式下的Manifest.xml以及Application类
Case3: 资源的冲突
资源的冲突是没有完全的解决办法的,根据不同的业务每个公司的方式都不同。我们只能提供一些方法极力避免,但是不能根除,这部分就是开发中需要注意的。方法1: 在module中配置资源前缀限制(只能限制xml文件,而不能作用于图片)方法2:多组件公共的图片采用统一的module来管理
Case4: Application分发冲突
不同的组件,除了一些需要在Application中公共初始化的东西,还有一些自己Module需要的部分。那必然需要一种方案,来解决这个问题。这种情况想必都不陌生,可以采用公共的Application继承的方式解决。每一个模块有两个角色,因此在开发时,不建议使用Application来获取Context。
Case5: 组件化开发,不建议使用ButterKnife
从这里开始,切入我们的正题了。
组件之间通信不像是单任务模式,使用EventBus就可以达到很好的效果。那么组件化之间,是通过什么方式通信呢?
隐式跳转 ? 嗯,会抢答了。但是有没有想过一个问题, 隐式跳转的局限性在哪 ? 每个组件(这里指四大组件)都需要添加隐式注册,而且很多时候却还是无能为力。
路由?
可以哦? 组件化通信的知名框架主要有两个ActivitRouter, ARouter。 其中ARouter是出自Alibaba, 也比较成熟。那么今天通过ARouter来揭秘组件通信的原理。
先说路由的概念,关于移动端架构的思考与总结这篇中有关于路由的说明,这里简单提一下作为前戏。
路由
说起路由,不禁想到了路由器
路由器的组成我们看一下, 一个接入口, N(N > 1)个输出口。
路由的工作是从信号源接收信号,然后分发到不同的接入口;如果按无线也算的话,输出口有无数。因为可能有偷网的N部手机。
路由机制和路由器很相似,如果说不同,唯一的不同可能在于路由器只有一个信号源,而路由机制是多个信号源的。为什么呢?因为组件之间的通信是相互的交叉通信。A需要调用B, B则是信号源;B需要A,A又成了信号源。
先说一下路由器的两大机制
注册机制
无论是插入网线,还是连接无线,都是路由器的注册机制。只有注册了(有线或无线连接),才能找到你的设备,才能进一步与你通信。
分发机制
路由器的内部肯定要查找对应的设备,并执行信号分发的。
明白了路由器的原理,路由机制就简单多了。我觉得,路由机制和EventBus很相似。不信?来看一下
首先看一下EventBus
// 发送
EventBus.getDefault().post("消息");
// 注册接收器
EventBus.getDefault().register(this);
@Subscribe
public void onEvent() {
//实现接收
}
从以上EventBus的使用可以看出:
1.消息发送以直接方式
EventBus的发送消息是直接得到实例发送的,并不需要注册。和一般通话机制不同,一般通话首先双端建立稳定连接, 进而才能实现通信。EventBus是将消息直接扔给路由, 路由去查找通信的另一端,然后传递消息。
2.消息接收先注册
试想一下上述消息的传递过程, 路由器得到消息,然后查找消息的接收方,找到之后将消息传递给接收方。
消息的接收经历先查找再分发的过程。那么通过什么查找呢?就是路由表,也就是相当于通讯录。因此消息的接收,注册是必然的。EventBus也是先要注册接收器,Router也是。
3.路由分发关系构建成表
如果你看过EventBus, 那么你肯定知道EventBus的核心就是保存有注册信息的Map。这个Map持有所有的注册的类,方法等。Router也需要一张表,它是由总表,组表多级表构成的,下面会说。
从以上分析出EventBus原理,其实EventBus就是一个小型的路由机制。它作用于一个Module, 负责同Module的通信,但是跨Module, 它无能为力。
与EventBus相似,ARouter机制就是一个跨Module的路由机制。
从ARouter的用法透析组件通信原理
// 需要接收消息的组件,要注册Router注解
@Route(group = "app", path = "/app/Component")
public class ComponentActivity extends AppCompatActivity {
// 通过Router直接发送事件
// 构造参数
Bundle bundle = new Bundle();
bundle.putString("data", "data from other module");
// 发送事件
ARouter.getInstance()
.build("/app/Component") // 跳转页面路由地址
.with(bundle) // 传参
.navigation();
以上是ARouter的用法。发送时,通过ARouter实例直接发送;接收时,先注册(@Router注解)。再结合EventBus,是不是明白了呢? ARouter是以路由地址来替代Activity实现进一步解耦的
明白了ARouter的通信流程,我们进一步看一下源码。
#######1. ARouter源码分为4部分:
arouter-annotation
annotation模块定义了常用的注解以及辅助解析的实体类
arouter-compiler
Processor这个关键字都不陌生吧!AnnotationProcessor是Google替代Apt用于注解解析的工具。从上图可以看出, ARouter分别定义了RouteProcessor, AutowiredProcessor, InterceptorProcessor分别解析路由机制最重要的三个注解,分别用于路由定义, 参数传值以及拦截器。
arouter-api
这个module毋庸置疑,就是给我使用的api的模块,包含核心类ARouter以及核心调用类_ARouter。
通过这些module划分,我们就可以猜到ARouter的实现思路是什么?如果明白了这个思路,我们可以自己定义我们自己的Router。
下面来分析思路
1. 添加注解@Route / @AutoWired
2.在RouteProcessor / AutoWiredProcessor中解析注解。 在process()中可以得到
添加@Route注解的所有类
@Route的注解参数
3.得到注解关联的信息后,动态生成路由表
先看一下@Route的源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
/**
* Path of route
*/
String path();
/**
* Used to merger routes, the group name MUST BE USE THE COMMON WORDS !!!
*/
String group() default "";
/**
* Name of route, used to generate javadoc.
*/
String name() default "undefined";
/**
* Extra data, can be set by user.
* Ps. U should use the integer num sign the switch, by bits. 10001010101010
*/
int extras() default Integer.MIN_VALUE;
/**
* The priority of route.
*/
int priority() default -1;
}
从源码可以看出, 重要的两个参数:
path: 定义了类的注解地址
group: 定义了类的注解的分组名称
@Route(group = "app", path = "/app/Component")
以上为我的使用, group定义了分组为‘app’; route定义的路由地址一般格式为:'组名/路由名称'。加入分组的概念可以大幅度提高查找的效率。
上面说到了生成路由表,不信? 来一波源码,源码来自RouteProcessor中的process(); 这个方法是解析注解的核心方法
/**
* {@inheritDoc}
*
* @param annotations
* @param roundEnv
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (CollectionUtils.isNotEmpty(annotations)) {
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;
}
private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
if (CollectionUtils.isNotEmpty(routeElements)) {
// Perpare the type an so on.
logger.info(">>> Found routes, size is " + routeElements.size() + " <<<");
rootMap.clear();
TypeMirror type_Activity = elements.getTypeElement(ACTIVITY).asType();
TypeMirror type_Service = elements.getTypeElement(SERVICE).asType();
TypeMirror fragmentTm = elements.getTypeElement(FRAGMENT).asType();
TypeMirror fragmentTmV4 = elements.getTypeElement(Consts.FRAGMENT_V4).asType();
// Interface of ARouter
TypeElement type_IRouteGroup = elements.getTypeElement(IROUTE_GROUP);
TypeElement type_IProviderGroup = elements.getTypeElement(IPROVIDER_GROUP);
ClassName routeMetaCn = ClassName.get(RouteMeta.class);
ClassName routeTypeCn = ClassName.get(RouteType.class);
/*
Build input type, format as :
```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)
);
/*
Build input param name.
*/
ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();
ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build(); // Ps. its param type same as groupParamSpec!
/*
Build method : 'loadInto'
*/
MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(rootParamSpec);
// Follow a sequence, find out metas of group first, generate java file, then statistics them as root.
for (Element element : routeElements) {
TypeMirror tm = element.asType();
Route route = element.getAnnotation(Route.class);
RouteMeta routeMeta = null;
if (types.isSubtype(tm, type_Activity)) { // Activity
logger.info(">>> Found activity route: " + tm.toString() + " <<<");
// Get all fields annotation by @Autowired
Map<String, Integer> paramsType = new HashMap<>();
for (Element field : element.getEnclosedElements()) {
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);
paramsType.put(StringUtils.isEmpty(paramConfig.name()) ? field.getSimpleName().toString() : paramConfig.name(), typeUtils.typeExchange(field));
}
}
routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
} 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 if (types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
logger.info(">>> Found fragment route: " + tm.toString() + " <<<");
routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), null);
} else {
throw new RuntimeException("ARouter::Compiler >>> Found unsupported class type, type = [" + types.toString() + "].");
}
categories(routeMeta);
// if (StringUtils.isEmpty(moduleName)) { // Hasn't generate the module name.
// moduleName = ModuleUtils.generateModuleName(element, logger);
// }
}
MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(providerParamSpec);
// Start generate java source, structure is divided into upper and lower levels, used for demand initialization.
for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
String groupName = entry.getKey();
MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(groupParamSpec);
// Build group method body
Set<RouteMeta> groupData = entry.getValue();
for (RouteMeta routeMeta : groupData) {
switch (routeMeta.getType()) {
case PROVIDER: // Need cache provider's super class
List<? extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces();
for (TypeMirror tm : interfaces) {
if (types.isSameType(tm, iProvider)) { // Its implements iProvider interface himself.
// This interface extend the IProvider, so it can be used for mark provider
loadIntoMethodOfProviderBuilder.addStatement(
"providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
(routeMeta.getRawType()).toString(),
routeMetaCn,
routeTypeCn,
ClassName.get((TypeElement) routeMeta.getRawType()),
routeMeta.getPath(),
routeMeta.getGroup());
} else if (types.isSubtype(tm, iProvider)) {
// This interface extend the IProvider, so it can be used for mark provider
loadIntoMethodOfProviderBuilder.addStatement(
"providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
tm.toString(), // So stupid, will duplicate only save class name.
routeMetaCn,
routeTypeCn,
ClassName.get((TypeElement) routeMeta.getRawType()),
routeMeta.getPath(),
routeMeta.getGroup());
}
}
break;
default:
break;
}
// Make map body for paramsType
StringBuilder mapBodyBuilder = new StringBuilder();
Map<String, Integer> paramsType = routeMeta.getParamsType();
if (MapUtils.isNotEmpty(paramsType)) {
for (Map.Entry<String, Integer> types : paramsType.entrySet()) {
mapBodyBuilder.append("put(\"").append(types.getKey()).append("\", ").append(types.getValue()).append("); ");
}
}
String mapBody = mapBodyBuilder.toString();
loadIntoMethodOfGroupBuilder.addStatement(
"atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap<String, Integer>(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
routeMeta.getPath(),
routeMetaCn,
routeTypeCn,
ClassName.get((TypeElement) routeMeta.getRawType()),
routeMeta.getPath().toLowerCase(),
routeMeta.getGroup().toLowerCase());
}
// Generate groups
String groupFileName = NAME_OF_GROUP + groupName;
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(groupFileName)
.addJavadoc(WARNING_TIPS)
.addSuperinterface(ClassName.get(type_IRouteGroup))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfGroupBuilder.build())
.build()
).build().writeTo(mFiler);
logger.info(">>> Generated group: " + groupName + "<<<");
rootMap.put(groupName, groupFileName);
}
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()) {
loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue()));
}
}
// Wirte provider into disk
String providerMapFileName = NAME_OF_PROVIDER + SEPARATOR + moduleName;
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(providerMapFileName)
.addJavadoc(WARNING_TIPS)
.addSuperinterface(ClassName.get(type_IProviderGroup))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfProviderBuilder.build())
.build()
).build().writeTo(mFiler);
logger.info(">>> Generated provider map, name is " + providerMapFileName + " <<<");
// Write root meta into disk.
String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(rootFileName)
.addJavadoc(WARNING_TIPS)
.addSuperinterface(ClassName.get(elements.getTypeElement(ITROUTE_ROOT)))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfRootBuilder.build())
.build()
).build().writeTo(mFiler);
logger.info(">>> Generated root, name is " + rootFileName + " <<<");
}
}
方法内容很多,但是主要做了两件事:
1) 解析注解类的注解参数与类参数,如组名, 注解地址, 注解类
2) 自动生成总表与组表。 总表存放组的信息, 组表存放组对应的路由表
如果你要追根溯源,那么给你一条黄河。自动生成类的条件是什么 ? 1) 你肯定要加@Route注解啊 2)你肯定要编译啊,因为Apt是编译时的注解解析技术
以下是注解生成的表,生成目录在
看alibaba包下的类,这些类为路由表相关的类
ARouter$$Provider$$app暂且不看,这是Provider生成的默认表
ARouter$$Root$$app 这是总表,存放的组的关系,我们看一下
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$app implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("app", ARouter$$Group$$app.class);
}
}
看到了吧, Root表生成的代码, 表的存放形式是Map, 表的key是分组名称, value是ARouter$$Group$$app。ARouter是框架名, Group代表着分组, app代表组名,这就是类名的拼接形式。
ARouter$$Group$$app这是组路由表,存放的是该组(组通过类名区分,ARouter$$Group$$组)的路由表, 我们看一下
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$app implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put(
"/app/Component",
RouteMeta.build(
RouteType.ACTIVITY,
ComponentActivity.class,
"/app/component",
"app",
new java.util.HashMap<String, Integer>(){
{put("from", 8);
put("id", 3); }},
-1,
-2147483648));
}
}
以上是路由表, 同样是一个Map, 表的key是路由地址/app/Component,表的value是一个Meta,包含路由类型ACTIVITY, 类名ComponentActivity.class, 路由地址, 组名‘app’, 该类下添加参数注解的参数from, id,以及优先级等。这是一个完整的Meta类,不信看一下Meta的源码
/**
* It contains basic route information.
*
* @author Alex <a href="mailto:zhilong.liu@aliyun.com">Contact me.</a>
* @version 1.0
* @since 16/8/24 09:45
*/
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
public RouteMeta() {
}
/**
* For versions of 'compiler' less than 1.0.7, contain 1.0.7
*
* @param type type
* @param destination destination
* @param path path
* @param group group
* @param priority priority
* @param extra extra
* @return this
*/
public static RouteMeta build(RouteType type, Class<?> destination, String path, String group, int priority, int extra) {
return new RouteMeta(type, null, destination, path, group, null, priority, extra);
}
/**
* For versions of 'compiler' greater than 1.0.7
*
* @param type type
* @param destination destination
* @param path path
* @param group group
* @param paramsType paramsType
* @param priority priority
* @param extra extra
* @return this
*/
public static RouteMeta build(RouteType type, Class<?> destination, String path, String group, Map<String, Integer> paramsType, int priority, int extra) {
return new RouteMeta(type, null, destination, path, group, paramsType, priority, extra);
}
/**
* Type
*
* @param route route
* @param destination destination
* @param type type
*/
public RouteMeta(Route route, Class<?> destination, RouteType type) {
this(type, null, destination, route.path(), route.group(), null, route.priority(), route.extras());
}
/**
* Type
*
* @param route route
* @param rawType rawType
* @param type type
* @param paramsType paramsType
*/
public RouteMeta(Route route, Element rawType, RouteType type, Map<String, Integer> paramsType) {
this(type, rawType, null, route.path(), route.group(), paramsType, route.priority(), route.extras());
}
/**
* Type
*
* @param type type
* @param rawType rawType
* @param destination destination
* @param path path
* @param group group
* @param paramsType paramsType
* @param priority priority
* @param extra extra
*/
public RouteMeta(RouteType type, Element rawType, Class<?> destination, String path, String group, Map<String, Integer> paramsType, int priority, int extra) {
this.type = type;
this.destination = destination;
this.rawType = rawType;
this.path = path;
this.group = group;
this.paramsType = paramsType;
this.priority = priority;
this.extra = extra;
}
public Map<String, Integer> getParamsType() {
return paramsType;
}
public RouteMeta setParamsType(Map<String, Integer> paramsType) {
this.paramsType = paramsType;
return this;
}
public Element getRawType() {
return rawType;
}
public RouteMeta setRawType(Element rawType) {
this.rawType = rawType;
return this;
}
public RouteType getType() {
return type;
}
public RouteMeta setType(RouteType type) {
this.type = type;
return this;
}
public Class<?> getDestination() {
return destination;
}
public RouteMeta setDestination(Class<?> destination) {
this.destination = destination;
return this;
}
public String getPath() {
return path;
}
public RouteMeta setPath(String path) {
this.path = path;
return this;
}
public String getGroup() {
return group;
}
public RouteMeta setGroup(String group) {
this.group = group;
return this;
}
public int getPriority() {
return priority;
}
public RouteMeta setPriority(int priority) {
this.priority = priority;
return this;
}
public int getExtra() {
return extra;
}
public RouteMeta setExtra(int extra) {
this.extra = extra;
return this;
}
@Override
public String toString() {
return "RouteMeta{" +
"type=" + type +
", rawType=" + rawType +
", destination=" + destination +
", path='" + path + '\'' +
", group='" + group + '\'' +
", priority=" + priority +
", extra=" + extra +
'}';
}
}
看了以上生成的类, 实体类与分析,应该可以理解了:Router根据注解动态生成了一个对应关系表。通过这个表,就可以根据路由地址查找到对应的Meta,得到要跳转的类, 并添加跳转参数。其实跳转还是原来的跳转,还是startActivity还是Bundle与Intent。只不过是在router模块里实现的统一的跳转与传参。
接下来看我们的api。我们在使用ARouter时,首先肯定是要初始化
ARouter.init(this);
那么初始化执行了什么呢? 我先偷偷告诉你,再去看真相... init的主要任务只有一个,就是导入路由表。先看一下使用流程
首先,我们添加了@Route注解
其次, 我们要编译项目吧,这时候执行RouteProcessor, 解析并生成路由表了
然后,才是我们的init, 这时候路由表已经生成了
再然后,就可以光明正大的把路由表导入了
我们来看init的源码
/**
* Init, it must be call before used router.
*/
public static void init(Application application) {
if (!hasInit) {
logger = _ARouter.logger;
_ARouter.logger.info(Consts.TAG, "ARouter init start.");
hasInit = _ARouter.init(application);
if (hasInit) {
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");
}
}
调用了_ARouter的init方法,为什么呢? 你也不想把你的逻辑暴漏给开发者不是?这叫隐藏细节。追踪到_ARouter的init
protected static synchronized boolean init(Application application) {
mContext = application;
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
// It's not a good idea.
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
// }
return true;
}
又跑到了LogisticsCenter,这个类是做什么呢?我来翻译下
LogisticsCenter contain all of the map
所有表的逻辑处理中心
一说到表,相信都猜到了,是的,给你一条黄河再,看源码
/**
* LogisticsCenter init, load all metas in memory. Demand initialization
*/
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
long startInit = System.currentTimeMillis();
//billy.qi modified at 2017-12-06
//load by plugin first
loadRouterMap();
if (registerByPlugin) {
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
Set<String> routerMap;
// It will rebuild router map every times when debuggable.
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
// These class was generated by arouter-compiler.
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();
}
PackageUtils.updateVersion(context); // Save new version name when router map update finishes.
} else {
logger.info(TAG, "Load router map from cache.");
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}
logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
startInit = System.currentTimeMillis();
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);
}
}
}
logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");
if (Warehouse.groupsIndex.size() == 0) {
logger.error(TAG, "No mapping files were found, check your configuration please!");
}
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
}
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}
看注释就知道了, load all metas in memory 导入内存中所有的Meta,就是路由表了。
接下来分析第三部分, 发送条件事件是怎么处理呢?先看调用
ARouter.getInstance()
.build("/app/Component")
.with(bundle)
.navigation();
直接看最关键的执行方法吧, navigation最终是走了_ARouter的navigation。上面说了,ARouter是个空壳子,用于隐藏内部细节,真正的实现在_ARouter中。看一下源码
/**
* Use router navigation.
*
* @param context Activity or null.
* @param postcard Route metas
* @param requestCode RequestCode
* @param callback cb
*/
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
if (debuggable()) { // Show friendly tips for user.
Toast.makeText(mContext, "There's no route matched!\n" +
" Path = [" + postcard.getPath() + "]\n" +
" Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
}
if (null != callback) {
callback.onLost(postcard);
} else { // No callback for this invoke, then we use the global degrade service.
DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
if (null != degradeService) {
degradeService.onLost(context, postcard);
}
}
return null;
}
if (null != callback) {
callback.onFound(postcard);
}
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(context, postcard, requestCode, callback);
}
/**
* Interrupt process, pipeline will be destory when this method called.
*
* @param exception Reson of interrupt.
*/
@Override
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
}
});
} else {
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
跳过乱七八糟的处理,直接看两个地方。其一是对拦截器的处理,无非是在最终之前先把拦截器的处理执行了。
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(context, postcard, requestCode, callback);
}
/**
* Interrupt process, pipeline will be destory when this method called.
*
* @param exception Reson of interrupt.
*/
@Override
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
}
});
} else {
return _navigation(context, postcard, requestCode, callback);
}
看一下拦截器的实现,先在onInterrupt走拦截的处理,然后在onContinue中走正常的流程,其实拦截器的实现很简单,虽然从名字看很唬人。继续看执行的源码
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = null == context ? mContext : context;
switch (postcard.getType()) {
case ACTIVITY:
// Build intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// Set flags.
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) { // Non activity, need less one flag.
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
// Navigation in main looper.
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
if (requestCode > 0) { // Need start for result
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
} else {
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
}
if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.
((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
}
if (null != callback) { // Navigation over.
callback.onArrival(postcard);
}
}
});
break;
case PROVIDER:
return postcard.getProvider();
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT:
Class fragmentMeta = postcard.getDestination();
try {
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;
}
看源码很清晰, 主干就是一个switch,因为@Route注解的对象有很多,Activity, Fragment, BroadCast,Provider,Service等等。直接看ACTIVITY,因为其他组件的实现都是相似的。
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// Set flags.
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) { // Non activity, need less one flag.
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
创建了一个Intent, 然后把参数拼上, 是不是很熟悉,难吗?不难,难的是中间的处理。如果是我们自己写,去掉那些复杂的考虑,实现一个简易的路由框架,其实是可以的。
// Navigation in main looper.
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
if (requestCode > 0) { // Need start for result
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
} else {
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
}
if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.
((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
}
if (null != callback) { // Navigation over.
callback.onArrival(postcard);
}
}
});
看一下以上的代码,就是执行跳转的源码,是不是也简单 ? 解析一下
首先,跳转的页面怎么得到?
不知道你还记不记得,在解析@Route注解的时候,我们就得到了ComponentActivity.class。参数也是我们在调用的时候就传入的, .with(bundle) 对就是它。
其次,Context是怎么得到的?
Context取自init。 因为在ARouter.init()时,参数是一个application。
最后,我们肯定要在主线程里执行啊
new Handler(Looper.getMainLooper()).post(new Runnable() {
解决了以上三个难题,让你写跳转,你也会,不是么?
从以上三部分的分析,你还觉得ARouter难吗?那么就来总结一下
先来看一下页面的跳转实现,都会的
Intent i = new Intent(mContext, ComponentActivity.class);
i.putExtra("p", "parameter");
mContext.startActivity(i);
在非本模块的非Activity中实现跳转,难度无非就是:
1) Context怎么得到 ? 我们通过init()解决了
2) ComponentActivity.class怎么得到? 我们通过@Route注解得到了
3)参数怎么办 ? 更简单, 我们直接调用api传的
4)怎么模拟主线程? new Hanlder(Looper.mainLooper()) 这个主线程,就是最后我们集成后,Module 'app'的主线程
解决了以上4个问题,是不是简单了? 与EventBus是不是很像呢?
学会这个框架,需要我们掌握什么:
1. 反射 (在单独的module下,通过反射才能得到一些属性。这里我们的Activity是通过反射得到的)
2. Apt 编译时解析注解
3. startActivity需要的条件, 4个条件让你找,你肯定找不全
4. 还有4吗?额忽然忘了
接下来,我们梳理一下:
1、 车祸的起源来自于@Route
2、 分析车祸现场,Processor解析下车祸的相关信息
3、 init给了我们身份证
- 掉Api, 我们浪起来啊
- 模拟跳转的条件,在国外跳转
其实, Fragment或其他组件与ACTIVITY差异不是很大。但是这里说一下参数的自动传值,也就是动态生成的我们包下的类
以下是源码
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ComponentActivity$$ARouter$$Autowired implements ISyringe {
private SerializationService serializationService;
@Override
public void inject(Object target) {
serializationService = ARouter.getInstance().navigation(SerializationService.class);;
ComponentActivity substitute = (ComponentActivity)target;
substitute.from = substitute.getIntent().getStringExtra("from");
substitute.id = substitute.getIntent().getIntExtra("id", 0);
}
}
一看是不是明白了,这不就是依赖注入吗? 好吧,分析一下
inject()就是注入的方法。方法的参数target就是Activity的对象,看源码就知道了。上面说过,传进来Activity.class后,通过反射,我们可以轻易得到Activity的对象,这里的对象来源就清楚了。再看实现,不就是getIntent().getXXExtra()吗?别说这个你不会。这就是依赖注入,在这里给跳转的Activity的参数赋值。
所以,别看我加了个注解就有值了,就觉得,咦?好神奇,那我只能遗憾的告诉你,哎吆,我是偷偷在别的地方赋值了啦!!!
以上就是对ARouter路由机制的全解析,怎么样 ? 你还觉得神秘吗? 如果让你写,我觉得应该可以了吧?
还是那句话,如果你觉得本篇文章对你有帮助,请为我打Call!!! 还有更多精彩干货让你收获满满哦