ARouter 基本原理
使用目的
使用各大开源路由库的目的,比如 ARouter、TheRouter 等,都是为了组件化、模块化准备的。各大模块之间的解耦,互不依赖又能调用对方提供的服务能力,来完成功能的开发。为什么解耦呢?是为了开发方便以及扩展性和组合性更好。
要想实现一个路由,需要满足最最核心的两点功能:
- 能建立路径(path)与目标类(Activity/Fragment等)的映射关系
- 模块间无需类依赖即可跳转
自然,我们就想到用一个全局的地方来保存页面数据信息,于是可以设计出这样的结构:公共模块 lib_common
中有个 RouterMap 来保存页面信息
kotlin
object RouterMap {
const val TAG = "RouterMap"
private val map = mutableMapOf<String, Class<*>>()
fun addRouter(path: String, clazz: Class<*>) {
map[path] = clazz
}
fun startActivity(context: Context, path: String) {
val clazz = map[path]
if (clazz == null) {
showToast(context, "not found activity, path: $path")
Log.e(TAG, "not found activity, path: $path")
return
}
try {
if (Activity::class.java.isAssignableFrom(clazz)) {
val intent = Intent(context, clazz)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
} else {
Log.e(TAG, "startActivity: path is not activity", )
}
} catch (e: Exception) {
Log.e(TAG, "start activity error", e )
showToast(context, "start activity error")
}
}
private fun showToast(context: Context, log: String) {
Toast.makeText(context , log , Toast.LENGTH_SHORT).show()
}
}
其他模块引用 lib_common
模块,然后在模块初始化时调用 addRouter
来注册。这一步可以使用 Startup / Provider
。
kotlin
class UserInitializer : Initializer<Unit> {
override fun create(context: Context) {
initRouter()
}
private fun initRouter() {
RouterMap.addRouter("user/user", UserActivity::class.java)
RouterMap.addRouter("user/detail", UserDetailActivity::class.java)
}
override fun dependencies(): List<Class<out Initializer<*>?>?> {
return emptyList()
}
}
这样其他地方就能跳转
java
RouterMap.startActivity(context, "user/user")
当然,聪明的你想到手动注册太麻烦、并且容易遗忘。于是想到可以通过 AOP / ASM 通过注解去自动生成。于是 ARouter 中有 @Router
、@Intercept
,@Autowired
注解来用于表明页面或者自动注入。在编译器生成对应代码。
Warehouse 仓库
ARouter 中保存路由信息的类是 Warehouse.java
比较简单,几个 map 对象和 list,作用是保存所有的路由页面、服务和拦截器。@Router
、@Intercept
修饰的类都在里面。
java
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();
}
}
当然保存的信息不止 Class 这一点信息,于是有了 RouteMeta
RouteMeta 路由元数据
RouteMeta 顾名思义路由元数据,用来保存路由信息
java
public class RouteMeta {
private RouteType type; // 路由类型
private Element rawType; // 路由的原始类型
private Class<?> destination; // 目的地
private String path; // 路线路径
private String group; // Group of route
private int priority = -1; // 优先级,数字越小,优先级越高
private int extra; // Extra data
private Map<String, Integer> paramsType; // Param type
private String name;
private Map<String, Autowired> injectConfig; // Cache inject config.
public RouteMeta() {
}
public RouteMeta build(xxx){}
}
初始化
那么是什么时候加载(addRouter)的呢?当然是 ARouter.init(application)
, ARouter 类是对外暴露的调用类,点进 ARouter.init() 可以看到内部的实现都是由 _ARouter、LogisticsCenter 实现。
java
// LogisticsCenter.java
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
long startInit = System.currentTimeMillis();
//load by plugin first
loadRouterMap();
if (registerByPlugin) {
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
Set<String> routerMap;
// It will rebuild router map every times when debuggable.
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
// These class was generated by arouter-compiler.
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}
PackageUtils.updateVersion(context); // Save new version name when router map update finishes.
} else {
logger.info(TAG, "Load router map from cache.");
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}
// 遍历生成的文件路径,根据不同类型存储
for (String className : routerMap) {
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);
}
}
}
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}
从中可以看出几点:
ClassUtils.getFileNameByPackageName();
通过扫描生成的类去获取路由表- 根据是否是 debugger 或新版本来更新路由表,否则从 sp 缓存中获取,避免每次打开应用都做扫描耗时操作
- 拿到路由表
routerMap
后根据类型添加到 (loadInfo) 仓库Warehouse
里
在初始化完成后,进行拦截器的初始化。看另一篇文章。
build().navigation() 跳转页面
初始化完成后,跳转页面前一般会调用 build(path)
,并且后面可能会附带参数 withXXX(param)
。build() 是创建了一个 Postcard
对象,存储对应的参数。 可以看出 Postcard
继承于 RouterMeta
,并且扩展了很多属性。
java
public final class Postcard extends RouteMeta {
// Base
private Uri uri;
private Object tag; // 标签,拦截器出错的错误信息。 后面拦截器流程中使用到
private Bundle mBundle; // 数据传递
private int flags = 0; // 就是启动 Activity 的 Intent.FLAG
private int timeout = 300; // Navigation timeout, TimeUnit.Second
private IProvider provider; // It will be set value, if this postcard was provider.
private boolean greenChannel; // 绿色通道,用于判断是否拦截
private SerializationService serializationService;
private Context context; // May application or activity, check instance type before use it.
private String action;
// Animation
private Bundle optionsCompat; // The transition animation of activity
private int enterAnim = -1;
private int exitAnim = -1;
}
navigation()
java
navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback)
navigation(Class<? extends T> service)
navigation() 跳转流程详解
核心跳转方法
ARouter的跳转核心在 _ARouter.navigation()
方法中:
java
protected Object navigation(final Context context, final Postcard postcard,
final int requestCode, final NavigationCallback callback) {
try {
// 1. 准备跳转 - 补全Postcard信息
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
// 路由未找到处理
if (callback != null) {
callback.onLost(postcard);
}
return null;
}
// 2. 跳转回调通知
if (callback != null) {
callback.onFound(postcard);
}
// 3. 绿色通道检查(是否跳过拦截器)
if (!postcard.isGreenChannel()) {
// 执行拦截器逻辑
if (!interceptorService.doInterceptions(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
// 拦截器通过,执行实际跳转
_navigation(context, postcard, requestCode, callback);
}
@Override
public void onInterrupt(Throwable exception) {
// 被拦截器中断
if (callback != null) {
callback.onInterrupt(postcard);
}
}
})) {
return null;
}
} else {
// 绿色通道直接跳转
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
LogisticsCenter.completion() - 路由信息补全
这个方法负责根据path找到对应的RouteMeta并填充Postcard:
java
public synchronized static void completion(Postcard postcard) {
// 1. 根据path从缓存中获取路由信息
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) {
// 2. 如果缓存中没有,尝试动态加载路由组
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());
if (null == groupMeta) {
throw new NoRouteFoundException("未找到对应路由: " + postcard.getPath());
}
// 3. 实例化路由组并加载到缓存
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
// 4. 重新尝试获取
completion(postcard);
} else {
// 5. 填充Postcard信息
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
// 6. 根据不同类型处理
switch (routeMeta.getType()) {
case PROVIDER:
// 如果是服务,创建Provider实例
Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
IProvider instance = Warehouse.providers.get(providerMeta);
if (null == instance) {
IProvider provider = providerMeta.getConstructor().newInstance();
provider.init(mContext);
Warehouse.providers.put(providerMeta, provider);
instance = provider;
}
postcard.setProvider(instance);
postcard.greenChannel(); // Provider跳转走绿色通道
break;
case FRAGMENT:
postcard.greenChannel(); // Fragment也走绿色通道
break;
}
}
}
_navigation() - 实际跳转逻辑
java
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:
// 1. 创建Intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// 2. 设置Flags
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
// 3. 执行跳转
if (requestCode >= 0) {
if (currentContext instanceof Activity) {
((Activity) currentContext).startActivityForResult(intent, requestCode, postcard.getOptionsBundle());
} else {
Log.w(TAG, "非Activity环境无法使用startActivityForResult");
}
} else {
currentContext.startActivity(intent, postcard.getOptionsBundle());
}
// 4. 动画设置
if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim())
&& currentContext instanceof Activity) {
((Activity) currentContext).overridePendingTransition(
postcard.getEnterAnim(), postcard.getExitAnim());
}
// 5. 跳转完成回调
if (callback != null) {
callback.onArrival(postcard);
}
break;
case PROVIDER:
// 返回Provider实例
return postcard.getProvider();
case FRAGMENT:
try {
// 返回Fragment实例
Fragment fragment = postcard.getDestination().getConstructor().newInstance();
Bundle args = postcard.getExtras();
if (null != args) {
fragment.setArguments(args);
}
return fragment;
} catch (Exception ex) {
logger.error(Consts.TAG, "获取Fragment实例失败", ex);
}
break;
}
return null;
}
拦截器执行流程
拦截器的执行是一个递归过程:
java
// InterceptorServiceImpl.java
@Override
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
// 检查是否初始化
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 {
_execute(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((Throwable) postcard.getTag());
} else {
callback.onContinue(postcard);
}
} catch (Exception e) {
callback.onInterrupt(e);
}
}
});
} else {
callback.onContinue(postcard);
}
}
/**
* Excute interceptor
*
* @param index current interceptor index
* @param counter interceptor counter
* @param postcard routeMeta
*/
private static void _execute(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();
_execute(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 execute over with fatal exception.
postcard.setTag(null == exception ? new HandlerException("No message.") : exception); // save the exception message for backup.
counter.cancel();
}
});
}
}
跳转流程总结
整个跳转流程可以概括为以下几个关键步骤:
- Build阶段:创建Postcard,设置基本参数
- Completion阶段:根据path查找路由信息,补全Postcard
- 拦截检查:非绿色通道时按顺序执行拦截器
- 实际跳转:根据目标类型(Activity/Provider/Fragment)执行相应跳转逻辑
- 回调通知 :在整个过程中通过
NavigationCallback
通知调用方状态变化
这种设计实现了路由查找与实际跳转的解耦,使得框架具有很好的扩展性,可以方便地添加各种拦截逻辑和路由策略。