AGP 升级问题续集来了,不看血亏,真是骚,高级Android程序员必会

本文深入探讨了Android Gradle Plugin(AGP)升级过程中遇到的字节码常量池问题。通过对比不同版本AGP在编译R类时的差异,揭示了ASM和javac生成的字节码在常量池占用上的区别。分析了静态代码块和常量池占用空间的关系,以及如何计算资源数量的上限。最后,提到了Android Studio新版本中对R类非final属性的处理变化,暗示未来可能的趋势。
摘要由CSDN通过智能技术生成

#15 = Utf8 InnerClasses

#16 = Utf8 Lcn/yan/libr/R$id;

#17 = Utf8

#18 = Utf8 SourceFile

#19 = Utf8 R.java

#20 = NameAndType #8:#9 // “”😦)V

#21 = NameAndType #6:#7 // test_layout:I

#22 = Class #25 // cn/yan/libr/R

#23 = Utf8 cn/yan/libr/R$id

#24 = Utf8 java/lang/Object

#25 = Utf8 cn/yan/libr/R

{

public static int test_layout;

descriptor: I

flags: ACC_PUBLIC, ACC_STATIC

public cn.yan.libr.R$id();

descriptor: ()V

flags: ACC_PUBLIC

Code:

stack=1, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method java/lang/Object.“”😦)V

4: return

LineNumberTable:

line 10: 0

LocalVariableTable:

Start Length Slot Name Signature

0 5 0 this Lcn/yan/libr/R$id;

static {};

descriptor: ()V

flags: ACC_STATIC

Code:

stack=1, locals=0, args_size=0

0: ldc #2 // int 2131492865

2: putstatic #3 // Field test_layout:I

5: return

LineNumberTable:

line 11: 0

}

SourceFile: “R.java”

InnerClasses:

public static final #14= #4 of #22; //id=class cn/yan/libr/R$id of class cn/yan/libr/R

反编译主模块产物中 javac 生成的依赖子模块自己的属性成员 final 且有明确初值的R$id.class为例(其他 R class 雷同):

yandeMacBook-Pro:libr yan$ javap -v R$id.class

Classfile /Users/yan/work/tmp/TestR/app/build/intermediates/classes/debug/cn/yan/libr/R$id.class

Last modified 2020-11-8; size 368 bytes

MD5 checksum c87e7b3284a1f78f83a2d0a8d3ee5ac8

Compiled from “R.java”

public final class cn.yan.libr.R$id

minor version: 0

major version: 52

flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER

//重点!!!class的常量池,这里总共占用#23个

Constant pool:

#1 = Methodref #3.#19 // java/lang/Object.“”😦)V

#2 = Class #21 // cn/yan/libr/R$id

#3 = Class #22 // java/lang/Object

#4 = Utf8 test_layout

#5 = Utf8 I

#6 = Utf8 ConstantValue

#7 = Integer 2130837504

#8 = Utf8

#9 = Utf8 ()V

#10 = Utf8 Code

#11 = Utf8 LineNumberTable

#12 = Utf8 LocalVariableTable

#13 = Utf8 this

#14 = Utf8 id

#15 = Utf8 InnerClasses

#16 = Utf8 Lcn/yan/libr/R$id;

#17 = Utf8 SourceFile

#18 = Utf8 R.java

#19 = NameAndType #8:#9 // “”😦)V

#20 = Class #23 // cn/yan/libr/R

#21 = Utf8 cn/yan/libr/R$id

#22 = Utf8 java/lang/Object

#23 = Utf8 cn/yan/libr/R

{

public static final int test_layout;

descriptor: I

flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

ConstantValue: int 2130837504

public cn.yan.libr.R$id();

descriptor: ()V

flags: ACC_PUBLIC

Code:

stack=1, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method java/lang/Object.“”😦)V

4: return

LineNumberTable:

line 10: 0

LocalVariableTable:

Start Length Slot Name Signature

0 5 0 this Lcn/yan/libr/R$id;

}

SourceFile: “R.java”

InnerClasses:

public static final #14= #2 of #20; //id=class cn/yan/libr/R$id of class cn/yan/libr/R

反编译主模块产物中 javac 生成的主模块自己合并的属性成员 final 且有明确初值的R$id.class也是 23,与上面一样,所以不再给出。

可以看到,由于我们 demo 中主 module 无额外的 id 资源,所以子 module 里通过 javac 生成的 R$id.class 的 class 常量池占用个数为 25,而主 module 里是先生成R.java然后通过 javac 编译生成的R.class,所以常量池占用个数变为了 23。我们类成员属性只有一个,子模块 javac 和主模块 javac 的 class 就差了 2 个,至于为什么后面分析,先继续。

到此我们先简单给下对比数据(以R$id.class为例,该 class 仅有明确定义的一个成员属性):

| AGP 版本 | 子moduleR$id.class常量池#个数 | 主moduleR$id.class常量池#个数 |

| :-: | :-- | :-- |

| 4.1.0 | ASM 生成 17 个 | ASM 生成 17 个 |

| 3.5.0 | ASM 生成 17 个 | javac 生成 23 个 |

| 3.1.2 | javac 生成 25 个 | javac 生成 23 个 |

到此其实就已经真相了,同事低版本 AGP 在 javac task 报错R$id.java类太大,我高版本 AGP 在 ASM 合并 task 炸了的本质就是低版本 AGP 生成R.java时就已经越界了,所以这个R.java在参与 javac 时自然就被爆常量池炸了,也就是提醒类文件太大错误;高版本 AGP 没有R.java经过 javac 到R.class,而是直接 ASM 一步到位 class,所以自然在 ASM 生成 class 时爆常量池炸了。

为什么看起来一样的 R 字节码常量池大小不一样?


这里我们先来看下 AGP3.1.5 版本产物为啥差 2 个的问题,子 module 比主 module 常量池多两个是因为子 module 的 R 成员属性是非 final 的,所以其值在字节码会被转换为 static 块初始化,进而常量池多了一个 ref 常量索引,刚好 2 个。如下图对比差异分析结论:

在这里插入图片描述

对于 AGP3.5.0 版本 lib module ASM 生成的 R class 比 app module 生成的 R class 小 5 个其实大家应该都知道为啥吧?因为 ASM 写的好情况下生成的是“紧凑型”的 class,咱们知道 JVM 对于 class 字节码是有严格规范和部分松散规范的,javac 是完全标准化的 class 产物,相对于 ASM 的 class 来说很多时候都是比较冗余的(包括 class 大小),否则由于子 module R 为非 final,这个场景下 javac 的 class 字节码会比 final 的多,如下:

在这里插入图片描述

到此其实一切都真相大白了。到此为了继续说明 AGP4.1.0 的优势,我们再看一组数据结论(其实就是常量池的理解,理解到位的情况下不用看这组都行),这里对 lib module 的非 final R 产物进行对比:

| 资源情况 | ASM 产物常量池#个数 | javac 产物常量池#个数 |

| :-- | :-- | :-- |

| 子模块有

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值