ARouter 是阿里推出的一款页面路由框架。由于项目中采用了组件化架构进行开发,通过 ARouter 实现了页面的跳转,之前看它的源码时忘了写笔记,因此今天来重新对它的源码进行一次分析。
(顺手留下GitHub链接,需要获取相关面试或者面试宝典核心笔记PDF等内容的可以自己去找)
https://github.com/xiangjiana/Android-MS
(更多完整项目下载。未完待续。源码。图文知识后续上传github。)
(VX:mm14525201314)
本篇源码解析基于 ARouter 1.2.4
初始化
ARouter 在使用前需要通过调用 Arouter.init
方法并传入 Application
进行初始化:
/**
* 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.init
,这个 _ARouter
类才是 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
:
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
long startInit = System.currentTimeMillis();
Set<String> routerMap;
// 获取存储 ClassName 集合的 routerMap(debug 模式下每次都会拿最新的)
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
// 根据指定的 packageName 获取 package 下的所有 ClassName
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
// 存入 SP 缓存
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}
} else {
logger.info(TAG, "Load router map from cache.");
// release 模式下,已经缓存了 ClassName 列表
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();
// 遍历 ClassName
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
// 发现是 Root,加载类构建对象后通过 loadInto 加载进 Warehouse.groupsIndex
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
// 发现是 Interceptor,加载类构建对象后通过 loadInto 加载进 Warehouse.interceptorsIndex
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// 发现是 ProviderGroup,加载类构建对象后通过 loadInto 加载进 Warehouse.providersIndex
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
// ...
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}
这里主要有如下几步:
1.获取
com.alibaba.android.arouter.routes
下存储ClassName
的集合routerMap
。
2.若为 debug 模式或之前没有解析过routerMap
,则通过ClassUtils.getFileNameByPackageName
方法对指定 package 下的所有ClassName
进行解析并存入 SP。
3.若并非 debug 模式,并且之前已经解析过,则直接从 SP 中取出。(debug 每次都需要更新,因为类会随着代码的修改而变动)
4.遍历routerMap
中的ClassName
。
- 如果是
RouteRoot
,则加载类构建对象后通过loadInto
加载进Warehouse.groupsIndex
。- 如果是
InterceptorGroup
,则加载类构建对象后通过loadInto
加载进Warehouse.interceptorsIndex
。- 如果是
ProviderGroup
,则加载类构建对象后通过loadInto 加载进
Warehouse.providersIndex`。
解析 ClassName
我们先看看 ClassUtils.getFileNameByPackageName
是如何对指定 package 下的 ClassName
集合进行解析的:
public static Set<String> getFileNameByPackageName(Context context, final String packageName) {
final Set<String> classNames = new HashSet<>();
// 通过 getSourcePaths 方法获取 dex 文件 path 集合
List<String> paths = getSourcePaths(context);
// 通过 CountDownLatch 对 path 的遍历处理进行控制
final CountDownLatch parserCtl = new CountDownLatch(paths.size());
// 遍历 path,通过 DefaultPoolExecutor 并发对 path 进行处理
for (final String path : paths) {
DefaultPoolExecutor.getInstance().execute(new Runnable() {
@Override
public void run() {
// 加载 path 对应的 dex 文件
DexFile dexfile = null;
try {
if (path.endsWith(EXTRACTED_SUFFIX)) {
// zip 结尾通过 DexFile.loadDex 进行加载
dexfile = DexFile.loadDex(path, path + ".tmp", 0);
} else {
// 否则通过 new DexFile 加载
dexfile = new DexFile(path);
}
// 遍历 dex 中的 Entry
Enumeration<String> dexEntries = dexfile.entries();
while (dexEntries.hasMoreElements()) {
// 如果是对应的 package 下的类,则添加其 className
String className = dexEntries.nextElement();
if (className.startsWith(packageName)) {
classNames.add(className);
}
}
} catch (Throwable ignore) {
Log.e("ARouter", "Scan map file in dex files made error.", ignore);
} finally {
if (null != dexfile) {
try {
dexfile.close();
} catch (Throwable ignore) {
}
}
parserCtl.countDown();
}
}
});
}
// 所有 path 处理完成后,继续向下走
parserCtl.await();
Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
return classNames;
}
这里的步骤比较简单,主要是如下的步骤:
1.通过
getSourcePaths
方法获取dex
文件的 path 集合。
2.创建了一个CountDownLatch
控制dex
文件的并行处理,以加快速度。
3.遍历 path 列表,通过DefaultPoolExecutor
对 path 并行处理。
4.加载 path 对应的dex
文件,并对其中的 Entry 进行遍历,若发现了对应 package 下的ClassName
,将其加入结果集合。
5.所有dex
处理完成后,返回结果
关于 getSourcePaths
如何获取到的 dex
集合这里就不纠结了,因为我们的关注点不在这里。
初始化 Warehouse
Warehouse 实际上就是仓库的意思,它存放了 ARouter 自动生成的类(RouteRoot
、InterceptorGroup
、ProviderGroup
)的信息。
我们先看看 Warehouse 类究竟是怎样的:
class Warehouse {
// 保存 RouteGroup 对应的 class 以及 RouteMeta
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
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<IInterceptor> 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$$Root$$homework implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("homework", ARouter$$Group$$homework.class);
}
}
可以看到,它在 groupsIndex
中对这个 RouteRoot
中的 IRouteGroup
进行了注册,也就是向 groupIndex
中注册了 Route Group 对应的 IRouteGroup
类。其他类也是一样,通过自动生成的代码将数据填入 Map 或 List 中。
可以发现,初始化过程主要完成了对自动生成的路由相关类 RouteRoot
、Interceptor
、ProviderGroup
的加载,对它们通过反射构造后将信息加载进了 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)
,这里只是进行了一些参数的设置。
之后,如果我们调用 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;
}
上面的代码主要有以下步骤:
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
中加载并构造它的对象,然后设置到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
的调转过程进行了包装。
Service 的获取
ARouter 除了可以通过 ARouter.getInstance().build().navigation()
这样的方式实现页面跳转之外,还可以通过 ARouter.getInstance().navigation(XXService.class)
这样的方式实现跨越组件的服务获取,我们看看它是如何实现的:
public <T> T navigation(Class<? extends T> service) {
return _ARouter.getInstance().navigation(service);
}
仍然跳转到了_ARouter
中去实现:
protected <T> 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 方法进行执行:
注解处理
那么 ARouter
是如何自动生成 RouteRoot
、RouteMeta
、ProviderGroup
、Provider
、Interceptor
的子类的呢?
实际上 ARouter 是通过 AnnotationProcessor 配合 AutoService 实现的,而对于类的生成主要是通过 JavaPoet 实现了对 Java 文件的编写,关于 JavaPoet 的具体使用可以看到其 GitHub 主页https://github.com/xiangjiana/Android-MS
由于注解处理部分的代码大部分就是获取注解的属性,并结合 JavaPoet
生成每个 Element 对应的 Java 代码,这块的代码比较多且并不复杂,这里就不带大家去看这部分的源码了,有兴趣的读者可以看看 arouter-complier
包下的具体实现。
总结
ARouter 的核心流程主要分为三部分:
编译期注解处理
通过 AnnotationProcessor
配合 JavaPoet
实现了编译期根据注解对 RouteRoot
、RouteMeta
、ProviderGroup
、Provider
、Interceptor
等类的代码进行生成,在这些类中完成了对 Warehouse 中装载注解相关信息的工作。
初始化
通过ARouter.init
,可以对ARouter
进行初始化,它主要分为两个步骤:
1.遍历
Apk
的dex
文件,查找存放自动生成类的包下的类的ClassName
集合。其中为了加快查找速度,通过一个线程池进行了异步查找,并通过CountDownLatch
来等待所有异步查找任务的结束。这个查找过程在非 debug 模式下是有缓存的,因为 release 的 Apk 其自动生成的类的信息必然不会变化
2.根据ClassName
的类型,分别构建RouteRoot
、InterceptorGroup
、ProviderGroup
的对象并调用了其loadInto
方法将这些 Group 的信息装载进 Warehouse,这个过程并不会将具体的RouteMeta
装载。这些 Group 中主要包含了一些其对应的下一级的信息(如RouteGroup
的 Class 对象等),之后就只需要取出下一级的信息并从中装载,不再需要遍历 dex 文件。
路由
路由的过程,主要分为以下几步:
1. 通过
ARouter
中的 build(path) 方法构建出一个 Postcard,或直接通过其navigate(serviceClass)
方法构建一个 Postcard。
2. 通过对 Postcard 中提供的一系列方法对这次路由进行配置,包括携带的参数,是否跳过拦截器等等。
3.通过 navigation 方法完成路由的跳转,它的步骤如下:
- a.通过
LogisticsCenter.completion
方法根据 Postcard 的信息结合 Warehouse 中加载的信息对 Postcard 的 Destination、Type 等信息进行补全,这个过程中会实现对RouteMeta
信息的装载,并且对于未跳过拦截器的类会逐个调用拦截器进行拦截器处理。- b.根据补全后 Postcard 的具体类型,调用对应的方法进行路由的过程(如对于 Activity 调用
startActivity
,对于 Fragment 构建对象并调用setArgument
)。
4.将 navigation 的结果返回(Activity 返回的就是 null)
(顺手留下GitHub链接,需要获取相关面试或者面试宝典核心笔记PDF等内容的可以自己去找)
https://github.com/xiangjiana/Android-MS
相关推荐
组件化开发推荐目前比较流行的ARouter框架,ARouter是由阿里开发团队开源的组件化框架,目前应用比较多,ARouter经过多年广大开发者测验并改进已经比较完善,选择阿里团队一直有维护更新的ARouter更稳妥一些。
XyPlayer智能解析源码v4.0.8正式版,无需安装,上传即可使用,非常便捷。它支持二次VIP影视解析和屏蔽广告功能。借助XyPlayer正式版源码,你可以轻松搭建专属于自己的影视平台,甚至还能调用第三方资源网站进行播放...
一次解析源码,部分需要更新COOKIE。自己下载后更具里面内容样式抓取吧!
XyPlayer智能解析源码,无需安装,上传即用,非常方便,支持二次VIP影视解析,支持屏蔽广告,XyPlayer正式版源码能让你轻松搭建专属于你的影视平台,甚至还能调用第三方的资源网站来进行播放。本次为大家带来...
源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析...
易语言源码易语言视频真实地址解析源码.rar 易语言源码易语言视频真实地址解析源码.rar 易语言源码易语言视频真实地址解析源码.rar 易语言源码易语言视频真实地址解析源码.rar 易语言源码易语言视频真实地址解析...
安装教程 将ckplayer文件夹全部上传至网站空间根目录即可 接口测试:http://你的域名/ckplayer/m3u8.php?url= 播放地址要有效才能解析,失效的地址无法解析 可自行整合到其他程序中!
素材资源解析系统源码 代下程序 第三方平台下载站程序千图网千库网等素材网站下载网站, 1.需要目标站会员解析 2.随着目标站规则的更改,解析接口可能会失效—接口不保证长期有效,不会修改的最好别买,因为有些...
【标题】"一次解析源码"通常指的是在软件开发中,对原始的编程代码进行一次性编译或解析,形成可以直接运行或使用的程序。这个过程不涉及对已有的编译结果进行再次解析,而是直接处理源代码,使得用户能够更深入地...
在深入学习Vue.js时,理解其源码是非常重要的一步,可以帮助开发者更好地掌握其工作原理,从而优化应用性能和解决实际问题。下面我们将详细探讨"vue源码解析.zip"中的关键知识点。 首先,我们来看"Vue源码探秘之...
【素材解析源码】是一种用于解析和处理网络素材的编程代码,主要应用于网站开发中。在互联网上,素材包括但不限于图片、音频、视频、文本等,这些资源在网页设计和内容展示中起着至关重要的作用。源码是程序的基础,...
易语言源码易语言百度云盘解析源码.rar 易语言源码易语言百度云盘解析源码.rar 易语言源码易语言百度云盘解析源码.rar 易语言源码易语言百度云盘解析源码.rar 易语言源码易语言百度云盘解析源码.rar 易语言源码...
易语言源码易语言百度网盘解析源码.rar 易语言源码易语言百度网盘解析源码.rar 易语言源码易语言百度网盘解析源码.rar 易语言源码易语言百度网盘解析源码.rar 易语言源码易语言百度网盘解析源码.rar 易语言源码...
千图等12网素材解析源码程序 支持解析网站:千图/90设计/千库/觅元素/包图/摄图/全图/图品汇等网站 支持千图其他各站图片解析,解析需要配置相应的官方会员cookie。 后台支持批量生产网站会员账号密码以及会员...
ANDROID SDK 源码解析 ANDROID SDK 源码解析 ANDROID SDK 源码解析
AT命令简易解析 AT命令的应用场合很多,也是应用最为通用的命令。而AT命令并不像通信协议特定的数据帧格式,解析AT命令需要匹配每个字符。AT命令一般有三种状态:查询、执行、设置,根据这三种状态,本文将使用一种...
简单实用的XML解析源码,开发的时候用到所以研究了下,现在分享出来给大家。 下载后取出里面的Markup.cpp和Markup.h,导入你的工程里面,CMarkup类就可以用了。其中的MSXML是另一套解析源码,主要使用COM,大家可以...
mybatis 3.x源码深度解析与最佳实践.html
java socket源码解析 java socket源码解析 java socket源码解析 java socket源码解析
无广告测试接口:...各位站长可以自己注册解析平台,然后下载客户端,自己搭建无广告解析! 保证全部无暗刷、无暗弹、无任何远程广告js、站长们可以使用测试验证,保证100%无广告!#源码类# #资源分享达人#