给 Arouter 优化的一些小建议

简介: 给 Arouter 优化的一些小建议

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 测了一下

目录
相关文章
|
移动开发 ARouter 开发工具
开源最佳实践:Android平台页面路由框架ARouter
为了更好地让开发者们更加深入了解阿里开源,阿里云云栖社区在3月1号了举办“阿里开源项目最佳实践”在线技术峰会,直播讲述了当前阿里新兴和经典开源项目实战经验以及背后的开发思路,在本次在线技术峰会上,阿里云资深开发工程师刘志龙分享了Android平台页面路由框架ARouter的技术方案、解决的问题以及在实际场景中的最佳实践。
47090 2
|
11天前
|
存储 设计模式 ARouter
组件化框架 ARouter 完全解析(一)
组件化框架 ARouter 完全解析(一)
17 2
|
11月前
|
ARouter
ARouter 源码分析1
ARouter 源码分析
|
11月前
|
ARouter
ARouter 源码分析2
ARouter 源码分析
|
ARouter 测试技术 开发工具
基于ARouter的Android组件化实现
基于ARouter的Android组件化实现
189 0
|
ARouter
关于Arouter的使用体验
关于Arouter的使用体验
139 0
|
ARouter 安全 API
XRouter 一个轻量级的Android路由框架,基于ARouter上进行改良,优化Fragment的使用,可结合XPage使用
XRouter 一个轻量级的Android路由框架,基于ARouter上进行改良,优化Fragment的使用,可结合XPage使用
774 0
XRouter 一个轻量级的Android路由框架,基于ARouter上进行改良,优化Fragment的使用,可结合XPage使用
|
Android开发
【Android 插件化】“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 )
【Android 插件化】“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 )
128 0
【Android 插件化】“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 )
|
Java Android开发
【Android 插件化】Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )(一)
Android 插件化】Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )(一)
238 0
|
Java Android开发
Android 插件化】Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )(二)
Android 插件化】Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )(二)
265 0