ARouter 源码解析:阿里推出的路由框架

static Map<String, RouteMeta> routes = new HashMap<>();

// 保存 Provider 以及 RouteMeta

static Map<Class, IProvider> providers = new HashMap<>();

static Map<String, RouteMeta> providersIndex = new HashMap<>();

// 保存 Interceptor 对应的 class 以及 Inteceptor

static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>(“More than one interceptors use same priority [%s]”);

static List interceptors = new ArrayList<>();

static void clear() {

routes.clear();

groupsIndex.clear();

providers.clear();

providersIndex.clear();

interceptors.clear();

interceptorsIndex.clear();

}

}

可以发现 Warehouse 就是一个纯粹用来存放信息的仓库类,它的数据的实际上是通过上面的几个自动生成的类在 loadInto 中对 Warehouse 主动填入数据实现的。

例如我们打开一个自动生成的 IRouteRoot 的实现类:

public class ARouter R o o t Root Roothomework implements IRouteRoot {

@Override

public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {

routes.put(“homework”, ARouter G r o u p Group Grouphomework.class);

}

}

可以看到,它在 groupsIndex 中对这个 RouteRoot 中的 IRouteGroup 进行了注册,也就是向 groupIndex 中注册了 Route Group 对应的 IRouteGroup 类。其他类也是一样,通过自动生成的代码将数据填入 Map 或 List 中。

可以发现,初始化过程主要完成了对自动生成的路由相关类 RouteRootInterceptorProviderGroup 的加载,对它们通过反射构造后将信息加载进了 Warehouse 类中。

路由跳转


Postcard 的创建

下面我们看看路由的跳转是如何实现的,我们先看到 ARouter.build 方法:

public Postcard build(String path) {

return _ARouter.getInstance().build(path);

}

它转调到了 _ARouter 的 build 方法:

protected Postcard build(String path) {

if (TextUtils.isEmpty(path)) {

throw new HandlerException(Consts.TAG + “Parameter is invalid!”);

} else {

PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);

if (null != pService) {

path = pService.forString(path);

}

return build(path, extractGroup(path));

}

}

它首先通过 ARouter.navigation 获取到了 PathReplaceService,它需要用户进行实现,若没有实现会返回 null,若有实现则调用了它的 forString 方法传入了用户的 Route Path 进行路径的预处理。

最后转调到了 build(path, group)group 通过 extractGroup 得到:

private String extractGroup(String path) {

if (TextUtils.isEmpty(path) || !path.startsWith(“/”)) {

throw new HandlerException(Consts.TAG + “Extract the default group failed, the path must be start with ‘/’ and contain more than 2 ‘/’!”);

}

try {

String defaultGroup = path.substring(1, path.indexOf(“/”, 1));

if (TextUtils.isEmpty(defaultGroup)) {

throw new HandlerException(Consts.TAG + “Extract the default group failed! There’s nothing between 2 ‘/’!”);

} else {

return defaultGroup;

}

} catch (Exception e) {

logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage());

return null;

}

}

extractGroup 实际上就是对字符串处理,取出 Route Group 的名称部分。

protected Postcard build(String path, String group) {

if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {

throw new HandlerException(Consts.TAG + “Parameter is invalid!”);

} else {

PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);

if (null != pService) {

path = pService.forString(path);

}

return new Postcard(path, group);

}

}

build(path, group) 方法同样也会尝试获取到 PathReplaceService 并对 path 进行预处理。之后通过 path 与 group 构建了一个 Postcard 类:

public Postcard(String path, String group) {

this(path, group, null, null);

}

public Postcard(String path, String group, Uri uri, Bundle bundle) {

setPath(path);

setGroup(group);

setUri(uri);

this.mBundle = (null == bundle ? new Bundle() : bundle);

}

这里最终调用到了 PostCard(path, group, uri, bundle),这里只是进行了一些参数的设置。

之后,如果我们调用 withIntwithDouble 等方法,就可以进行参数的设置。例如 withInt 方法:

public Postcard withInt(@Nullable String key, int value) {

mBundle.putInt(key, value);

return this;

}

它实际上就是在对 Bundle 中设置对应的 keyvalue

最后我们通过 navigation 即可实现最后的跳转:

public Object navigation() {

return navigation(null);

}

public Object navigation(Context context) {

return navigation(context, null);

}

public Object navigation(Context context, NavigationCallback callback) {

return ARouter.getInstance().navigation(context, this, -1, callback);

}

public void navigation(Activity mContext, int requestCode) {

navigation(mContext, requestCode, null);

}

public void navigation(Activity mContext, int requestCode, NavigationCallback callback) {

ARouter.getInstance().navigation(mContext, this, requestCode, callback);

}

通过如上的 navigation 可以看到,实际上它们都是最终调用到 ARouter.navigation 方法,在没有传入 Context 时会使用 Application 初始化的 Context,并且可以通过 NavigationCallback 对 navigation 的过程进行监听。

public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {

return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);

}

ARouter 仍然只是将请求转发到了 _ARouter

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {

try {

// 通过 LogisticsCenter.completion 对 postcard 进行补全

LogisticsCenter.completion(postcard);

} catch (NoRouteFoundException ex) {

// …

}

if (null != callback) {

callback.onFound(postcard);

}

// 如果设置了 greenChannel,会跳过所有拦截器的执行

if (!postcard.isGreenChannel()) {

// 没有跳过拦截器,对 postcard 的所有拦截器进行执行

interceptorService.doInterceptions(postcard, new InterceptorCallback() {

@Override

public void onContinue(Postcard postcard) {

_navigation(context, postcard, requestCode, callback);

}

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

}

上面的代码主要有以下步骤:

  1. 通过 LogisticsCenter.completion 对 postcard 进行补全。

  2. 如果 postcard 没有设置 greenChannel,则对 postcard 的拦截器进行执行,执行完成后调用 _navigation 方法真正实现跳转。

  3. 如果 postcard 设置了 greenChannel,则直接跳过所有拦截器,调用 _navigation 方法真正实现跳转。

Postcard 的补全

我们看看 LogisticsCenter.completion 是如何实现 postcard 的补全的:

public synchronized static void completion(Postcard postcard) {

if (null == postcard) {

throw new NoRouteFoundException(TAG + “No postcard!”);

}

// 通过 Warehouse.routes.get 尝试获取 RouteMeta

RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());

if (null == routeMeta) {

// 若 routeMeta 为 null,可能是并不存在,或是还没有加载进来

// 尝试获取 postcard 的 RouteGroup

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 {

// …

// 如果找到了对应的 RouteGroup,则将其加载进来并重新调用 completion 进行补全

IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();

iGroupInstance.loadInto(Warehouse.routes);

Warehouse.groupsIndex.remove(postcard.getGroup());

// …

completion(postcard); // Reload

}

} else {

// 如果找到了对应的 routeMeta,将它的信息设置进 postcard 中

postcard.setDestination(routeMeta.getDestination());

postcard.setType(routeMeta.getType());

postcard.setPriority(routeMeta.getPriority());

postcard.setExtra(routeMeta.getExtra());

Uri rawUri = postcard.getUri();

// 将 uri 中的参数设置进 bundle 中

if (null != rawUri) {

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 auto inject.

postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));

}

// Save raw uri

postcard.withString(ARouter.RAW_URI, rawUri.toString());

}

// 对于 provider 和 fragment,进行特殊处理

switch (routeMeta.getType()) {

case PROVIDER:

// 如果是一个 provider,尝试从 Warehouse 中查找它的类并构造对象,然后将其设置到 provider

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

// provider 和 fragment 都会跳过拦截器

postcard.greenChannel();

break;

case FRAGMENT:

// provider 和 fragment 都会跳过拦截器

postcard.greenChannel();

default:

break;

}

}

}

这个方法主要完成了对 postcard 的信息与 Warehouse 的信息进行结合,以补全 postcard 的信息,它的步骤如下:

  1. 通过 Warehouse.routes.get 根据 path 尝试获取 RouteMeta 对象。

  2. 若获取不到 RouteMeta 对象,可能是不存在或是还没有进行加载(第一次都未加载),尝试获取 RouteGroup 调用其 loadInto 方法将 RouteMeta 加载进 Warehouse,最后调用 completion 重新尝试补全 。

  3. 将 RouteMeta 的信息设置到 postcard 中,其中会将 rawUri 的参数设置进 Bundle

  4. 对于 Provider 和 Fragment 特殊处理,其中 Provider 会从 Warehouse 中加载并构造它的对象,然后设置到 postcardProvider 和 Fragment 都会跳过拦截器。

RouteGroup 的 loadInto 仍然是自动生成的,例如下面就是一些自动生成的代码:

public void loadInto(Map<String, RouteMeta> atlas) {

atlas.put(“/homework/commit”, RouteMeta.build(RouteType.ACTIVITY, HomeworkCommitActivity.class, “/homework/commit”, “homework”, null, -1, -2147483648));

// …

}

它包括了我们补全所需要的如 DestinationClasspath 等信息,在生成代码时自动根据注解进行生成。

执行跳转

我们看看 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:

// 对 Activity,构造 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);

}

// 切换到主线程,根据是否需要 result 调用不同的 startActivity 方法

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:

// provider 直接返回对应的 provider

return postcard.getProvider();

case BOARDCAST:

case CONTENT_PROVIDER:

case FRAGMENT:

// 对于 broadcast、contentprovider、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;

}

可以发现,它会根据 postcard 的 type 来分别处理:

  • 对于 Activity,会构造一个 Intent 并将之前 postcard 中的参数设置进去,之后会根据是否需要 result 调用不同的 startActivity 方法。

  • 对于 Provider,直接返回其对应的 provider 对象。

  • 对于 BroadcastContentProviderFragment,反射构造对象后,将参数设置进去并返回。

可以发现 ARouter 的初始化和路由跳转的整体逻辑还是不难的,实际上就是对 ActivityFragment 的调转过程进行了包装。

Service 的获取


ARouter 除了可以通过 ARouter.getInstance().build().navigation() 这样的方式实现页面跳转之外,还可以通过 ARouter.getInstance().navigation(XXService.class) 这样的方式实现跨越组件的服务获取,我们看看它是如何实现的:

public T navigation(Class<? extends T> service) {

return _ARouter.getInstance().navigation(service);

}

仍然跳转到了 _ARouter 中去实现:

protected T navigation(Class<? extends T> service) {

try {

Postcard postcard = LogisticsCenter.buildProvider(service.getName());

// Compatible 1.0.5 compiler sdk.

// Earlier versions did not use the fully qualified name to get the service

if (null == postcard) {

// No service, or this service in old version.

postcard = LogisticsCenter.buildProvider(service.getSimpleName());

}

if (null == postcard) {

return null;

}

LogisticsCenter.completion(postcard);

return (T) postcard.getProvider();

} catch (NoRouteFoundException ex) {

logger.warning(Consts.TAG, ex.getMessage());

return null;

}

}

这里首先通过 LogisticsCenter.buildProvider 传入 service.class 的 name 构建出了一个 postcard

而在 ARouter 老版本中,并不是通过这样一个完整的 name 来获取 Service 的,而是通过 simpleName,下面为了兼容老版本,在获取不到时会尝试用老版本的方式重新构建一次。

之后会通过 LogisticsCenter.completion 对 postcard 进行补全,最后通过 postcard.Provider 获取对应的 Provider

除了 buildProvider 之外,其他方法我们已经在前面进行过分析,就不再赘述了:

public static Postcard buildProvider(String serviceName) {

RouteMeta meta = Warehouse.providersIndex.get(serviceName);

if (null == meta) {

return null;

} else {

return new Postcard(meta.getPath(), meta.getGroup());

}

}

这里实际上非常简单,就是通过 Warehouse 中已经初始化的 providersIndex 根据 serviceName 获取对应的 RouteMeta,之后根据 RouteMeta 的 path 和 group 返回对应的 Postcard

拦截器机制


通过前面的分析,可以发现 ARouter 中存在一套拦截器机制,在 completion 的过程中对拦截器进行了执行,让我们看看它的拦截器机制的实现。

我们先看到 IInterceptor 接口:

public interface IInterceptor extends IProvider {

/**

  • The operation of this interceptor.

  • @param postcard meta

  • @param callback cb

*/

void process(Postcard postcard, InterceptorCallback callback);

}

拦截器中主要通过 process 方法完成执行过程,可以在其中对 postcard 进行处理。而拦截器的执行我们知道,是通过 InterceptorServiceImpl.doInterceptions 实现的:

if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {

checkInterceptorsInitStatus();

if (!interceptorHasInit) {

callback.onInterrupt(new HandlerException(“Interceptors initialization takes too much time.”));

return;

}

LogisticsCenter.executor.execute(new Runnable() {

@Override

public void run() {

CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());

try {

_excute(0, interceptorCounter, postcard);

interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);

if (interceptorCounter.getCount() > 0) { // Cancel the navigation this time, if it hasn’t return anythings.

callback.onInterrupt(new HandlerException(“The interceptor processing timed out.”));

} else if (null != postcard.getTag()) { // Maybe some exception in the tag.

callback.onInterrupt(new HandlerException(postcard.getTag().toString()));

} else {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

获取!!(备注:Android)**

最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

[外链图片转存中…(img-12JypKTn-1712252393843)]

【算法合集】

[外链图片转存中…(img-RJgpWvqG-1712252393844)]

【延伸Android必备知识点】

[外链图片转存中…(img-26mWIkDA-1712252393844)]

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值