给 Arouter 优化的一些小建议

4,768 阅读3分钟

Arouter 应该算是 Android 国民级框架了,在自己做组件化框架的时候,也是参考了不少 Arouter 的设计,在阅读源码中,觉得有的点是可以优化的,所以就有了今天的文章。

1、混淆优化

image.png

在 README 中可以看到,如果是开启混淆的话,需要添加如上的规则。主要原因是 gradle-plugin 在收集 apt 生成类的时候,注入到 LogisticsCenter 的是 className,然后运行时通过反射的方式来初始化类。

Arouter 代码

在 Arouter-gradle-plugin 模块的 RouteMethodVisitor 类中会对所有收集到的类,注入到 LogisticsCenter 的 loadRouterMap 方法中:

 @Override
 void visitInsn(int opcode) {
     //generate code before return
     if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
         extension.classList.each { name ->
                 name = name.replaceAll("/", ".")
             mv.visitLdcInsn(name)//类名
             // generate invoke register method into LogisticsCenter.loadRouterMap()
             mv.visitMethodInsn(Opcodes.INVOKESTATIC
                                , ScanSetting.GENERATE_TO_CLASS_NAME
                                , ScanSetting.REGISTER_METHOD_NAME
                                , "(Ljava/lang/String;)V"
                                , false)
         }
     }
     super.visitInsn(opcode)
  }

注入结果如下:

private static void loadRouterMap() {
    registerByPlugin = false;
    // 就大致举一个例子
    register("com.alibaba.android.arouter.routes.ARouter?Root?modulejava");
    ... 
}

最后会通过 register 方法来完成反射初始化:

 private static void register(String className) {
        if (!TextUtils.isEmpty(className)) {
            try {
                Class<?> clazz = Class.forName(className);
                Object obj = clazz.getConstructor().newInstance();
                if (obj instanceof IRouteRoot) {
                    registerRouteRoot((IRouteRoot) obj);
                } 
                ...
        }
    }

优化建议

  • 将 String 类型的 className 替换成 Class

我们可以从 gradle-plugin 的 RouteMethodVisitor 入手,来更改一下 RouteMethodVisitor:

@Override
void visitInsn(int opcode) {
    //generate code before return
    if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
        extension.classList.each { name ->
            // 获取 Class
            mv.visitLdcInsn(Type.getType("L" + name + ";"))
            // generate invoke register method into LogisticsCenter.loadRouterMap()
            mv.visitMethodInsn(Opcodes.INVOKESTATIC
                               , ScanSetting.GENERATE_TO_CLASS_NAME
                               , ScanSetting.REGISTER_METHOD_NAME
                               , "(Ljava/lang/Class;)V"
                               , false)
                                  }
    }
    super.visitInsn(opcode)
}

注入的结果如下:

private static void loadRouterMap() {
    registerByPlugin = false;
    // 就大致举一个例子
    register(com.alibaba.android.arouter.routes.ARouter?Root?modulejava.class);
    ... 
}

还要再更改一下 register 方法,更改如下:

private static void register(Class clazz){
   if (clazz!=null) {
        try {
           Object obj = clazz.getConstructor().newInstance();
            if (obj instanceof IRouteRoot) {
               registerRouteRoot((IRouteRoot) obj);
        ...

2、反射优化

虽然混淆的问题可以通过 Class 来解决,但仍然无法解决反射带来的性能问题。我们在 ASM 插桩的时候是可以拿到类名的,那我们能不能通过 new 类()  的方式来初始化类呢?我们可以继续按照上面的思路来解决掉反射的问题。

继续来看 RouteMethodVisitor ,来更改一下 RouteMethodVisitor:

@Override
void visitInsn(int opcode) {
    //generate code before return
    if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
        extension.classList.each { name ->
            mv.visitVarInsn(Opcodes.ALOAD, 0)
            //用无参构造方法创建实例
            mv.visitTypeInsn(Opcodes.NEW, name)
            mv.visitInsn(Opcodes.DUP)
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, name, "<init>", "()V", false)

            String interfaceName = ""
            String insertMethod = ""
            // ①、
            if (name.contains("ARouter\$\$Root\$\$")) {
                    interfaceName = "com/alibaba/android/arouter/facade/template/IRouteRoot"
                    insertMethod = "registerRouteRoot"
             } else if (name.contains("ARouter\$\$Interceptors\$\$")) {
                    interfaceName = "com/alibaba/android/arouter/facade/template/IInterceptorGroup"
                    insertMethod = "registerInterceptor"
             } else if (name.contains("ARouter\$\$Providers\$\$")) {
                    interfaceName = "com/alibaba/android/arouter/facade/template/IProviderGroup"
                    insertMethod = "registerProvider"
             }
			
            if (!TextUtils.isEmpty(interfaceName) && !TextUtils.isEmpty(insertMethod)) {
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC
                                       , ScanSetting.GENERATE_TO_CLASS_NAME
                                       , insertMethod
                                       , "(L${interfaceName};)V"
                                       , false)
            }
         }
     }
    super.visitInsn(opcode)
 }

①:根据类名来判断当前是 Root、Interceptors 还是 Providers,因为 Arouter 在 apt 生成类的时候会对有一个类命名规则,我们只要根据这个规则,即可找到该类实现的是哪个接口。这个地方还需要有一个 insertMethod,因为我们是 new 类() 的方式,不能像 register 方法那样通过 Class 来做统一处理,我们需要明确的类型来注入,所以,这个地方用的是接口。

注入效果如下:

private static void loadRouterMap() {
    registerByPlugin = false;
    registerRouteRoot(new com.alibaba.android.arouter.routes.ARouter?Root?modulejava());
    registerInterceptor(new com.alibaba.android.arouter.routes.ARouter?Interceptors?app());
    registerProvider(new com.alibaba.android.arouter.routes.ARouter?Providers?app());
    ... 
}

3、一些有趣的 issue

1、issue 776 : kt 中注入 Autowired 无效

Arouter Autowired注入的时候在存在 kotlin-java 兼容性问题

针对基本数据类型的传递 var showBadge: Boolean? = null 注入失败 var showBadge: Boolean? = false 注入成功

原因:
在 arouter 生成代码中,获取注入变量通过 substitute.showBadge = substitute.getIntent().getBooleanExtra("showBadge", substitute.showBadge) ,对于基本数据类型的获取接口都带有默认参数,如 getBooleanExtra(String name, boolean defaultValue), 问题出在默认参数 对应 java boolean 类型没有 null 的概念 ,报错 can't unbox a null value

2、issue 818 : androidx 项目中使用 Arouter 的困惑

这个是我自己提的 🤣 ,主要是在看 arouter-compiler 源码的时候,Arouter 写死的是 support.fragment 的路径,为什么 types.isSubtype 的判断对于 androidx.fragment 却可以通过的问题,Jetifier 其实是不会将常量的 support 替换成 androidx 的,因为我写了一个 support 转 androidx 的 demo 测了一下

... 待补充吧