#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 时爆常量池炸了。
这里我们先来看下 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 产物常量池#个数 |
| :-- | :-- | :-- |
| 子模块有