阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

背景

当项目的业务越来越复杂,业务线越来越多的时候,就需要按照业务线去分不同的模块去开发,这样专门的人负责专门的业务模块,最终上线由壳工程去负责进行组合打包各个module,完成业务的快速迭代。整个过程会涉及到各个模块间进行通信,比如订单模块和个人中心模块,可能会需要频繁的传递数据和页面跳转,这个时候怎么去处理呢?我们能想到的方案就是采用类名反射,来动态创建需要跳转和交互的类,这样编译时就不会报错,运行时又可以完成模块间的交互。阿里巴巴推出的开源路由框架——ARouter就是基于反射和注解来解决这个问题的,本文不讲基本使用(基本使用在项目的github主页上已经将的非常详细了),通过分析整个路由过程来讲解它的基本原理。

说在前面

首先在我们需要用到的类的类名加上注解@Route(“/group/name”),注意这里需要至少两层路径(第一个是分组,第二个一般是类名)。这个注解就是代表这个类可以被其他模块找到的一个路径的注解,并且它是一个编译时注解,这就意味着在编译时就已经生成了相应的辅助类。ARouter把路由一共分为以下几类:

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”);

其中我们常用的就是ACTIVITY,PROVIDER,FRAGMENT这三个了,也基本上满足了我们模块化开发的需求。另外一点就是分组的概念,ARouter是按照组来进行整理的,也就是第一层的路径,所以前面说必须要两层路径,否则不知道归到哪里去,一般一个module按照模块名采用统一的分组标识。我们来看看注解生成的类(这里只包含了Activity,Fragment,Provider):

package com.alibaba.android.arouter.routes;
//。。。import省略
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$Personal implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/Personal/EARNING", RouteMeta.build(RouteType.ACTIVITY, PerEarningActivity.class, "/personal/earning", "personal", null, -1, -2147483648));
//...省略Activity,Fragment
atlas.put("/Personal/main", RouteMeta.build(RouteType.FRAGMENT, PerMainFragment.class, "/personal/main", "personal", null, -1, -2147483648));
atlas.put("/Personal/service", RouteMeta.build(RouteType.PROVIDER, PerServiceImpl.class, "/personal/service", "personal", null, -1, -2147483648));
}
}

以上就是所有注解的路径的信息集合,包含了所有的Activity,Fragment,Provider(一般一个module一个Provider就够用了,专门用来跟其他模块交互),并都以路径为key放到这个map中。

package com.alibaba.android.arouter.routes;
//。。。import省略
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Providers$$modlue_personal implements IProviderGroup {
@Override
public void loadInto(Map<String, RouteMeta> providers) {
providers.put("com.tb.test.service.ModulePersonalService", RouteMeta.build(RouteType.PROVIDER, PerServiceImpl.class, "/Personal/service", "personal", null, -1, -2147483648));
}
}

这个类是专门的Provider的索引的集合,所有的provider都被以全类名为索引放到一个map中。

package com.alibaba.android.arouter.routes;
//。。。import省略 /**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$modlue_personal implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("Personal", ARouter$$Group$$Personal.class);
}
}

这个类是所有的group的信息收集,全部都以group的名字为key,以注解生成的不同的group的类的class对象为value放入到一个map中。

总共就生成这三种类型的类,当然,如果你有不同的分组还会生成其他的类,不过都是这三种里面的一种。

完成了这些注解信息的收集,下面就会去使用这些信息来完成我们的跨模块交互了。

初始化过程

使用ARouter必须先要进行初始化:

if (isDebug()) {
// These two lines must be written before init, otherwise these configurations will be invalid in the init process
ARouter.openLog(); // Print log
ARouter.openDebug(); // Turn on debugging mode (If you are running in InstantRun mode, you must turn on debug mode! Online version needs to be closed, otherwise there is a security risk)
} ARouter.init(mApplication); // As early as possible, it is recommended to initialize in the Application

上面这段话就是去初始化Arouter,我们来看看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这个类里面,跟进去:

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.init这句话,跟进去看看,核心代码如下:

List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

            //
for (String className : classFileNames) {
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);
}
}

我们可以看到,首先是去获取到所有的app里的由ARouter注解生成的类的类名,他们的统一特点就是在同一个包下,包名为:com.alibaba.android.arouter.routes
然后就是循环遍历这些类,也就是刚才我们说的那三种类。在这里,有一个Warehouse类,看下代码:

/**
* Storage of route meta and other data.
*
* @author zhilong <a href="mailto:zhilong.lzl@alibaba-inc.com">Contact me.</a>
* @version 1.0
* @since 2017/2/23 下午1:39
*/
class Warehouse {
// 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<>(); static void clear() {
routes.clear();
groupsIndex.clear();
providers.clear();
providersIndex.clear();
interceptors.clear();
interceptorsIndex.clear();
}
}

很简单,定义了几个静态map,在初始化的时候来存放之前的注解生成的那些相关信息。初始化里面存的就是所有group索引的map,所有拦截器(本文不讲)索引的map,所有provider索引的map。至此,之前的那些注解类里面的信息都被存储起来了,这样后续在查找的时候就很方便可以找到对应的类,我们继续看初始化之后的afterInit方法:

static void afterInit() {
// Trigger interceptor init, use byName.
interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}

我们跟踪之后,发现最终会调用LogisticsCenter中的completion方法:

/**
* Completion the postcard by route metas
*
* @param postcard Incomplete postcard, should completion by this method.
*/
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
} RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) { // Maybe its does't exist, or didn't load.
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta.
if (null == groupMeta) {
throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
} else {
// Load route and cache it into memory, then delete from metas.
try {
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
} IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup()); if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
}
} catch (Exception e) {
throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
} 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(); if (MapUtils.isNotEmpty(paramsType)) {
// Set value by its type, just for params which annotation by @Param
for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
setValue(postcard,
params.getValue(),
params.getKey(),
resultMap.get(params.getKey()));
} // Save params name which need autoinject.
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 be implememt 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.put(providerMeta, provider);
instance = provider;
} catch (Exception e) {
throw new HandlerException("Init provider failed! " + e.getMessage());
}
}
postcard.setProvider(instance);
postcard.greenChannel(); // Provider should skip all of interceptors
break;
case FRAGMENT:
postcard.greenChannel(); // Fragment needn't interceptors
default:
break;
}
}
}

这个方法有点长,不过我们可以看到,核心功能就是postcard的信息完善。postcard就是整个路由过程中的信使,类似于生活中的明信片功能,包含了路由所有需要的信息。通过第34行的递归调用,根据groupsIndex和providersIndex保证了Warehouse里面的另外两个静态map(routes,providers)的赋值,这样最终都会走到36行else分支,去保证所有路由信息的完整性,另外swtich…case里面的postcard.greenChannel()其实是activity跳转专用的,目的是用来拦截activity跳转,来对跳转过程进行干预,在之前或者之后做一些自己的处理,所以greenChannel就是绿色通道,不进行拦截。另外代码里面也可以看到,类的生成都是采用getConstructor().newInstance()这种反射来进行的,最终调用:

ModulePersonalService service = (ModulePersonalService) ARouter.getInstance().build("/Personal/service").navigation();

得到这个跨模块服务之后,里面的所有方法都可以去调用来实现功能需求了。

调用过程
Activity的跳转如下:

            ARouter.getInstance().build("/Personal/main").navigation(activity);

最终调用代码则是_ARouter类里面的_navigation方法:

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;
}

可以看到对Activity的处理,最终就是调用startActivity方法,对provider就是返回一个类的实例,而BOARDCAST、CONTENT_PROVIDER、FRAGMENT也都是生成一个实例返回,对于METHOD、SERVICE暂时是没有处理的。

拦截器和自动注入的功能,本文没有去分析,一般跳到某一个页面需要判断是否登陆的时候,可以使用拦截器,自动注入可以在页面间传递数据,非常方便。

原文链接:https://blog.csdn.net/binbinqq86/article/details/80927885
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

组件化框架设计之阿里巴巴开源路由框架——ARouter原理分析(一)的更多相关文章

  1. 如何通过 Vue+Webpack 来做通用的前端组件化架构设计

    目录:   1. 架构选型     2. 架构目录介绍     3. 架构说明     4. 招聘消息 目前如果要说比较流行的前端架构哪家强,屈指可数:reactjs.angularjs.emberj ...

  2. WMRouter:美团外卖Android开源路由框架

    WMRouter是一款Android路由框架,基于组件化的设计思路,功能灵活,使用也比较简单. WMRouter最初用于解决美团外卖C端App在业务演进过程中的实际问题,之后逐步推广到了美团其他App ...

  3. golang web框架设计2:自定义路由

    继续学习谢大的Go web框架设计 HTTP路由 http路由负责将一个http的请求交到对应的函数处理(或者一个struct的方法),路由在框架中相当于一个事件处理器,而这个时间包括 用户请求的路径 ...

  4. 比JLRoutes更强大更好用的iOS开源路由框架—FFRouter

    目前iOS常用路由框架是JLRouter.HHRouter.MGJRouter. 但是这些路由库都各有不足,首先是JLRouter,用不到的功能繁多,而且基于遍历查找URL,效率低下.HHRouter ...

  5. .NET框架设计—常被忽视的框架设计技巧

    阅读目录: 1.开篇介绍 2.元数据缓存池模式(在运行时构造元数据缓存池) 2.1.元数据设计模式(抽象出对数据的描述数据) 2.2.借助Dynamic来改变IOC.AOP动态绑定的问题 2.3.元数 ...

  6. Android应用程序组件Content Provider在应用程序之间共享数据的原理分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6967204 在Android系统中,不同的应用 ...

  7. 阿里巴巴开源前端框架--Weex实践

    Weex是最近很火很NB的一个技术产品,因为本篇介绍的是怎样使用Weex的最佳实践,所以就不罗里吧嗦的夸它怎么怎么好了,感兴趣的可以访问Weex HomePage,或加入旺旺群:1330170019. ...

  8. 【通信框架】Apache的开源通信框架thrift概述

    在阅读的过程中有不论什么问题.欢迎一起交流 邮箱:1494713801@qq.com    QQ:1494713801 一.作用 Thrift("Scalable Cross-Languag ...

  9. 【通信框架】Google的开源通信框架protobuf概述

    在阅读的过程中有不论什么问题,欢迎一起交流 邮箱:1494713801@qq.com    QQ:1494713801 一.作用 protobuf(Protocol Buffers)是Google内部 ...

随机推荐

  1. 并行开发 1.Parallel

    原文:8天玩转并行开发——第一天 Parallel的使用 随着多核时代的到来,并行开发越来越展示出它的强大威力,像我们这样的码农再也不用过多的关注底层线程的实现和手工控制, 要了解并行开发,需要先了解 ...

  2. 【知识强化】第四章 指令系统 4.3 CISC和RISC的基本概念

    那么我们进入本章的最后一节,CISC和RISC. 我们先来回顾一下,我们这一章的一个概览.我们之前已经把指令格式和指令的寻址方式都讲完了,这两部分呢是本章的一个重点.而本章的这一部分,CISC和RIS ...

  3. XC6SLX45T-2FGG484C 原厂订购 原装正品

    作为一家科研公司,保证的原厂品质和正规采购渠道是科学严谨的研发工作中重要的一环,更是保证研发产品可靠.稳定的基础.而研发中所遇到的各种不可预测的情况更是每个工程师向技术的山峰攀登中时会遇到的各种难题. ...

  4. BJSV-P-003高清智能卡口系统

    高清智能卡口系统 捕获率99%,车牌识别率98%   ■ 道路安装示意图 ■ 系统结构 ■      抓拍实例 北京太速科技有限公司在线客服:QQ:448468544 淘宝网站:orihard.tao ...

  5. OpenCV图像数据字节对齐

    目录 1. IplImage的data字段,是char*类型,是4字节对齐. 2. 手动创建的Mat通常是没有字节对齐的 3. 从IplImage转过来的Mat,是字节对齐的 4. 总结 图像数据是否 ...

  6. 动态规划之数字三角形(POJ1163)

    在下面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大.路径上的每一步都只能往左下或 右下走.只需要求出这个最大和即可,不必给出具体路径. 既然求目标问题是根据查表得来的,自然 ...

  7. 太恐怖了!黑客正在GPON路由器中利用新的零日漏洞

    即使在意识到针对GPONWi-Fi路由器的各种主动网络攻击之后,如果您还没有将其从互联网上带走,那么请小心,因为一个新的僵尸网络已加入GPON组织,该组织正在利用未公开的零日漏洞(零时差攻击). 来自 ...

  8. 英语单词deprecated

    deprecated 来源——fdisk /dev/sdb [root@centos65 ~]# fdisk /dev/sdb WARNING: DOS-compatible mode is depr ...

  9. Android开发新手常见的10个误区

    在过去十年中最流行的移动应用开发开发平台中,我们认为,Android平台是一个新开发的最方便的平台.一个廉价的工具,友好的开发者社区,众所周知的编程语言(Java),使得开发Android应用程序从未 ...

  10. 使用Microsoft.Practices.Unity 依赖注入

    Unity是微软Patterns & Practices团队所开发的一个轻量级的,并且可扩展的依赖注入(Dependency Injection)容器,它支持常用的三种依赖注入方式:构造器注入 ...