文章

ARouter总结

ARouter总结

ARouter

为什么需要 ARouter?解耦

传统的 Activity 之间通信,通过 startActivity(intent),而在组件化的项目中,上层的 module 没有依赖关系 (即便两个 module 有依赖关系,也只能是单向的依赖)。
那么如何实现在没有依赖的情况下进行界面跳转呢?
使用 ARouter 的原因就是为了解耦,即没有依赖时可以彼此跳转

Arouter 提供的功能?

  1. 根据 URL 路由到指向的页面
  2. 依赖注入
  3. 路由失败降级服务
  4. 组件间通信的 Provider
  5. 支持获取 Fragment
  6. 路由过程中支持拦截,拦截是在子线程中,通过 CountDownLatch 实现,每个 Interceptor 会 countDown 一下

ARouter 的实现原理

90a8w

ARouter 如何生成路由表?

  1. 根据 Route 注解,通过 kapt 工具,在 RouteProcessor 生成对应的页面路由信息

Activity、Fragment、Provider 等

  1. 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 跳转原理?

  1. LogisticsCenter.completion 填充 Postcard 信息
  2. Fragment 和 Provider 类型直接跳转 (IProvider 在 LogisticsCenter.completion 反射创建,Fragment 在 _navigation 时创建)
  3. 其他类型需要走拦截器逻辑

ARouter 路由如何查找 Activity/Fragment 的?

  1. 通过 @Route 注解的 Activity 或 Fragment,通过 kapt 会生成对应的路由信息
  2. 在 navigation 时,通过 LogisticsCenter.completion() 完善 Postcard 信息
  3. 如果 Activity 的话,通过 Postcard 信息填满 Intent,最后 startActivity 实现的,没有反射
  4. Fragment 的话就是通过 PostCard 的数据,反射创建 Fragment(在 _navigation() 反射创建)

ARouter 实现组件间通信的原理?IProvider

ARouter 中通过 IProvider 实现。
以 im_module 需要调用 room_module 的礼物功能为例:

  1. 在 common_module 中下沉接口,定义要提供的功能
1
2
3
interface IMGiftProvider : IProvider {
    fun loadGift(message: GiftMessage)
}
  1. 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)
    }
}
  1. 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 的实现原理?

  1. 通过 @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));
  }
}
  1. 通过 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);
  }
}
  1. 在 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 插件做了什么?

  1. 扫描 class,找到实现了 IRouteRoot、IInterceptorGroup、IProviderGroup 的类
  2. 在 LogisticsCenter.loadRouterMap 中将上面扫描到的类调用 register 进行注册,这样就不需要在启动时扫描 dex 了
  3. 可以缩短初始化时间;同时解决应用加固导致无法直接访问 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 的登录状态。

  1. 所有的业务模块都依赖 common_module,可能只有部分 module 需要和 login_module 交互,但下沉到了 common_module,导致所有的业务模块都可见下沉的接口和数据类
  2. 因为所有的业务模块都可以和 login_module 通信,如果这个过程出现了问题,排查也造成困扰,不知道是哪个 module 和 login_module 通信
  3. 所有业务组件的接口下沉,一定程度上造成了底层的膨胀,这种显然也不利于通用层的维护

解决:
将组件对外开放的功能和数据对象打成 jar 包,哪个组件需要,那么通过申请的形式获取这个 jar 包,那么对于开放方是知晓那些组件使用了自己的功能的,发生问题时,只需要在这几个组件里面排查就可以了。此时由于不在经过通用层,通用层膨胀的问题也解决了。

接口和事件以及一些跨组件使用的 Model 放到哪里好呢?如果直接将这些类下沉到一个公共组件中,由于业务的频繁更新,这个公共组件可能会更新得十分频繁,开发也十分的不方便,所以使用公共组件是行不通的,于是我们采取了另一种方式——组件 API :为每个有对外暴露需求的组件添加一个 API 模块,API 模块中只包含对外暴露的 Model 和组件通信用的 Interface 与 Event。有需要引用这些类的组件只要依赖 API 即可。

组件 API 模块实现
yhti3
一个典型的组件工程结构是这个样子:

  • template :组件代码,它包含了这个组件所有业务代码
  • template-api:组件的接口模块,专门用于与其他组件通信,只包含 Model、Interface 和 Event,不存在任何业务和逻辑代码
  • app 模块:用于独立运行 app,它直接依赖组件模块,只要添加一些简单的配置,即可实现组件独立运行。

3、页面不支持正则,不支持一个页面多个路由地址

4、不支持动态路由

不支持动态路由的下发,最近的提交好像有暴露接口动态更新路由了

5、transform 未支持增量更新;不支持 KSP

本文由作者按照 CC BY 4.0 进行授权