background

The last article, “Recording the thinking during an AGP investigation, I made a story out of an Accident!” After the release, many friends said they couldn’t understand it and didn’t want to write this article at first. But recently, a colleague in a large group was doing another technical requirement, and his R also exploded, but he exploded in the JavAC phase. The scene I analyzed in the last article was the ASM generation stage of bytecode, so I asked him to send me his R. Java file. I tried to compile and compare it to my bytecode and found something I hadn’t realized before, so I felt like my last post didn’t go far enough. The AGP upgrade R issue can still be dug up, so I wanted a sequel. In view of some people did not understand the last article, this article on the step by step with pictures. So what you’re going to see first is a case study, and the real problem and analysis will unfold later; It’s long but comprehensive, and if you’re not interested in the case study, you can go straight to the analysis.

Just to be clear, it doesn’t really matter which version of AGP the changes discussed below will start with, so we’ll be looking at three specific versions (why three? Because my colleague is minimum version problem here, I am at the highest version of the crack, we have a middle version, so the three), unless you’re compatible with hook AGP, otherwise only with the latest version of the current and previous version to do gap analysis just understand the principle of nature, don’t entangled with different versions of the AGP task difference, Don’t obsess over points that aren’t relevant to your need to dig deeper. The general ran, not after the rabbit.

We created a new project called TestR with gradle version 6.5 and buildToolsVersion 30.0.2, where the main module is app. The app relies on an Android sub-module named libR. The gradle dependencies on both modules are free of any dependencies (including AndroidX, etc.). Only in this way can the generated R be the most original and clean (otherwise, there will be many more R dependencies). Affect our main line analysis). Both the main module and submodule are compiled in Java8:

/ / 【 craftsman if water to add WeChat yanbo373131686 contact me, concern WeChat public number: code farmers a daily topic Without permission is strictly prohibited reproduced, https://blog.csdn.net/yanbober]

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}
Copy the code

The javAP and JDK versions of classes are decompiled on the command line as follows:

YandeMacBook -Pro: Libr Yan $Java -version Java version "1.8.0_191" Java(TM) SE Runtime Environment (build 1.8.0_191-B12) Java HotSpot(TM) 64-Bit Server VM (build 25.191-B12, Mixed Mode) yandeMacBook-Pro: Libr Yan $JavAP -version 1.8.0_191Copy the code

The following analysis adopts the control variable method, all environments are consistent, and only the AGP version number is changed.

AGP4.1.0 version of R (current)

Gradle build script version is as follows:

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript{...dependencies {
        classpath "Com. Android. Tools. Build: gradle: 4.1.0." "}}Copy the code

Product analysis of the libR submodule

Here is a schematic of the resulting submodule structure after compilation:

As you can see from the libR module aboveR.jarThe inside of theR.classThe contents are as follows:

// The difference between the former R.Java
// We can see that the class attributes in the submodule intermediate are non-final and have no initial value
package cn.yan.libr;

public final class R {
    private R(a) {}public static final class id {
        public static int test_layout; // Only define, no more random assignment, still remain non-final

        private id(a) {}}...public static final class string {
        public static int lib_test_string;

        private string(a) {}}}Copy the code

Product analysis of app main Module

Next, let’s take a look at the situation under the main module of app, and the product structure diagram of the main module after compilation:

Above first take a look at the build/intermediates/compile_and_runtime_not_namespaced_r_class_jar/debug/cn/yan/libr/R.c lass of libr final R Class, as follows:

// Known conclusion
// In contrast to intermediate products in the Lib Module, where the lib R is final and given an explicit assignment
package cn.yan.libr;

public final class R {
    private R(a) {}public static final class id {
        public static final int test_layout = 2130837504; // A specific value has been assigned

        private id(a) {}}...public static final class string {
        public static final int lib_test_string = 2131099649;

        private string(a) {}}}Copy the code

Then take a look at the build/intermediates/compile_and_runtime_not_namespaced_r_class_jar/debug/cn/yan/testr/R.c lass, final R of the app Class, as follows:

// Known conclusion
// In this case, the R of the main module is final and has an explicit value. In addition, the R properties of sub-modules have been merged into this class
package cn.yan.testr;

public final class R {
    private R(a) {}// From the attributes under the submodule cn.yan.libr.R$id.class
    public static final class id {
        public static final int test_layout = 2130837504;

        private id(a) {}}...// Merge the properties of the main module and submodule
    public static final class string {
        public static final int app_name = 2131099648;
        public static final int lib_test_string = 2131099649;

        private string(a) {}}}Copy the code

AGP3.5.0 version of R

Gradle build script version is as follows:

/ / 【 craftsman if water to add WeChat yanbo373131686 contact me, concern WeChat public number: code farmers a daily topic Without permission is strictly prohibited reproduced, https://blog.csdn.net/yanbober]

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript{...dependencies {
        classpath "Com. Android. Tools. Build: gradle: 3.5.0." "}}Copy the code

Product analysis of the libR submodule

Here is a schematic of the resulting submodule structure after compilation:

As you can see from the libR module aboveR.jarThe inside of theR.classThe contents are as follows:

package cn.yan.libr;

public final class R {
    private R(a) {}public static final class id {
        public static int test_layout;

        private id(a) {}}...public static final class string {
        public static int lib_test_string;

        private string(a) {}}}Copy the code

Product analysis of app main Module

Next, let’s take a look at the situation under the main module of app, and the product structure diagram of the main module after compilation:

As we can see, the biggest difference between AGP4.1.0 and AGP4.1.0 is that the build product of the App Module is not the same as AGP3.5.0build/generated/not_namespaced_r_class_sources/debug/r/cn/yan/libr/R.javaandbuild/generated/not_namespaced_r_class_sources/debug/r/cn/yan/testr/R.javaTwo source Java files, which are final static and have explicit values; They were then compiled through JavACbuild/intermediates/javac/debug/classes/cn/yan/libr/R.classandbuild/intermediates/javac/debug/classes/cn/yan/testr/R.classTwo class files, remember, the class is generated by Javac.

Above the first take a look at the build/intermediates/javac/debug/classes/cn/yan/libr/R.c lass of libr final R class, as follows:

package cn.yan.libr;

public final class R {
    private R(a) {}public static final class string {
        public static final int lib_test_string = 2131099649;

        private string(a) {}}...public static final class id {
        public static final int test_layout = 2130837504;

        private id(a) {}}}Copy the code

Then take a look at the build/intermediates/javac/debug/classes/cn/yan/testr/R.c lass app under final R class, as follows:

package cn.yan.testr;

public final class R {
    public R(a) {}// Merge attributes of the main module and submodule
    public static final class string {
        public static final int app_name = 2131099648;
        public static final int lib_test_string = 2131099649;

        public string(a) {}}...public static final class id {
        public static final int test_layout = 2130837504;

        public id(a) {}}}Copy the code

AGP3.1.2 version R

Gradle build script version is as follows:

/ / 【 craftsman if water to add WeChat yanbo373131686 contact me, concern WeChat public number: code farmers a daily topic Without permission is strictly prohibited reproduced, https://blog.csdn.net/yanbober]

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript{...dependencies {
        classpath "Com. Android. Tools. Build: gradle: 3.1.2." "}}Copy the code

Product analysis of the libR submodule

Here is a schematic of the resulting submodule structure after compilation:

As you can see from the libR module abovebuild/intermediates/classes/debug/cn/yan/libr/R.classThe contents are as follows:

package cn.yan.libr;

public final class R {
    public R(a) {}public static final class string {
        public static int lib_test_string = 2132082689;

        public string(a) {}}...public static final class id {
        public static int test_layout = 2131492865;

        public id(a) {}}}Copy the code

Product analysis of app main Module

Next, let’s take a look at the situation under the main module of app, and the product structure diagram of the main module after compilation:As we can see, the biggest difference between this version and AGP3.5.0 above is that the libR Module build product is not the same as AGP3.1.2build/generated/source/r/debug/cn/yan/libr/R.javaSource Java files, which are non-final and have explicit values; They were then compiled through JavACbuild/intermediates/classes/debug/cn/yan/libr/R.classClass file, remember, the submodule class here is also generated by Javac. Everything else is the same as AGP 3.5.0, so the class file for the main module is no longer given.

Got it? In fact, everything here is not beyond our understanding since we do Android, posted here just for someone to say that they can not understand the previous article “Record an AGP research process of thinking, I made a story from an accident!” Article just, at the same time to here also illustrated the explanation of my github.com/yanbober/ap… The principle of.

What are the R features of different versions of AGP?

As you can see from the comparison of the three AGP releases above, Google has been improving on the R build issue and has indeed improved somewhat with the latest release. In addition, it should also prove a question you usually write code, that is, when we use R to access the resource test_layout in the libR module, the package imported is CN.yan.libr. R, In the main module app, when accessing the resource test_layout of the sub-module through R, the package imported is cn.yan.testr.R. Both methods access the resource of test_layout of the sub-module. This is because the main Module will R merge and append to the submodule merge mechanism. As I mentioned at the end of my last post, Google’s R is static final for submodules a long time ago, which shows that Google has been improving, but R is still tricky.

Based on the above three versions of the analysis, we can draw the following table conclusion, the details of their own can actually go to see the AGP source code, here is not expanded, I in order to lazy directly through the call stack to see, the essence of the same. Google’s actions can be seen in the following table (since the main line of this article is the difference analysis, so there is no description of the main module overwriting the submodule R feature, nor the R.txt list) :

AGP version Submodule R features Main Module R features
4.1.0 Directly under their module through the ASM generated attributes are final and no specific initial build/intermediates/compile_r_class_jar/debug/R.j ar package, no source and javac process. Submodule properties generated directly under the main module through ASM are final and properties with specific initial values and merges are final And the merge of the initial value of the specific product build/intermediates/compile_and_runtime_not_namespaced_r_class_jar/debug/R.j ar, no source and javac process.
3.5.0 Directly under their module through the ASM generated attributes are final and no specific initial build/intermediates/compile_only_not_namespaced_r_class_jar/debug/R.j ar package, No source code and Javac procedures. First in the main module generate the module properties is final and there is a specific initial build/generated/not_namespaced_r_class_sources/debug/r/cn/yan/libr/R.j ava, Regeneration into consolidation properties are final and concrete initial build/generated/not_namespaced_r_class_sources/debug/r/cn/yan/testr/R.j ava, Then javAC is compiled and generated Build/intermediates/javac/debug/classes/cn/yan/libr/R.c lass and build/intermediates/javac/debug/classes/cn/yan/testr/R.c lass And participate in the packaging.
3.1.2 In his first module generated attributes are final and concrete initial build/generated/source/r/debug/cn/yan/libr/R.j ava, Then the javac compiler to generate the build/intermediates/classes/debug/cn/yan/libr/R.c lass. First, in the main module generate the module properties is final and there is a specific initial build/generated/source/r/debug/cn/yan/libr/R.j ava, Regeneration into consolidation properties are final and concrete initial build/generated/source/r/debug/cn/yan/testr/R.j ava, Then javac Compiler to generate the build/intermediates/classes/debug/cn/yan/libr/R.c lass and build/intermediates/classes/debug/cn/yan/testr/R.c lass, and participate in the packaging.

Using the table above and the screenshot from the previous case, you can basically get a sense of the minor actions AGP has taken with R in several major versions. As you can see, with the upgrade of AGP, R becomes more and more efficient. Here is how I view it:

  • The main and submodules of version 3.1.2 should be completedR.java, and the main and child R class member attributes, whether final or non-final, have explicit initial values, and then the corresponding class is generated through javAC compilation.
  • Evolving to version 3.5.0, submodules are no longer neededR.javaIn one stepR.jar, and the R member attributes of the submodule are non-final and have no definite initial value. This may seem like a small step, but it actually improves compilation speed a little bit and optimizes some issues. Anyway, it’s all generated files.
  • By evolving to version 4.1.0, master and submodules are no longer generatedR.javaIn addition to the advantages of 3.5.0, the main Module is also improved.

As you can see, the above improvement is worth it, because we all know that r.java or R.lass of a submodule is useless in the end, and can be interpreted to some extent as simply compiling to pass (whether directly compiling to aar or Module dependencies), and not packaging the final package. Only when the apK is finally generated with the main module, will the real static final and the submodule R class that participate in the packaging be regenerated with the initial value and the merged main Module R class, so the official optimization is actually beneficial for large projects.

R Class bytecode features of different AGP versions?

Ok, from here we start the sequel to the last article, that is, the background information of this article, colleagues with a lower version of AGP in Javac prompt R burst, I use a higher version of AGP in ASM prompt R burst.

The size of the Constant pool is not the actual size of the pool, but the number of numbers that are decompiled by javAP.

AGP4.1.0 R bytecode

R$id.class (R$id.class); R$id.class (R$id.class);

// [Craftsman Ruoshui plus wechat Yanbo373131686] Contact me, follow wechat public number: Code farmers a daily topic Without permission is strictly prohibited reproduced https://blog.csdn.net/yanbober 】 yandeMacBook - Pro: libr yan $javap - v R \ $id. Class Classfile /Users/yan/work/tmp/TestR/libR/build/intermediates/compile_r_class_jar/debug/cn/yan/libr/R$id.class Last modified The 1981-1-1; size 241 bytes MD5 checksum 2f897e4e6d3128fb19074d53825f5711 public final class cn.yan.libr.R$id minor version: 0 Major version: 52 flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER #17 Constant pool for class: #1 = Utf8 cn/yan/libr/R$id #2 = Class #1 // cn/yan/libr/R$id #3 = Utf8 java/lang/Object #4 = Class #3 // java/lang/Object #5 = Utf8 cn/yan/libr/R #6 = Class #5 // cn/yan/libr/R #7 = Utf8 id #8 = Utf8 test_layout #9 = Utf8 I #10 = Integer 0 #11 = Utf8 <init> #12 = Utf8 ()V #13 = NameAndType #11:#12 // "<init>":()V #14 = Methodref #4.#13 // java/lang/Object."<init>":()V #15 = Utf8 ConstantValue #16 = Utf8 Code #17 = Utf8 InnerClasses { public static int test_layout; descriptor: I flags: ACC_PUBLIC, ACC_STATIC ConstantValue: int 0 } InnerClasses: public static final #7= #2 of #6; //id=class cn/yan/libr/R$id of class cn/yan/libr/RCopy the code

For example, decompile R$id.class whose property members are final and have explicit initial values in r.jar generated by ASM in the main module product (other R classes are the same) :

yandeMacBook-Pro:libr yan$ javap -v R\$id.class Classfile /Users/yan/work/tmp/TestR/app/build/intermediates/compile_and_runtime_not_namespaced_r_class_jar/debug/cn/yan/libr/R$id. class Last modified 1981-1-1; size 241 bytes MD5 checksum 42e85bbb5479d39b2b093131204f367b public final class cn.yan.libr.R$id minor version: 0 Major version: 52 flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER #17 Constant pool for class: #1 = Utf8 cn/yan/libr/R$id #2 = Class #1 // cn/yan/libr/R$id #3 = Utf8 java/lang/Object #4 = Class #3 // java/lang/Object #5 = Utf8 cn/yan/libr/R #6 = Class #5 // cn/yan/libr/R #7 = Utf8 id #8 = Utf8 test_layout #9 = Utf8 I #10 = Integer 2130837504 #11 = Utf8 <init> #12 = Utf8 ()V #13 = NameAndType #11:#12 // "<init>":()V #14 = Methodref #4.#13 // java/lang/Object."<init>":()V #15 = Utf8 ConstantValue #16 = Utf8 Code #17 = Utf8 InnerClasses { public static  final int test_layout; descriptor: I flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: int 2130837504 } InnerClasses: public static final #7= #2 of #6; //id=class cn/yan/libr/R$id of class cn/yan/libr/RCopy the code

The R$id.class in r.jar, which is final and has a definite initial value, is also 17, as above, so it is not given.

As you can see, since the main module of our Demo has no additional ID resources, the class constant pool occupied by R$ID. class generated by ASM in the sub-module is 17, and the main module is also 17.

AGP3.5.0 R bytecode

R$id.class (R$id.class); R$id.class (R$id.class);

yandeMacBook-Pro:libr yan$ javap -v R\$id.class Classfile /Users/yan/work/tmp/TestR/libR/build/intermediates/compile_only_not_namespaced_r_class_jar/debug/cn/yan/libr/R$id.class Last modified 2020-11-8; size 241 bytes MD5 checksum 4b6b893cff236cdc445f392e0ce41a2b public final class cn.yan.libr.R$id minor version: 0 Major version: 52 flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER #17 Constant pool for class: #1 = Utf8 cn/yan/libr/R$id #2 = Class #1 // cn/yan/libr/R$id #3 = Utf8 java/lang/Object #4 = Class #3 // java/lang/Object #5 = Utf8 cn/yan/libr/R #6 = Class #5 // cn/yan/libr/R #7 = Utf8 id #8 = Utf8 test_layout #9 = Utf8 I #10 = Integer 2131427329 #11 = Utf8 <init> #12 = Utf8 ()V #13 = NameAndType #11:#12 // "<init>":()V #14 = Methodref #4.#13 // java/lang/Object."<init>":()V #15 = Utf8 ConstantValue #16 = Utf8 Code #17 = Utf8 InnerClasses { public static  int test_layout; descriptor: I flags: ACC_PUBLIC, ACC_STATIC ConstantValue: int 2131427329 } InnerClasses: public static final #7= #2 of #6; //id=class cn/yan/libr/R$id of class cn/yan/libr/RCopy the code

For example, decompile the R$id.class generated by Javac in the main module product that depends on the submodule’s own property members final and has explicit initial values (other R classes are the same) :

// [Craftsman Ruoshui plus wechat Yanbo373131686] Contact me, follow wechat public number: Code farmers a daily topic Without permission is strictly prohibited reproduced https://blog.csdn.net/yanbober 】 yandeMacBook - Pro: libr yan $javap - v R \ $id. Class Classfile /Users/yan/work/tmp/TestR/app/build/intermediates/javac/debug/classes/cn/yan/libr/R$id.class Last modified 2020-11-8; size 368 bytes MD5 checksum 18fbba9c0a1f51068cbd984810e53eac 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 Constant pool = #23 Constant pool = #23 Constant pool = #23 #1 = Methodref #3.#19 // java/lang/Object."<init>":()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 <init> #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 // "<init>":()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 } SourceFile: "R.java" InnerClasses: public static final #14= #2 of #20; //id=class cn/yan/libr/R$id of class cn/yan/libr/RCopy the code

R$id.class, which is final and has a definite initial value, is also 23, as above, so it is no longer given.

As you can see, since the main module of demo has no additional ID resources, the class constant pool occupied by R$ID. class generated by ASM in submodule is 17. In the main module, r.java is generated by the javac compiler, so the constant pool usage is changed to 23. There is only one member attribute in our class, and there is a gap of six between ASM and Javac classes.

AGP3.1.2 R bytecode

For example, decompile R$id.class (R$id.class) generated by Javac in submodule artifacts that are not final and whose property members have no explicit initial values (other R classes are the same) :

yandeMacBook-Pro:libr yan$ javap -v R\$id.class Classfile /Users/yan/work/tmp/TestR/libR/build/intermediates/classes/debug/cn/yan/libr/R$id.class Last modified 2020-11-8; size 409 bytes MD5 checksum 7ff99374e393fa8059ab57413e787968 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 Constant pool = #25 Constant pool #1 = Methodref #5.#20 // java/lang/Object."<init>":()V #2 = Integer 2131492865 #3 = Fieldref #4.#21 // cn/yan/libr/R$id.test_layout:I #4 = Class #23 // cn/yan/libr/R$id #5 = Class #24 // java/lang/Object #6 = Utf8 test_layout #7 = Utf8 I #8 = Utf8 <init> #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 <clinit> #18 = Utf8 SourceFile #19 = Utf8 R.java #20 = NameAndType #8:#9 // "<init>":()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."<init>":()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/RCopy the code

For example, decompile the R$id.class generated by Javac in the main module product that depends on the submodule’s own property members final and has explicit initial values (other R classes are the same) :

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 Constant pool = #23 Constant pool = #23 Constant pool = #23 #1 = Methodref #3.#19 // java/lang/Object."<init>":()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 <init> #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 // "<init>":()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."<init>":()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/RCopy the code

R$id.class, which is final and has a definite initial value, is also 23, as above, so it is no longer given.

As you can see, the number of class constant pools in R$id.class generated by javac in sub-modules is 25, since the main module has no additional ID resources. In the main module, r.java is generated by the javac compiler, so the constant pool usage is changed to 23. There is only one member attribute in our class. There is only two classes difference between the submodule Javac and the main module Javac.

For example, R$id.class has only one member attribute that is clearly defined.

AGP version Son of the moduleR$id.classNumber of constant pools # The main moduleR$id.classNumber of constant pools #
4.1.0 ASM generates 17 ASM generates 17
3.5.0 ASM generates 17 Javac generates 23
3.1.2 Javac generates 25 Javac generates 23

Javac task: R$id. Javac task: R$id. Javac task: R$id. So this R.Java is naturally getting blown up by the constant pool when he participates in Javac, that is, by alerting the class file to large errors; The advanced version of AGP does not pass r.java through javac to R.lass. Instead, it is direct ASM to complete the class in one step. Therefore, the constant pool burst when ASM generates the class.

Why do R bytecode constants that look the same have different sizes?

There are two more submodules than the main Module constant pool because the R member property of the submodule is non-final, so its value is converted to static block initialization in bytecode. Then the constant pool has one more ref constant index, which is exactly 2. The comparison and difference analysis conclusions are as follows:

For AGP3.5.0, the R class generated by LIB Module ASM is 5 smaller than that generated by app Module. Since ASM produces “compact” classes when well written, we know that the JVM has strict and partially loose specifications for class bytecode, and that JavAC is a fully standardized class product. This is often redundant (including class size) relative to ASM classes. Otherwise, since submodule R is non-final, javac class bytecode in this scenario will be more than final, as follows:

And here it all came to light. In order to continue to demonstrate the benefits of AGP4.1.0, let’s look at a set of data conclusions (actually, the constant pool understanding, if you have understood it properly). Here is a comparison of the lib Module’s non-final R products:

resources Number of ASM product constant pools Number of javac product constant pools #
There is one submodule@+id/xxx R$idClass constant pool #17 R$idClass constant pool #25
There are two submodules@+id/xxx R$idClass constant pool #19 R$idClass constant pool #29
There are three submodules@+id/xxx R$idClass constant pool #21 R$idClass constant pool #33

The final R product of the app main Module is compared here:

resources Number of ASM product constant pools Number of javac product constant pools #
There is one submodule@+id/xxx R$idClass constant pool #17 R$idClass constant pool #23
There are two submodules@+id/xxx R$idClass constant pool #19 R$idClass constant pool #25
There are three submodules@+id/xxx R$idClass constant pool #21 R$idClass constant pool #27

It can be seen from the above comparison that the ASM generated bytecode constant pool usage is always lower than the JavAC generated bytecode constant pool usage for both static final and non-static final member scenarios.

How exactly should the possible upper limit be calculated?

To this point, I have to repeat the previous article “Recording the thinking in the process of AGP research, I made a story from an accident!” The end of the world, and to carry out an extended calculation, and then come up with a relatively accurate doomsday time. Javac’s doomsday calculation may seem pointless (AGP4.1.0 is no longer used), but it is worth exploring.

For the above case let’s take a closer look at the bytecode.

The number of non-final properties of R with initial values compiled by Javac is 10942.

// The number of non-final attributes with initial values is 10942, and the number of non-final attributes with initial values is 10942, and the number of non-final attributes with initial values is 10942, and the number of non-final attributes with initial values is 10942. Constant pool: #1 = Methodref #21887.#32838 // java/lang/Object."<init>":()V #2 = Integer 2131492865 #3 = Fieldref #21886.#32839 // Id.MULTI:I #4 = Integer 2131492866 ...... #43780 = NameAndType #32830:#21889 // value_5_textview_info:I #43781 = Utf8 Id #43782 = Utf8 java/lang/Object { public static int MULTI; descriptor: I flags: ACC_PUBLIC, ACC_STATIC ...... / / you can see the face in the static block attribute assignment operation for LDC, putstatic, ldc_w byte offset, at this time, the area has 65525 size, quickly Fried / / that every one more final attribute assignment, here at least more than 4 bytes, Static {}; static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: ldc #2 // int 2131492865 2: putstatic #3 // Field MULTI:I 5: ldc #4 // int 2131492866 7: putstatic #5 // Field NONE:I ...... 65519: ldc_w #21884 // int 2131503806 65522: putstatic #21885 // Field value_5_textview_info:I 65525: return LineNumberTable: line 2: 0 ...... line 10943: 65519 } SourceFile: "Id.java"Copy the code

The final number of initial value properties of R compiled by Javac is 10942.

// The final number of properties with initial values is 10942, and you can add as many as possible // The final number of properties with initial values is 21898, and the number of properties is 65536: #1 = Methodref #3.#21896 // java/lang/Object."<init>":()V ...... #21896 = NameAndType #21890:#21891 // "<init>":()V #21897 = Utf8 Id #21898 = Utf8 java/lang/Object { public static final  int MULTI; descriptor: I flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: int 2131492865 ...... Public Id(); public Id(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 } SourceFile: "Id.java"Copy the code

The final or non-final R generated by ASM does not have static blocks with or without initial values. It occupies constant pool space and saves constant pool space compared to Javac. The bytecode fragment is not provided here, but can be analyzed in the section below.

On the instructions of the static block takes up the size in bytes may reference en.wikipedia.org/wiki/Java_b… Calculation. This is probably the most detailed class file structure analysis you’ve ever seen.

With this background and case studies, we can now calculate when the world will end.

AGP version Description of general occupancy calculations
4.1.0 The non-final property R of the submodule is generated by ASM, so there is no static code block assignment, so each property can be calculated according to the constant pool occupancy. In general, the constant pool identifier is increased by #2 for each additional property. The final property R of the main Module is generated by ASM, so there is no static code block assignment, so each property can be calculated according to the constant pool occupancy. In general, the constant pool identifier is increased by #2 for each additional property. It’s just that the R attribute of the main Module counts the combined number of all sub-modules. Therefore, both the sub-module and the main module can generally be estimated with a single dimension of constant pool, and the upper limit of constant pool is the upper limit of the number of attributes.
3.5.0 The non-final property R of the submodule is generated by ASM, so there is no static code block assignment, so each property can be calculated according to the constant pool occupancy. In general, the constant pool identifier is increased by #2 for each additional property. The final property R of the main Module is generated by Javac, so there is no static code block assignment, so each property can be calculated according to the constant pool occupancy. In general, the constant pool identifier will increase #2 for each additional property. It’s just that the R attribute of the main Module counts the combined number of all sub-modules. Therefore, both the sub-module and the main module can generally be estimated with a single dimension of constant pool, and the upper limit of constant pool is the upper limit of the number of attributes.
3.1.2 The non-final property R of submodule is generated by JavAC, so there is static code block assignment, so the constant pool occupancy of each property is actually less than the size of the static code block, so the calculation dimension can be counted by using the sum of the instruction bits of static code block. In general, every increment of static block property will be at least 4 more bytes (depending on the ldc_w or LDC instruction). The final property R of the main Module is generated by Javac, so there is no static code block assignment, so each property can be calculated according to the constant pool occupancy. In general, the constant pool identifier will increase #2 for each additional property. It’s just that the R attribute of the main Module counts the combined number of all sub-modules. So submodules are calculated as static blocks, usually adding 4 or more bytes to a property; The main Module is generally estimated as a single dimension of the constant pool, and the upper limit of the constant pool is the upper limit of the number of attributes.

It can be found here that with the upgrade of AGP, there is actually a certain effect here. The number of resources that can be held by R generated by javac in the 3.1.2 lib Module is much smaller than that of R generated by ASM in the 3.5.0 lib Module. Roughly, this wave of improvements has at least doubled the number of resources that lib Modules can hold.

Why are R classes generated by ASM in AGP “compact”?

After the release of this article, some people will say that ASM is “compact” bytecode, so send it to home, here directly give an example to verify, still use the control variable method, we write a Java file javac to generate class, Use ASM to generate a class file with the same effect and compare.

The Java class we wrote looks like this:

/ / 【 craftsman if water to add WeChat yanbo373131686 contact me, concern WeChat public number: code farmers a daily topic Without permission is strictly prohibited reproduced, https://blog.csdn.net/yanbober]

public class R {
    public static final class id {
        public static int test1 = 1;
        public static int test2 = 2; }}Copy the code

The bytecode of the id class is as follows:

yandeMacBook-Pro:src yan$ javap -v R\$id.class Classfile /Users/yan/work/tmp/aaa/src/R$id.class ....... Constant pool: #1 = Methodref #5.#16 // java/lang/Object."<init>":()V ...... #24 = Utf8 R { ...... Static {}; static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: iconst_1 1: putstatic #2 // Field test1:I 4: iconst_2 5: putstatic #3 // Field test2:I 8: return LineNumberTable: line 3: 0 line 4: 4 } ......Copy the code

This non-final comparison is clearly normal, with both constant pools and static blocks occupied.

We’ll look at how AGP4.1.0 by ASM is generated, first look at the com. Android. View the build: gradle: 4.1.0 source GenerateNamespacedLibraryRFilesTask. Kt, are as follows:

abstract class GenerateNamespacedLibraryRFilesTask @Inject constructor(objects: ObjectFactory) :
    NonIncrementalTask() {
    ......
    private class TaskAction @Inject constructor(private val params: Params) : Runnable {
        override fun run(a){.../ / generated R.j ar
            if(params.rJarFile ! =null) {
                // Generate the R.jar file containing compiled R class and its' inner classes.exportToCompiledJava(ImmutableList.of(resources), params.rJarFile.toPath()) } ...... }}... }Copy the code

The above exportToCompiledJava method from com. Android. View the build: builder: 4.1.0 source BytecodeRClassWriterKt. Kt, are as follows:

// The parameter passes a list of resource symbols and the path to the output r.jar, and the resource ID is non-final
@Throws(IOException::class)
fun exportToCompiledJava(tables: 可迭代<SymbolTable>, outJar: Path, finalIds: Boolean = false) {
	//JarFlinger is a wrapper class used to generate jars
    JarFlinger(outJar).use { jarCreator ->
        // NO_COMPRESSION because R.jar isn't packaged into final APK or AAR
        jarCreator.setCompressionLevel(NO_COMPRESSION)
        val mergedTables = tables.groupBy { it.tablePackage }.map { SymbolTable.merge(it.value) }
        mergedTables.forEach { table ->
        	// Generate a compiled Java class based on a list of resource symbols
            exportToCompiledJava(table, jarCreator, finalIds)
        }
    }
}
Copy the code

Continue to look at the exportToCompiledJava method implementation:

@Throws(IOException::class)
fun exportToCompiledJava(
    table: SymbolTable,
    jarMerger: JarCreator,
    finalIds: Boolean = false
) {
    val resourceTypes = EnumSet.noneOf(ResourceType::class.java)
    // Loop to generate different bytecode R inner classes depending on the resource type, such as R$id.class
    for (resType in ResourceType.values()) {
        // Generate the corresponding R internal class bytecode byte array by ASM
        valbytes = generateResourceTypeClass(table, resType, finalIds) ? :continue
        resourceTypes.add(resType)
        // Generate the inner class name, such as R$id
        val innerR = internalName(table, resType)
        // Write the ASM byte array to the class file and add it to the JAR
        jarMerger.addEntry(innerR + SdkConstants.DOT_CLASS, bytes.inputStream())
    }
	// Generate the most external R.lass and add it to the JAR
    // Generate and write the main R class file.
    val packageR = internalName(table, null)
    jarMerger.addEntry(
        packageR + SdkConstants.DOT_CLASS,
        generateOuterRClass(resourceTypes, packageR).inputStream())
}

// Generate the class name based on the resource type concatenation, such as R$id
private fun internalName(table: SymbolTable, type: ResourceType?).: String {
    val className = if (type == null) "R" else "R$${type.getName()}"

    return if (table.tablePackage.isEmpty()) {
        className
    } else {
        "${table.tablePackage.replace("."."/")}/$className"}}Copy the code

GenerateResourceTypeClass method is the focus of the code above, we continue to go in, as follows:

// This is the ASM operation that generates R$id.class
private fun generateResourceTypeClass(
    table: SymbolTable, resType: ResourceType, finalIds: Boolean): ByteArray? {
    val symbols = table.getSymbolByResourceType(resType)
    if (symbols.isEmpty()) {
        return null
    }
    val cw = ClassWriter(COMPUTE_MAXS)
    val internalName = internalName(table, resType)
    cw.visit(
            Opcodes.V1_8,
            ACC_PUBLIC + ACC_FINAL + ACC_SUPER,
            internalName, null."java/lang/Object".null)

    cw.visitInnerClass(
            internalName,
            internalName(table, null),
            resType.getName(),
            ACC_PUBLIC + ACC_FINAL + ACC_STATIC)

    for (s in symbols) {
        cw.visitField(
                ACC_PUBLIC + ACC_STATIC + if (finalIds) ACC_FINAL else 0,
                s.canonicalName,
                s.javaType.desc,
                null.if (s is Symbol.StyleableSymbol) null else s.intValue
        )
                .visitEnd()

        if (s is Symbol.StyleableSymbol) {
            val children = s.children
            for ((i, child) in children.withIndex()) {
                cw.visitField(
                        ACC_PUBLIC + ACC_STATIC + if (finalIds) ACC_FINAL else 0."${s.canonicalName}_${canonicalizeValueResourceName(child)}"."I".null,
                        i)
            }
        }
    }

    // Constructor
    val init = cw.visitMethod(ACC_PRIVATE, "<init>"."()V".null.null)
    init.visitCode()
    init.visitVarInsn(ALOAD, 0)
    init.visitMethodInsn(INVOKESPECIAL, "java/lang/Object"."<init>"."()V".false)
    init.visitInsn(RETURN)
    init.visitMaxs(0.0)
    init.visitEnd()

    // init method
    if (resType == ResourceType.STYLEABLE) {
        val method = Method("<clinit>"."()V")
        val clinit = GeneratorAdapter(ACC_PUBLIC.or(ACC_STATIC), method, null.null, cw)
        clinit.visitCode()
        for (s in symbols) {
            s as Symbol.StyleableSymbol
            val values = s.values
            clinit.push(values.size)
            clinit.newArray(INT_TYPE)

            for ((i, value) in values.withIndex()) {
                clinit.dup()
                clinit.push(i)
                clinit.push(value)
                clinit.arrayStore(INT_TYPE)
            }

            clinit.visitFieldInsn(PUTSTATIC, internalName, s.canonicalName, "[I")
        }
        clinit.returnValue()
        clinit.endMethod()
    }

    cw.visitEnd()

    return cw.toByteArray()
}
Copy the code

You may say “I’m not familiar with ASM”, and I’m confused by the code above. Let me turn the above code into an example that you can understand, as follows:

// Generate the ID class bytecode of the javac case in this section
class RDump implements Opcodes {
    public static byte[] dump() throws Exception {
        ClassWriter cw = new ClassWriter(COMPUTE_MAXS);
        String internalName = "R$id";
        cw.visit(
                Opcodes.V1_8,
                ACC_PUBLIC + ACC_FINAL + ACC_SUPER,
                internalName, null."java/lang/Object".null);

        cw.visitInnerClass(
                internalName,
                "R"."id",
                ACC_PUBLIC + ACC_FINAL + ACC_STATIC);

        cw.visitField(ACC_PUBLIC + ACC_STATIC + 0."test1"."I".null.1).visitEnd();
        cw.visitField(ACC_PUBLIC + ACC_STATIC + 0."test2"."I".null.2).visitEnd();

        // Constructor
        MethodVisitor init = cw.visitMethod(ACC_PRIVATE, "<init>"."()V".null.null);
        init.visitCode();
        init.visitVarInsn(ALOAD, 0);
        init.visitMethodInsn(INVOKESPECIAL, "java/lang/Object"."<init>"."()V".false);
        init.visitInsn(RETURN);
        init.visitMaxs(0.0);
        init.visitEnd();

        cw.visitEnd();

        returncw.toByteArray(); }}public class Test {
    public static void main(String[] args) throws Exception {
        FileOutputStream stream = new FileOutputStream("t1.class"); stream.write(RDump.dump()); stream.flush(); stream.close(); }}Copy the code

Javap decompiles non-final members without static block instructions, which is directly reflected in constant pool usage.

At this point, you might say, “This is how the ASM paragraph is written, how does it reflect the” compact “you said earlier? The ASM Bytecode Outline plugin is used to generate ASM Code for the class in this section.

The class generated by this code is decompiled just like the class compiled by Javac, with non-final pits in both constant pools and static blocks.

See why I said “compact” earlier? Again, the JVM Class bytecode specification is mandatory, but it’s not absolutely mandatory, depending on how you want ASM to be generated.

Is Google still doing things?

On an article also said that a long time ago lib era (Eclipse) R is also the final module, not letter can see tools.android.com/tips/non-co… At that time, Google changed the R for submodules to non-final for building efficiency, but the R for the main module was still final. With the migration of time, no matter how the AGP is upgraded, you can see Google’s small actions continue, but the submodule is not final, the main module final rule has not changed.

If you have a newer version of AS (like 4.1), you’ll notice that your main module is warned about the switch notation for R ID (if else), AS follows:

Some people who compile with a new version of AS with Lint on May also report an error directly from LintResource lDs will be non-final in Android Gradle Plugin version 5.0This problem is actually a kind of advance warning from Google, which can be temporarily bypassing with the following configuration:

/ / 【 craftsman if water to add WeChat yanbo373131686 contact me, concern WeChat public number: code farmers a daily topic Without permission is strictly prohibited reproduced, https://blog.csdn.net/yanbober]

android {
    lintOptions {
        disable 'NonConstantResourceId'}}Copy the code

If you use a Butterknife for your project, you will find that AS will also give you a hint Non-final resource IDs have been supported for a few years now. Apply the plugin and use R2 as detailed in the readme. Or, even better, stop using Butter Knife and use view binding! . What the hell? Butterknife dying? Go to Butterknife’s issues, and sure enough, the chicken king has already given the same answer, as follows:

In AGP5.0, the R member attribute of the main module will be made non-final as well.

conclusion

It turns out that the assumptions we made at the end of the last article are true, and Google is probably aware of some problems, whether it’s due to package size, insufficient R, or build speed. Personally, I don’t believe that Google made this change by accident. Should be the inevitable choice of one of the above scenarios.

There is still a long way to go. This is a quick solution to a difficult problem. After a glance at the execution order of Gradle Task and the above comparison analysis, I have roughly guessed the changes in the AGP section. Other temporarily unavailable analysis, interested can continue to dig down AGP source code or build their own comparative analysis.

Code word is not easy, after reading this article you still don’t want to make a friend ???? Quickly add a wechat concern under the public account what, slipped away.