ARouter总结
ARouter
为什么需要 ARouter?解耦
传统的 Activity 之间通信,通过 startActivity(intent),而在组件化的项目中,上层的 module 没有依赖关系 (即便两个 module 有依赖关系,也只能是单向的依赖)。
那么如何实现在没有依赖的情况下进行界面跳转呢?
使用 ARouter 的原因就是为了解耦,即没有依赖时可以彼此跳转
Arouter 提供的功能?
- 根据 URL 路由到指向的页面
- 依赖注入
- 路由失败降级服务
- 组件间通信的 Provider
- 支持获取 Fragment
- 路由过程中支持拦截,拦截是在子线程中,通过 CountDownLatch 实现,每个 Interceptor 会 countDown 一下
ARouter 的实现原理
ARouter 如何生成路由表?
- 根据
Route
注解,通过 kapt 工具,在 RouteProcessor 生成对应的页面路由信息
Activity、Fragment、Provider 等
Interceptor
注解,通过 InteceptorProcessor 生成对应的拦截器信息
ARouter 如何加载路由表的?
所谓的加载路由表,其实就是加载 RouteProcessor 生成的类文件。
在我们调用 ARouter 的 init() 方法时,ARouter 会调用 LogisticsCenter 的 init() 方法,在 LogisticsCenter 的 init() 方法中,会判断当前路由表加载方式是否为插件,不是的话则从 Dex 中加载路由表,是的话则由插件从 Jar 中加载路由表。
在 LogisticsCenter.init(),从 dex 扫描到保存到一个 set 集合,这个会存本地 sp 文件,然后还会更新到 Warehouse 里保存的路由信息和 provider 信息。
运行时扫描 dex 会增加启动耗时,用
com.alibaba.arouter
配合 asm 在编译时就把路由表装载好
ARouter navigation 跳转原理?
- LogisticsCenter.completion 填充 Postcard 信息
- Fragment 和 Provider 类型直接跳转 (IProvider 在 LogisticsCenter.completion 反射创建,Fragment 在 _navigation 时创建)
- 其他类型需要走拦截器逻辑
ARouter 路由如何查找 Activity/Fragment 的?
- 通过
@Route
注解的 Activity 或 Fragment,通过 kapt 会生成对应的路由信息 - 在 navigation 时,通过 LogisticsCenter.completion() 完善 Postcard 信息
- 如果 Activity 的话,通过 Postcard 信息填满 Intent,最后 startActivity 实现的,没有反射
- Fragment 的话就是通过 PostCard 的数据,反射创建 Fragment(在 _navigation() 反射创建)
ARouter 实现组件间通信的原理?IProvider
ARouter 中通过 IProvider
实现。
以 im_module 需要调用 room_module 的礼物功能为例:
- 在 common_module 中下沉接口,定义要提供的功能
1
2
3
interface IMGiftProvider : IProvider {
fun loadGift(message: GiftMessage)
}
- room_module 实现 IMGiftProvider
1
2
3
4
5
6
7
@Route(path = ARouterConstants.Room.ROUTER_PATH_PROVIDER_LOAD_GIFT)
class GiftProvider : IMGiftProvider {
override fun loadGift(message: GiftMessage) {
GiftWareHouse.addGift(message)
}
}
- im_module 使用该功能
1
2
3
4
5
6
7
8
9
10
11
12
13
internal class IMGlobalGiftMessageInterceptor : IMBizMessageInterceptor {
@Autowired
@JvmField
var giftProvider: IMGiftProvider? = null
init {
ARouter.getInstance().inject(this)
}
// ...
giftProvider?.loadGift(boxGiftMessage)
// ...
}
IProvider 的实现原理?
- 通过
@Route
注解的 IProvider,通过 kapt,在 RouteProcessor 处理,生成对应的 IProviderGroup
1
2
3
4
5
6
7
8
public class ARouter$$Providers$$m_room implements IProviderGroup {
@Override
public void loadInto(Map<String, RouteMeta> providers) {
providers.put("club.jinmei.mgvoice.core.arouter.provider.family.IFamilyRecallProvider", RouteMeta.build(RouteType.PROVIDER, FamilyRecallProvider.class, "/room/family_recall_provider", "room", null, -1, -2147483648));
providers.put("club.jinmei.mgvoice.core.arouter.provider.gift.IMGiftProvider", RouteMeta.build(RouteType.PROVIDER, GiftProvider.class, "/room/load_gift", "room", null, -1, -2147483648));
providers.put("club.jinmei.mgvoice.core.arouter.provider.room.IRoomProvider", RouteMeta.build(RouteType.PROVIDER, RoomProviderImpl.class, "/room/room_provider", "room", null, -1, -2147483648));
}
}
- 通过 inject,我们就可以得到该 IMGiftProvider 的实现类实例,具体是通过 navigation(Class) 实现的
1
2
3
4
5
6
7
8
9
10
public class IMGlobalGiftMessageInterceptor$$ARouter$$Autowired implements ISyringe {
private SerializationService serializationService;
@Override
public void inject(Object target) {
serializationService = ARouter.getInstance().navigation(SerializationService.class);
IMGlobalGiftMessageInterceptor substitute = (IMGlobalGiftMessageInterceptor)target;
substitute.giftProvider = ARouter.getInstance().navigation(IMGiftProvider.class);
}
}
- 在 navigation(Class) 中会调用 LogisticsCenter.completion(),在这里会将 Provider 反射创建出来,navigation() 的返回值 postcard.getProvider(),就是 Provider 反射创建出来的实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public synchronized static void completion(Postcard postcard) {
switch (routeMeta.getType()) {
case 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);
postcard.greenChannel(); // Provider should skip all of interceptors
break;
}
}
com.alibaba.arouter 插件做了什么?
- 扫描 class,找到实现了 IRouteRoot、IInterceptorGroup、IProviderGroup 的类
- 在 LogisticsCenter.loadRouterMap 中将上面扫描到的类调用 register 进行注册,这样就不需要在启动时扫描 dex 了
- 可以缩短初始化时间;同时解决应用加固导致无法直接访问 dex 文件,初始化失败的问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//源码代码,插桩前
private static void loadRouterMap() {
//registerByPlugin一直被置为false
registerByPlugin = false;
}
//插桩后反编译代码
private static void loadRouterMap() {
registerByPlugin = false;
register("com.alibaba.android.arouter.routes.ARouter$$Root$$modulejava");
register("com.alibaba.android.arouter.routes.ARouter$$Root$$modulekotlin");
register("com.alibaba.android.arouter.routes.ARouter$$Root$$arouterapi");
register("com.alibaba.android.arouter.routes.ARouter$$Interceptors$$modulejava");
register("com.alibaba.android.arouter.routes.ARouter$$Providers$$modulejava");
register("com.alibaba.android.arouter.routes.ARouter$$Providers$$modulekotlin");
register("com.alibaba.android.arouter.routes.ARouter$$Providers$$arouterapi");
}
ARouter 如何实现动态路由?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ARouter.getInstance().addRouteGroup(new IRouteGroup() {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/dynamic/activity", // path
RouteMeta.build(
RouteType.ACTIVITY, // 路由信息
TestDynamicActivity.class, // 目标的 Class
"/dynamic/activity", // Path
"dynamic", // Group, 尽量保持和 path 的第一段相同
0, // 优先级,暂未使用
0 // Extra,用于给页面打标
)
);
}
});
ARouter 的思考
ARouter 的 group 设计?
Warehouse 中有 groupsIndex 和 routes
1
2
3
4
5
class Warehouse {
// Cache route and metas
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
static Map<String, RouteMeta> routes = new HashMap<>();
}
groupsIndex 在 ARouter 时,会调用 LogisticsCenter.init 进行初始化;而 routes 是在 navigation 时,在 LogisticsCenter.completion 时进行装载的。
也就是说 groupsIndex 的设计,用到了懒加载的思想,用到了哪个 group,在首次 navigation 时,完善 Postcard 时,会将 routes 给装载,并缓存到 Warehouse.routes 中。
groupsIndex:
1
2
3
4
5
6
7
8
public class ARouter$$Root$$app implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("about", ARouter$$Group$$about.class);
routes.put("home", ARouter$$Group$$home.class);
routes.put("splash", ARouter$$Group$$splash.class);
}
}
routes(about):
1
2
3
4
5
6
7
public class ARouter$$Group$$about implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/about/about", RouteMeta.build(RouteType.ACTIVITY, AboutActivity.class, "/about/about", "about", null, -1, -2147483648));
atlas.put("/about/change_environment", RouteMeta.build(RouteType.ACTIVITY, ChangeEnvironmentActivity.class, "/about/change_environment", "about", null, -1, -2147483648));
}
}
ARouter 的不足?
1、启动时进行路由表的初始化
ARouter init 时会扫描 dex,将所有的路由分组添加到 groupIndex 中去,然后存到 sp 去。
1
2
3
4
5
6
7
8
9
10
11
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
// ...
Set<String> routerMap;
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
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);
}
}
}
缺点:
ARouter 的缺陷在于首次初始化时会通过反射扫描 dex(只有首次,第二次会从 sp 读取),同时将结果存储在 SP 中,会拖慢首次启动速度
这两次操作都是耗时的
优化:
用 tramsform+asm 在编译器进行处理,可节省 1 秒左右的启动时间
2、ARouter 接口下沉到公共 common_module 问题
问题:
以 login_module 登录模块和 room_module 房间模块为例,room_module 依赖 login_module 的登录状态。
- 所有的业务模块都依赖 common_module,可能只有部分 module 需要和 login_module 交互,但下沉到了 common_module,导致所有的业务模块都可见下沉的接口和数据类
- 因为所有的业务模块都可以和 login_module 通信,如果这个过程出现了问题,排查也造成困扰,不知道是哪个 module 和 login_module 通信
- 所有业务组件的接口下沉,一定程度上造成了底层的膨胀,这种显然也不利于通用层的维护
解决:
将组件对外开放的功能和数据对象打成 jar 包,哪个组件需要,那么通过申请的形式获取这个 jar 包,那么对于开放方是知晓那些组件使用了自己的功能的,发生问题时,只需要在这几个组件里面排查就可以了。此时由于不在经过通用层,通用层膨胀的问题也解决了。
接口和事件以及一些跨组件使用的 Model 放到哪里好呢?如果直接将这些类下沉到一个公共组件中,由于业务的频繁更新,这个公共组件可能会更新得十分频繁,开发也十分的不方便,所以使用公共组件是行不通的,于是我们采取了另一种方式——组件 API :为每个有对外暴露需求的组件添加一个 API 模块,API 模块中只包含对外暴露的 Model 和组件通信用的 Interface 与 Event。有需要引用这些类的组件只要依赖 API 即可。
- template :组件代码,它包含了这个组件所有业务代码
- template-api:组件的接口模块,专门用于与其他组件通信,只包含 Model、Interface 和 Event,不存在任何业务和逻辑代码
- app 模块:用于独立运行 app,它直接依赖组件模块,只要添加一些简单的配置,即可实现组件独立运行。
3、页面不支持正则,不支持一个页面多个路由地址
4、不支持动态路由
不支持动态路由的下发,最近的提交好像有暴露接口动态更新路由了