这里最终调用到了 PostCard(path, group, uri, bundle)
,这里只是进行了一些参数的设置。
之后,如果我们调用 withInt
、withDouble
等方法,就可以进行参数的设置。例如 withInt
方法:
public Postcard withInt(@Nullable String key, int value) {
mBundle.putInt(key, value);
return this;
}
它实际上就是在对 Bundle
中设置对应的 key
、value
。
最后我们通过 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;
}
上面的代码主要有以下步骤:
-
通过
LogisticsCenter.completion
对postcard
进行补全。 -
如果
postcard
没有设置greenChannel
,则对postcard
的拦截器进行执行,执行完成后调用_navigation
方法真正实现跳转。 -
如果
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
的信息,它的步骤如下:
-
通过
Warehouse.routes.get
根据path
尝试获取RouteMeta
对象。 -
若获取不到
RouteMeta
对象,可能是不存在或是还没有进行加载(第一次都未加载),尝试获取RouteGroup
调用其loadInto
方法将RouteMeta
加载进Warehouse
,最后调用completion
重新尝试补全 。 -
将
RouteMeta
的信息设置到postcard
中,其中会将rawUri
的参数设置进Bundle
。 -
对于
Provider
和Fragment
特殊处理,其中Provider
会从Warehouse
中加载并构造它的对象,然后设置到postcard
。Provider
和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));
// …
}
它包括了我们补全所需要的如 Destination
、Class
、path
等信息,在生成代码时自动根据注解进行生成。
执行跳转
我们看看 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
对象。 -
对于
Broadcast
、ContentProvider
、Fragment
,反射构造对象后,将参数设置进去并返回。
可以发现 ARouter
的初始化和路由跳转的整体逻辑还是不难的,实际上就是对 Activity
、Fragment
的调转过程进行了包装。
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 {
callback.onContinue(postcard);
}
} catch (Exception e) {
callback.onInterrupt(e);
}
}
});
} else {
callback.onContinue(postcard);
}
这里的执行通过一个 Executor
执行,它首先构造了一个值为 interceptors
个数的 CountDownLatch
,之后通过 _execute
方法进行执行:
private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
if (index < Warehouse.interceptors.size()) {
IInterceptor iInterceptor = Warehouse.interceptors.get(index);
iInterceptor.process(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
// Last interceptor excute over with no exception.
counter.countDown();
_excute(index + 1, counter, postcard); // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
}
@Override
public void onInterrupt(Throwable exception) {
// Last interceptor excute over with fatal exception.
postcard.setTag(null == exception ? new HandlerException(“No message.”) : exception.getMessage()); // save the exception message for backup.
counter.cancel();
// Be attention, maybe the thread in callback has been changed,
// then the catch block(L207) will be invalid.
// The worst is the thread changed to main thread, then the app will be crash, if you throw this exception!
if (!Looper.getMainLooper().equals(Looper.myLooper())) { // You shouldn’t throw the exception if the thread is main thread.
throw new HandlerException(exception.getMessage());
}
}
});
}
}
这里会调用 interceptor.process
,并在其调用完成后调用 _execute
执行下一个 interceptor
,从而对每个 interceptor
进行执行。
那么 ARouter 是如何自动生成 RouteRoot
、RouteMeta
、ProviderGroup
、Provider
、Interceptor
的子类的呢?
实际上 ARouter 是通过 AnnotationProcessor
配合 AutoService
实现的,而对于类的生成主要是通过 JavaPoet
实现了对 Java 文件的编写,关于 JavaPoet
的具体使用可以看到其 GitHub 主页:https://github.com/square/javapoet
由于注解处理部分的代码大部分就是获取注解的属性,并结合 JavaPoet
生成每个 Element
对应的 Java 代码,这块的代码比较多且并不复杂,这里就不带大家去看这部分的源码了,有兴趣的读者可以看看 arouter-complier
包下的具体实现。
ARouter 的核心流程主要分为三部分:
编译期注解处理
通过 AnnotationProcessor
配合 JavaPoet
实现了编译期根据注解对 RouteRoot
、RouteMeta
、ProviderGroup
、Provider
、Interceptor
等类的代码进行生成,在这些类中完成了对 Warehouse
中装载注解相关信息的工作。
初始化
通过 ARouter.init
,可以对 ARouter 进行初始化,它主要分为两个步骤:
-
遍历 Apk 的 dex 文件,查找存放自动生成类的包下的类的
ClassName
集合。其中为了加快查找速度,通过一个线程池进行了异步查找,并通过CountDownLatch
来等待所有异步查找任务的结束。这个查找过程在非 debug 模式下是有缓存的,因为 release 的 Apk 其自动生成的类的信息必然不会变化 -
根据
ClassName
的类型,分别构建RouteRoot
、InterceptorGroup
、ProviderGroup
的对象并调用了其loadInto
方法将这些Group
的信息装载进Warehouse
,这个过程并不会将具体的RouteMeta
装载。这些Group
中主要包含了一些其对应的下一级的信息(如RouteGroup
的 Class 对象等),之后就只需要取出下一级的信息并从中装载,不再需要遍历 dex 文件。
路由
路由的过程,主要分为以下几步:
-
通过
ARouter
中的build(path)
方法构建出一个Postcard
,或直接通过其navigate(serviceClass)
方法构建一个Postcard
。 -
通过对
Postcard
中提供的一系列方法对这次路由进行配置,包括携带的参数,是否跳过拦截器等等。 -
通过
navigation
方法完成路由的跳转,它的步骤如下: -
通过
LogisticsCenter.completion
方法根据Postcard
的信息结合Warehouse
中加载的信息对Postcard
的Destination
、Type
等信息进行补全,这个过程中会实现对RouteMeta
信息的装载,并且对于未跳过拦截器的类会逐个调用拦截器进行拦截器处理。 -
根据补全后
Postcard
的具体类型,调用对应的方法进行路由的过程(如对于Activity
调用startActivity
,对于Fragment
构建对象并调用setArgument
)。 -
将
navigation
的结果返回(Activity
返回的就是 null)
本文在开源项目:https://github.com/Android-Alvin/Android-LearningNotes 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…
总结
作为一名从事Android的开发者,很多人最近都在和我吐槽Android是不是快要凉了?而在我看来这正是市场成熟的表现,所有的市场都是温水煮青蛙,永远会淘汰掉不愿意学习改变,安于现状的那批人,希望所有的人能在大浪淘沙中留下来,因为对于市场的逐渐成熟,平凡并不是我们唯一的答案!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
个调用拦截器进行拦截器处理。
-
根据补全后
Postcard
的具体类型,调用对应的方法进行路由的过程(如对于Activity
调用startActivity
,对于Fragment
构建对象并调用setArgument
)。 -
将
navigation
的结果返回(Activity
返回的就是 null)
本文在开源项目:https://github.com/Android-Alvin/Android-LearningNotes 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…
总结
作为一名从事Android的开发者,很多人最近都在和我吐槽Android是不是快要凉了?而在我看来这正是市场成熟的表现,所有的市场都是温水煮青蛙,永远会淘汰掉不愿意学习改变,安于现状的那批人,希望所有的人能在大浪淘沙中留下来,因为对于市场的逐渐成熟,平凡并不是我们唯一的答案!
[外链图片转存中…(img-bXCcqopw-1714480918699)]
[外链图片转存中…(img-c3o4GeAg-1714480918700)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!