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

目录
相关文章
|
Cloud Native Java Docker
云原生之使用docker部署Subsonic音乐流媒体服务
云原生之使用docker部署Subsonic音乐流媒体服务
1381 0
云原生之使用docker部署Subsonic音乐流媒体服务
|
Android开发
【Android 安装包优化】动态库打包配置 ( “armeabi-v7a“, “arm64-v8a“, “x86“, “x86_64“ APK 打包 CPU 指令集配置 | NDK 完整配置参考 )
【Android 安装包优化】动态库打包配置 ( “armeabi-v7a“, “arm64-v8a“, “x86“, “x86_64“ APK 打包 CPU 指令集配置 | NDK 完整配置参考 )
1794 0
【Android 安装包优化】动态库打包配置 ( “armeabi-v7a“, “arm64-v8a“, “x86“, “x86_64“ APK 打包 CPU 指令集配置 | NDK 完整配置参考 )
|
开发框架 JavaScript 前端开发
Electron 重大更新,33.0.0版本发布,带来多项新特性与改进!
本文介绍了 Electron 33.0.0 版本的重要更新,包括核心组件的升级(Chromium、Node.js 和 V8),新增功能(如 app.setClientCertRequestPasswordHandler 和 View.setBorderRadius),重要改进和主要问题修复。建议开发者尽快升级,以享受更强大的性能和功能。
651 0
Electron 重大更新,33.0.0版本发布,带来多项新特性与改进!
|
SQL 开发框架 .NET
深入解析Entity Framework Core中的自定义SQL查询与Raw SQL技巧:从基础到高级应用的全面指南,附带示例代码与最佳实践建议
【8月更文挑战第31天】本文详细介绍了如何在 Entity Framework Core (EF Core) 中使用自定义 SQL 查询与 Raw SQL。首先,通过创建基于 EF Core 的项目并配置数据库上下文,定义领域模型。然后,使用 `FromSqlRaw` 和 `FromSqlInterpolated` 方法执行自定义 SQL 查询。此外,还展示了如何使用 Raw SQL 进行数据更新和删除操作。最后,通过结合 LINQ 和 Raw SQL 构建动态 SQL 语句,处理复杂查询场景。本文提供了具体代码示例,帮助读者理解和应用这些技术,提升数据访问层的效率和灵活性。
778 0
|
Java Maven 数据库
Annotation Processing Tool自动生成代码
本文介绍了一种利用Java注解处理器(Annotation Processor)自动生成协议接收与发送类接口的方法,显著提升开发效率。注解处理器能在编译阶段扫描并处理特定注解,生成所需Java代码。文中详细展示了如何通过自定义`HttpProto`注解及对应的处理器`ProtoServiceProcessor`,实现在保存协议类后自动生成客户端请求工具和服务端控制器代码。此外,还提供了具体实现步骤、依赖配置及常见问题解决方案,如处理“服务配置文件不正确”错误和Gradle项目的配置方法。此技术特别适用于需要频繁处理协议或数据交互的应用场景。
154 1
|
存储 设计模式 ARouter
组件化框架 ARouter 完全解析(一)
组件化框架 ARouter 完全解析(一)
625 2
SPSS导入数据 错误号 105
SPSS导入数据 错误号 105
455 0
|
数据采集 消息中间件 Oracle
通过流计算与消息中间件实现Oracle到ADB的实时数据同步链路优化实践
介绍基于流计算与消息中间件链路(Oracle->OGG->DataHub->Flink/Blink->ADB)实现的Oracle到ADB的数据实时同步架构潜在问题及处理方案。
481 0
通过流计算与消息中间件实现Oracle到ADB的实时数据同步链路优化实践
new Set与...new Set()的区别
new Set与...new Set()的区别,妙用多多
393 0