Arouter 应该算是 Android 国民级框架了,在自己做组件化框架的时候,也是参考了不少 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); } ... } } 复制代码
我们可以从 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); ... 复制代码
虽然混淆的问题可以通过 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()); ... } 复制代码
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
这个是我自己提的 🤣 ,主要是在看 arouter-compiler 源码的时候,Arouter 写死的是 support.fragment 的路径,为什么 types.isSubtype 的判断对于 androidx.fragment 却可以通过的问题,Jetifier 其实是不会将常量的 support 替换成 androidx 的,因为我写了一个 support 转 androidx 的 demo 测了一下
... 待补充吧