前语
ARouter是阿里技术团队开源的组件化结构,用于组件化项目中完成跨模块调用。我会从两个方面对ARouter结构源码进行剖析:运行期源码和编译期源码。编译期间,ARouter内部的注解处理器会根据相重视解进行辅佐代码生成,这些生成的代码是ARrouter之所以能够供给跨模块调用的关键;运行期间,再凭借于这些编译器产品来完成跨模块调用。本篇先进行运行时源码的剖析,引出问题,读者先能够思考一下,编译器产品的完成逻辑,我会在下一篇中和咱们一同进行剖析。
demo项目结构
咱们以ARouter供给的demo为例来进行ARouter源码的剖析,在剖析源码之前,咱们先了解一下demo的架构组成,有助于咱们对ARouter进行更全面的了解。
graph TD
app --> module-java
app --> module-java-export
app --> module-kotlin
module-java --> arouter-api
module-java-export --> arouter-api
module-kotlin --> arouter-api
module-java --> arouter-compiler
module-java-export --> arouter-compiler
module-kotlin --> arouter-compiler
module-java --> arouter-annotation
module-java-export --> arouter-annotation
module-kotlin --> arouter-annotation
编译期产品
今日的重点是无参数传递时的Activity页面跳转,咱们先了解一下编译期间产生的辅佐代码。
module-java模块
module-java模块中被注解的类以及其参数装备许多,咱们以Test2Activity作为剖析的进口。
@Route(path = "/test/activity2")
public class Test2Activity extends AppCompatActivity {
group
首要咱们要知道一点,ARouter会按照module名为每个mudule生成一个名为ARouter$$Root$$modulejava
格式的类,其间modulejava表明当时模块名。如module-java模块生成的类为:
public class ARouter$$Root$$modulejava implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
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);
}
}
也便是说,有几个模块(装备了ARouter)就会生成几个ARouter$$Root$$xxx
类,从上边的示例能够看出,这个类是用来管理本模块中定义的一切group的,module-java模块中定义了四个group,别离是m2、module、test、yourservicegroupname。
凭借这个辅佐类,就能够经过group名找到该group的管理类ARouter$$Group$$xxx
,ARouter$$Group$$xxx
内部管理了该分组下的一切path信息,接下来咱们看看这些path信息。
path
持续剖析编译期的第二个产品,也便是ARouter$$Root$$modulejava
map映射中存储的value-Class<? extends IRouteGroup>
,如上边test分组对应的ARouter$$Group$$test.class
,它内部存储的是本分组下一切path的映射联系。
public class ARouter$$Group$$test implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("ch", 5); put("fl", 6); put("dou", 7); put("boy", 0); put("url", 8); put("pac", 10); put("obj", 11); put("name", 8); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("pac", 10); put("ch", 5); put("obj", 11); put("fl", 6); put("name", 8); put("dou", 7); put("boy", 0); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
}
}
能够看到,办法体中履行时,是以path为key,以被注解类的相关信息封装出来的RouteMeta为value存入map中,有了这个类,就能够根据定义的path来找到被注解的类的相关信息。
group + path
了解了group和path的辅佐类生成的办法之后,你是不是也能猜到ARouter跨模块调用的大概完成了?如A、B两个没有依靠联系的模块需要跨模块调用,A想要翻开B中的某Activity页面,只需要在该Activity上添加@Route注解,指定该Activity的group和path,如@Route(path = "/login/loginApi")
然后编译过程中,注解处理器会根据B模块名生成该模块下的group管理类ARouter$$Root$$B
,有了ARouter$$Root$$B
再传入group名(login),就能够拿到本分组下的一切path信息管理类,ARouter$$Group$$login
,ARouter$$Group$$login
内存储的又是一切path的合集,再经过指定path就能够找到该path对应的被注解的类的信息。所以,即使没有依靠联系的两个模块,在ARouter的协助下,也能完成调用到B模块中类的作用。
源码剖析
了解了编译器产品后,接下来咱们开端从下面两个办法下手,进行Activity页面跳转(无参数)源码剖析。
- ARouter.init(getApplication())
- ARouter.getInstance().build(“/test/activity2”).navigation()
init
init是ARouter的初始化办法,跟进这个办法,会进入到LogisticsCenter的init办法中。
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
//经过插件加载
loadRouterMap();
if (registerByPlugin) {
//假如是经过插件注册,则不需要做任何处理,registerByPlugin默以为false,那么它是
//在哪被赋值的?loadRouterMap
} else {
//非插件加载
}
}
ARouter的初始化有两种办法,一种是经过插件在编译期生成代码,另一种是运行期遍历一切dex文件解分出注解类。咱们先看看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());
}
能够看到这儿除了将registerByPlugin赋值为false之外,再无任何代码,但是从办法注释上能够看出,ARouter插件会经过主动生成代码刺进到这个办法中来注册一切的Routers, Interceptors and Providers,生成代码的办法如办法体中注释那样,经过刺进代码调用registerRouteRoot、registerProvider、registerInterceptor办法进行注册(registerProvider、registerInterceptor注释上没表现出来,但实际会有),而传入的参数便是上边说到过的编译期产品如ARouter$$Root$$modulejava
。
private static void registerRouteRoot(IRouteRoot routeRoot) {
markRegisteredByPlugin();
if (routeRoot != null) {
routeRoot.loadInto(Warehouse.groupsIndex);
}
}
registerRouteRoot被调用后会先调用markRegisteredByPlugin将registerByPlugin变量赋值为true,总算找到registerByPlugin赋值的当地了。紧接着将Warehouse.groupsIndex这个静态map作为参数,调用IRouteRoot完成类的loadInto办法,咱们以ARouter$$Root$$modulejava
辅佐类的完成为例,看下这个loadInto办法的完成,它会将modulejava模块下一切分组信息存入Warehouse.groupsIndex这个map调集中。
public class ARouter$$Root$$modulejava implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
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);
}
}
也便是说,一切的Routers, Interceptors and Providers分组信息都会被一次性在loadRouterMap办法中被存入到Warehouse.groupsIndex、Warehouse.interceptorsIndex、Warehouse.providersIndex等map调集中,这便是初始化时做的准备工作。
代码注册
接下来咱们再看下非插件类型的注册。前边在说插件类型初始化时,是经过调用registerRouteRoot办法直接创立各模块下的形如ARouter$$Root$$modulejava
的方针,将其new出来存入map。插件当然是能够直接在编译期获取到各模块下生成的这个方针,但是若不使用插件生成的办法,又该怎样拿到这些类并创立方针呢?ARouter的做法便是遍历一切dex文件。
Set<String> routerMap;
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
//debug状态下或者新版别时,需要从头扫描dex并将一切包名以"com.alibaba.android.arouter.routes"
//开头的类获取到,getFileNameByPackageName办法完成就不再深入了
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
//拿到一次后,以json字符串的办法保存到SharedPreferences,下次同一版别不再进行扫描,由于扫描本身是很耗时的
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}
PackageUtils.updateVersion(context);
} else {
//不需要从头扫描的状况,直接从sp中读取
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}
//逻辑走到这儿,routerMap中存储的全部是IRouteRoot、IInterceptorGroup、IProviderGroup类型的类,
//别离经过反射的办法将方针创立出来,并调用loadInto填充三个map调集
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
办法的最后,拿到各个IRouteRoot、IInterceptorGroup、IProviderGroup完成类后,经过反射创立方针,并调用各自的loadInto办法,这样一来,初始化完成后,三个静态map:Warehouse.groupsIndex、Warehouse.interceptorsIndex、Warehouse.providersIndex中就有信息了。
至此,咱们就把ARouter初始化的逻辑剖析完了。接下来开端看看,它是怎样凭借这些准备好的信息完成跨模块调用的,以下面一行代码调用为例。
ARouter.getInstance().build("/test/activity2").navigation();
getInstance
创立出ARouter单例。
public static ARouter getInstance() {
if (!hasInit) {
throw new InitException("ARouter::Init::Invoke init(context) first!");
} else {
if (instance == null) {
synchronized (ARouter.class) {
if (instance == null) {
instance = new ARouter();
}
}
}
return instance;
}
}
build
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
ARouter的build办法会进入_ARouter的build办法。
protected Postcard build(String path) {
//这儿有一个获取PathReplaceService的逻辑,与本次剖析无关,咱们先不重视,默以为null
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
//extractGroup从path上解分出group,如"/test/activity2"的group便是test
return build(path, extractGroup(path), true);
}
相同,这儿的PathReplaceService越过,默以为null。能够看到,终究build办法便是构建了一个Postcard,并将group和path两个值保存为其成员变量。
protected Postcard build(String path, String group, Boolean afterReplace) {
......
return new Postcard(path, group);
}
navigation
由于build办法的返回值是Postcard方针,那么navigation天然也是Postcard的办法了。不断跟进此办法,你会发现终究又来到了_ARouter中,_ARouter的navigation办法中,将postcard方针传了进去,所以_ARouter中也就包括了postcard的信息。
public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
}
_ARouter的navigation办法,这儿的内容较多,咱们只重视和本次剖析相关的,去掉一些非主线代码。
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
//填充postcard信息
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
return null;
}
//这儿是一个拦截器机制,跟本次剖析无关,看完LogisticsCenter.completion直接进入它的onContinue办法中
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
_navigation(postcard, requestCode, callback);
}
});
return null;
}
LogisticsCenter.completion,看看这个办法做了什么。
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
}
//首要根据传入的途径获取到RouteMeta方针。RouteMeta方针是什么?
//还记得咱们前面剖析path生成的辅佐类时说到的下面这一行代码,
//RouteMeta便是封装了被@Route注解的类的信息。
//如RouteMeta.build(RouteType.ACTIVITY, TestModule2Activity.class, "/module/2", "m2", null, -1, -2147483648)
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
//拿到这个信息后,又分两种状况处理,为空和不为空,为空的状况不在咱们今日剖析的范围内,所以就不考虑了,咱们来看非空的逻辑。
if (null == routeMeta) {
......
} else {
//给postcard填充信息,将routeMeta保存的方针信息设置进去
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
//本次剖析不涉及uri,所以直接越过
Uri rawUri = postcard.getUri();
if (null != rawUri) { // Try to set params into bundle.
......
}
//这儿也都不满足,由于咱们的routeMeta.getType()是RouteType.ACTIVITY,越过
switch (routeMeta.getType()) {
case PROVIDER:
break;
case FRAGMENT:
default:
break;
}
}
}
所以这个办法对于本次剖析来说,也仅仅填充Postcard信息,前边Postcard先存储了group和path信息,现在又将方针类的信息存入进去,可见此类的命名为“明信片”仍是很有意思的,明信片天然要包括目的地的信息,咱们看一下代码履行到这儿,Postcard现已包括了哪些信息,先有一个直观的了解。
持续回到navigation办法中,咱们持续来看onContinue办法的履行,来到这儿,才算是真正要开端进行页面跳转了,办法体中咱们只保留了ACTIVITY类型,其他类型不在本次剖析之内。
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = postcard.getContext();
switch (postcard.getType()) {
case ACTIVITY:
// 首要构建intent方针,从上边的截图中咱们能够看到,getDestination得到的
//是方针途径Test2Activity的class方针,正好用来构建intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
//传递的intent参数,本次咱们剖析的是不带参数的跳转,所以这个暂不重视
intent.putExtras(postcard.getExtras());
// 设置flag类型
int flags = postcard.getFlags();
if (0 != flags) {
intent.setFlags(flags);
}
// 假如currentContext不是Activity的上下文context方针,就设置FLAG_ACTIVITY_NEW_TASK
if (!(currentContext instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
// 暂不重视
String action = postcard.getAction();
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
}
// 切换到主线程履行startActivity,看到这儿是不是觉得很奇妙,原来ARouter
//终究仍是使用了最常规的activity翻开办法进行跳转的
runInMainThread(new Runnable() {
@Override
public void run() {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
break;
}
return null;
}
总结
至此咱们就现已将ARouter跨模块完成页面跳转的逻辑剖析完了,咱们回忆一下ARouter到底是怎样做到的?咱们在非组件化开发中,页面跳转只需拿到方针页面Activity的class方针,就能够直接调用startActivity进行跳转,咱们也发现ARouter终究的完成也是如此,所以这个结构所做的逻辑就很清晰了,一句话形容,整个结构所做的核心便是在协助咱们去拿到方针Activity页面的class方针。知道这一点之后,咱们还要知道它是怎样拿到的?ARouter经过编译器生成辅佐代码的办法来完成,辅佐代码中保存好开发者定义的path和方针页面的映射联系,这样一来,开发者就能够经过path拿到方针页面的信息,完成跳转。