上集回顧
上一篇文章解說了模塊化以及組件,插件化的概念。模塊化是一種解決項目分層的思想,組件化和插件化分別是其不同的實現(xiàn)。其中組件化以Module的形式拆分一個項目,在調(diào)試時獨立為組件, 在集成打包時則又承擔了庫的角色,可謂一會兒排成人字, 一會兒排成一字。插件化則以apk插件的形式劃分模塊,在最終打包時動態(tài)集成到一起。
除了說到三種概念以及對比, 還提前說了一下組件化的核心思路,以及配置實現(xiàn)。那么接下來本篇內(nèi)容說什么呢?
1. 總結(jié)一下組件的配置(前篇回顧,亦是本篇需要)
2. 帶著ARouter揭秘組件之間的通訊
這里先總結(jié)也是簡單提一下前一篇的組件化流程,不作深入剖析。如果還是不了解或不清楚,回去再看一下
模塊化,組件化傻傻分不清?附帶組件化福利
Case1: 通過config.gradle實現(xiàn)多組件實現(xiàn)統(tǒng)一配置
項目大了組件必然劃分極多,不然也用不到組件化的程度。不同的組件gradle如果單獨配置,很難做到統(tǒng)一,并且會出現(xiàn)很多奇怪難解的問題;為了保證配置的統(tǒng)一性,采用單獨的統(tǒng)一配置文件是勢在必得,也即config.gradle
Case2: 組件角色變換帶來的配置變更
角色變換必然意味著配置的更新。在Library模式下, Manifest.xml 注冊文件, Application應用類, applicationId是不需要的。而在application模式下, 這些又都是需要的。因為我們就知道了兩種模式切換下,需要配置哪些東西。我們必然要根據(jù)isModule變量來分別配置gralde;以及通過一個單獨的module(名字隨意)文件夾存放application模式下的Manifest.xml以及Application類
Case3: 資源的沖突
資源的沖突是沒有完全的解決辦法的,根據(jù)不同的業(yè)務每個公司的方式都不同。我們只能提供一些方法極力避免,但是不能根除,這部分就是開發(fā)中需要注意的。方法1: 在module中配置資源前綴限制(只能限制xml文件,而不能作用于圖片)方法2:多組件公共的圖片采用統(tǒng)一的module來管理
Case4: Application分發(fā)沖突
不同的組件,除了一些需要在Application中公共初始化的東西,還有一些自己Module需要的部分。那必然需要一種方案,來解決這個問題。這種情況想必都不陌生,可以采用公共的Application繼承的方式解決。每一個模塊有兩個角色,因此在開發(fā)時,不建議使用Application來獲取Context。
Case5: 組件化開發(fā),不建議使用ButterKnife
從這里開始,切入我們的正題了。
組件之間通信不像是單任務模式,使用EventBus就可以達到很好的效果。那么組件化之間,是通過什么方式通信呢?
隱式跳轉(zhuǎn) ? 嗯,會搶答了。但是有沒有想過一個問題, 隱式跳轉(zhuǎn)的局限性在哪 ? 每個組件(這里指四大組件)都需要添加隱式注冊,而且很多時候卻還是無能為力。
路由?
可以哦? 組件化通信的知名框架主要有兩個ActivitRouter, ARouter。 其中ARouter是出自Alibaba, 也比較成熟。那么今天通過ARouter來揭秘組件通信的原理。
先說路由的概念,關于移動端架構(gòu)的思考與總結(jié)這篇中有關于路由的說明,這里簡單提一下作為前戲。
路由
說起路由,不禁想到了路由器
路由器的組成我們看一下, 一個接入口, N(N > 1)個輸出口。
路由的工作是從信號源接收信號,然后分發(fā)到不同的接入口;如果按無線也算的話,輸出口有無數(shù)。因為可能有偷網(wǎng)的N部手機。
路由機制和路由器很相似,如果說不同,唯一的不同可能在于路由器只有一個信號源,而路由機制是多個信號源的。為什么呢?因為組件之間的通信是相互的交叉通信。A需要調(diào)用B, B則是信號源;B需要A,A又成了信號源。
先說一下路由器的兩大機制
注冊機制
無論是插入網(wǎng)線,還是連接無線,都是路由器的注冊機制。只有注冊了(有線或無線連接),才能找到你的設備,才能進一步與你通信。
分發(fā)機制
路由器的內(nèi)部肯定要查找對應的設備,并執(zhí)行信號分發(fā)的。
明白了路由器的原理,路由機制就簡單多了。我覺得,路由機制和EventBus很相似。不信?來看一下
首先看一下EventBus
// 發(fā)送
EventBus.getDefault().post("消息");
// 注冊接收器
EventBus.getDefault().register(this);
@Subscribe
public void onEvent() {
//實現(xiàn)接收
}
從以上EventBus的使用可以看出:
1.消息發(fā)送以直接方式
EventBus的發(fā)送消息是直接得到實例發(fā)送的,并不需要注冊。和一般通話機制不同,一般通話首先雙端建立穩(wěn)定連接, 進而才能實現(xiàn)通信。EventBus是將消息直接扔給路由, 路由去查找通信的另一端,然后傳遞消息。
2.消息接收先注冊
試想一下上述消息的傳遞過程, 路由器得到消息,然后查找消息的接收方,找到之后將消息傳遞給接收方。
消息的接收經(jīng)歷先查找再分發(fā)的過程。那么通過什么查找呢?就是路由表,也就是相當于通訊錄。因此消息的接收,注冊是必然的。EventBus也是先要注冊接收器,Router也是。
3.路由分發(fā)關系構(gòu)建成表
如果你看過EventBus, 那么你肯定知道EventBus的核心就是保存有注冊信息的Map。這個Map持有所有的注冊的類,方法等。Router也需要一張表,它是由總表,組表多級表構(gòu)成的,下面會說。
從以上分析出EventBus原理,其實EventBus就是一個小型的路由機制。它作用于一個Module, 負責同Module的通信,但是跨Module, 它無能為力。
與EventBus相似,ARouter機制就是一個跨Module的路由機制。
從ARouter的用法透析組件通信原理
// 需要接收消息的組件,要注冊Router注解
@Route(group = "app", path = "/app/Component")
public class ComponentActivity extends AppCompatActivity {
// 通過Router直接發(fā)送事件
// 構(gòu)造參數(shù)
Bundle bundle = new Bundle();
bundle.putString("data", "data from other module");
// 發(fā)送事件
ARouter.getInstance()
.build("/app/Component") // 跳轉(zhuǎn)頁面路由地址
.with(bundle) // 傳參
.navigation();
以上是ARouter的用法。發(fā)送時,通過ARouter實例直接發(fā)送;接收時,先注冊(@Router注解)。再結(jié)合EventBus,是不是明白了呢? ARouter是以路由地址來替代Activity實現(xiàn)進一步解耦的
明白了ARouter的通信流程,我們進一步看一下源碼。
#######1. ARouter源碼分為4部分:
arouter-annotation
annotation模塊定義了常用的注解以及輔助解析的實體類
arouter-compiler
Processor這個關鍵字都不陌生吧!AnnotationProcessor是Google替代Apt用于注解解析的工具。從上圖可以看出, ARouter分別定義了RouteProcessor, AutowiredProcessor, InterceptorProcessor分別解析路由機制最重要的三個注解,分別用于路由定義, 參數(shù)傳值以及攔截器。
arouter-api
這個module毋庸置疑,就是給我使用的api的模塊,包含核心類ARouter以及核心調(diào)用類_ARouter。
通過這些module劃分,我們就可以猜到ARouter的實現(xiàn)思路是什么?如果明白了這個思路,我們可以自己定義我們自己的Router。
下面來分析思路
1. 添加注解@Route / @AutoWired
2.在RouteProcessor / AutoWiredProcessor中解析注解。 在process()中可以得到
添加@Route注解的所有類
@Route的注解參數(shù)
3.得到注解關聯(lián)的信息后,動態(tài)生成路由表
先看一下@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;
}
從源碼可以看出, 重要的兩個參數(shù):
path: 定義了類的注解地址
group: 定義了類的注解的分組名稱
@Route(group = "app", path = "/app/Component")
以上為我的使用, group定義了分組為‘a(chǎn)pp’; 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 + " <<<");
}
}
方法內(nèi)容很多,但是主要做了兩件事:
1) 解析注解類的注解參數(shù)與類參數(shù),如組名, 注解地址, 注解類
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這是組路由表,存放的是該組(組通過類名區(qū)分,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, 路由地址, 組名‘a(chǎn)pp’, 該類下添加參數(shù)注解的參數(shù)from, id,以及優(yōu)先級等。這是一個完整的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根據(jù)注解動態(tài)生成了一個對應關系表。通過這個表,就可以根據(jù)路由地址查找到對應的Meta,得到要跳轉(zhuǎn)的類, 并添加跳轉(zhuǎn)參數(shù)。其實跳轉(zhuǎn)還是原來的跳轉(zhuǎn),還是startActivity還是Bundle與Intent。只不過是在router模塊里實現(xiàn)的統(tǒng)一的跳轉(zhuǎn)與傳參。
接下來看我們的api。我們在使用ARouter時,首先肯定是要初始化
ARouter.init(this);
那么初始化執(zhí)行了什么呢? 我先偷偷告訴你,再去看真相... init的主要任務只有一個,就是導入路由表。先看一下使用流程
首先,我們添加了@Route注解
其次, 我們要編譯項目吧,這時候執(zhí)行RouteProcessor, 解析并生成路由表了
然后,才是我們的init, 這時候路由表已經(jīng)生成了
再然后,就可以光明正大的把路由表導入了
我們來看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.");
}
}
調(diào)用了_ARouter的init方法,為什么呢? 你也不想把你的邏輯暴漏給開發(fā)者不是?這叫隱藏細節(jié)。追蹤到_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 導入內(nèi)存中所有的Meta,就是路由表了。
接下來分析第三部分, 發(fā)送條件事件是怎么處理呢?先看調(diào)用
ARouter.getInstance()
.build("/app/Component")
.with(bundle)
.navigation();
直接看最關鍵的執(zhí)行方法吧, navigation最終是走了_ARouter的navigation。上面說了,ARouter是個空殼子,用于隱藏內(nèi)部細節(jié),真正的實現(xiàn)在_ARouter中??匆幌略创a
/**
* 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;
}
跳過亂七八糟的處理,直接看兩個地方。其一是對攔截器的處理,無非是在最終之前先把攔截器的處理執(zhí)行了。
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);
}
看一下攔截器的實現(xiàn),先在onInterrupt走攔截的處理,然后在onContinue中走正常的流程,其實攔截器的實現(xiàn)很簡單,雖然從名字看很唬人。繼續(xù)看執(zhí)行的源碼
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,因為其他組件的實現(xiàn)都是相似的。
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);
}
創(chuàng)建了一個Intent, 然后把參數(shù)拼上, 是不是很熟悉,難嗎?不難,難的是中間的處理。如果是我們自己寫,去掉那些復雜的考慮,實現(xiàn)一個簡易的路由框架,其實是可以的。
// 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);
}
}
});
看一下以上的代碼,就是執(zhí)行跳轉(zhuǎn)的源碼,是不是也簡單 ? 解析一下
首先,跳轉(zhuǎn)的頁面怎么得到?
不知道你還記不記得,在解析@Route注解的時候,我們就得到了ComponentActivity.class。參數(shù)也是我們在調(diào)用的時候就傳入的, .with(bundle) 對就是它。
其次,Context是怎么得到的?
Context取自init。 因為在ARouter.init()時,參數(shù)是一個application。
最后,我們肯定要在主線程里執(zhí)行啊
new Handler(Looper.getMainLooper()).post(new Runnable() {
解決了以上三個難題,讓你寫跳轉(zhuǎn),你也會,不是么?
從以上三部分的分析,你還覺得ARouter難嗎?那么就來總結(jié)一下
先來看一下頁面的跳轉(zhuǎn)實現(xiàn),都會的
Intent i = new Intent(mContext, ComponentActivity.class);
i.putExtra("p", "parameter");
mContext.startActivity(i);
在非本模塊的非Activity中實現(xiàn)跳轉(zhuǎn),難度無非就是:
1) Context怎么得到 ? 我們通過init()解決了
2) ComponentActivity.class怎么得到? 我們通過@Route注解得到了
3)參數(shù)怎么辦 ? 更簡單, 我們直接調(diào)用api傳的
4)怎么模擬主線程? new Hanlder(Looper.mainLooper()) 這個主線程,就是最后我們集成后,Module 'app'的主線程
解決了以上4個問題,是不是簡單了? 與EventBus是不是很像呢?
學會這個框架,需要我們掌握什么:
1. 反射 (在單獨的module下,通過反射才能得到一些屬性。這里我們的Activity是通過反射得到的)
2. Apt 編譯時解析注解
3. startActivity需要的條件, 4個條件讓你找,你肯定找不全
4. 還有4嗎?額忽然忘了
接下來,我們梳理一下:
1、 車禍的起源來自于@Route
2、 分析車禍現(xiàn)場,Processor解析下車禍的相關信息
3、 init給了我們身份證
- 掉Api, 我們浪起來啊
- 模擬跳轉(zhuǎn)的條件,在國外跳轉(zhuǎn)
其實, Fragment或其他組件與ACTIVITY差異不是很大。但是這里說一下參數(shù)的自動傳值,也就是動態(tài)生成的我們包下的類
以下是源碼
/**
* 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()就是注入的方法。方法的參數(shù)target就是Activity的對象,看源碼就知道了。上面說過,傳進來Activity.class后,通過反射,我們可以輕易得到Activity的對象,這里的對象來源就清楚了。再看實現(xiàn),不就是getIntent().getXXExtra()嗎?別說這個你不會。這就是依賴注入,在這里給跳轉(zhuǎn)的Activity的參數(shù)賦值。
所以,別看我加了個注解就有值了,就覺得,咦?好神奇,那我只能遺憾的告訴你,哎吆,我是偷偷在別的地方賦值了啦?。。?/p>
以上就是對ARouter路由機制的全解析,怎么樣 ? 你還覺得神秘嗎? 如果讓你寫,我覺得應該可以了吧?
還是那句話,如果你覺得本篇文章對你有幫助,請為我打Call!!! 還有更多精彩干貨讓你收獲滿滿哦