backgroundIn the past period of time, JD Android APP has optimized the size of the installation package through a series of means such as image compression, image transfer download, resource obfuscation compilation, plug-inization, plug-in post-installation, and hybrid development, and achieved good weight loss benefits. After completing these conventional weight loss optimizations, in order to further optimize the size of the installation package, we investigated the newly launched R8 compiler by Google and learned that the R8 compiler can improve the build efficiency while optimizing the package size. So we started to try to upgrade the AGP version to enable R8 compilation. The upgrade process was not very smooth and encountered the following problems. Introduction to obfuscation toolsFor Android applications, code obfuscation is often used as one of the means to improve the security of the application. Code obfuscation is to convert the source code into a form that is functionally equivalent but difficult to read and understand, which reduces the readability of the code. Even if it is successfully decompiled, it is difficult to derive the true meaning of the code. Code obfuscation can increase the difficulty of decompiling and cracking the application. On the other hand, after code obfuscation, the names of classes, methods or fields are mapped to short and meaningless names, which can also reduce the size of the application package. 01ProGuardBefore AGP3.4.0, ProGuard was used as the default optimization tool in the Android packaging process. ProGuard optimizes the source code in the following four steps: shrink, optimize, obfuscate, and preverify.
The above four stages can be run independently and are enabled by default. You can turn off the corresponding stages by setting the -dontshrink, -dontoptimize, -dontobfuscate, and -dontpreverify rules in the obfuscation configuration file. After ProGuard performs code compression optimization and obfuscation on the .class file, it will be handed over to the D8 compiler for desugaring and converting the .class file into a .dex file. The execution process is as follows: Figure 1 ProGuard and D8 optimization process 02R8After AGP 3.3.0, Google officially introduced R8, which is a replacement for ProGuard, but is compatible with ProGuard's keep rules. R8 integrates optimization processes such as code desugaring, compression, obfuscation, optimization, and dex processing (D8) into one step. After enabling R8 compilation, the project construction efficiency is better than ProGuard in the actual development process. The compilation process is as follows. Figure 2 R8 optimization process
When Android officially releases the Release package, we usually enable the optimization function by setting: release { Pitfall experience01R8 Confusion Rules1.1 Problem SymptomFirst, let's take a look at the mapping.txt file generated after the AGP 3.3.3 build tool packages the obfuscation and observe the mapping relationship of the data classes after obfuscation. The OtherBean data class has an exclusion rule added to the obfuscation configuration file - keep class com.jd.obfuscate.bean.OtherBean{ *; }, while the WatermelonBean data class has no keep rule added. In addition, these two data classes have three fields with the same name: name, color, and shape: com.jd.obfuscate.bean.OtherBean -> com.jd.obfuscate.bean.OtherBean: Carefully observe the obfuscated class names and field names of the two data classes. Since the WatermelonBean data class does not have a keep rule added, it is found that the class name and other field names of the WatermelonBean data class are obfuscated into meaningless names, while the fields name, color, and shape are not obfuscated. Next, let's take a look at how the WatermelonBean data class will be obfuscated after upgrading AGP 3.6.4 and enabling R8 compilation: com.jd.obfuscate.bean.WatermelonBean -> bbaaa: After enabling R8 compilation, it was found that the name, color, and shape fields in the WatermelonBean data class without the keep rule were also obfuscated. When interacting with the server during development, when using frameworks such as Gson and FastJson to parse server data, deserialization or reflection is involved. The corresponding object data class cannot be confused, and corresponding keep rules should be added, otherwise the json data cannot be parsed into the correct object. So if your project fails to check that all network parsing data classes have been added with corresponding keep rules before the application goes online after upgrading AGP, then there will be problems with business functions. 1.2 Cause AnalysisWhat causes the above phenomenon? ① When you upgrade AGP and enable R8 compilation, if you look carefully at the packaging log, you will find a warning about the "-useuniqueclassmembernames" obfuscation rule: AGPBI: {"kind":"warning","text":"Ignoring option: -useuniqueclassmembernames", ②For those who are familiar with ProGuard obfuscation rules, you should be able to tell at a glance that the above obfuscation phenomenon is caused by the "-useuniqueclassmembernames" obfuscation rule. The explanation of this rule is: generate globally unique obfuscated names for member variables with the same name in different classes after obfuscation. If this rule is not set, methods or fields of different classes may be mapped to meaningless names such as a, b, and c. ③Remove the "-useuniqueclassmembernames" obfuscation rule and verify. Let's look at the obfuscated mapping file of the WatermelonBean data class: com.jd.obfuscate.bean.WatermelonBean -> com.jd.obfuscate.bean.a: It was found that the fields name, color, and shape in the WatermelonBean data class were all obfuscated into letters, confirming that the obfuscation rule caused the class name to be obfuscated while some of its field names were not obfuscated. How does Proguard obfuscate field names? Developers who have developed custom Android Gradle plugins should be familiar with the Gradle Transform abstract class. Gradle Transform is a set of standard APIs officially provided by Android to developers to modify .class files during the conversion of class files to dex files during the project build phase. Through these APIs, bytecodes can be manipulated to achieve common functions. AGP source code viewing method, add dependencies in the project: implementation 'com.android.tools.build:gradle:3.3.3' Locate the source code to com.android.build.gradle.internal.transforms.ProGuardTransform: public void transform(final TransformInvocation invocation) { Create an asynchronous task processed by ProGuard and add it to the WorkQueue queue, execute doMinification() -> runProguard() -> (new ProGuard(this.configuration)).execute(); //Core method, perform corresponding operations according to the obfuscation rule configuration The above is the basic process of ProGuard execution. Let's focus on the obfuscate() obfuscation method: The execute() method performs the actual obfuscation operation: The Obfuscator class is the class that actually does the obfuscation, including class obfuscation ClassObfuscator and member obfuscation MemberObfuscator. In the executed method, two processing logics about the "-useuniqueclassmembernames" rule were found: // If the class member names have to correspond globally, Obfuscation is divided into six steps: Step 1: If the "-useuniqueclassmembernames" obfuscation rule is set, first create a MethodLinker visitor object through ClassVisitor)new AllMemberVisitor(new MethodLinker()), convert the field information in all classes into a linked list and connect them together, use the field name and field type as the key, query whether the visitorInfo information of the field already exists in the memberMap, if not, call the lastMember() method to try to get the visitorInfo of the field in the linked list and store it in the memberMap; if it can be queried, add the field information as visitorInfo to the linked list of field information: public void visitAnyMember(Clazz clazz, Member member) { Step 2: Mark the class name, method or field name to which the keep rule is added (NameMarker) so that it will not be confused. The ProGuard source code makes extensive use of the visitor pattern, accessing the object pool by creating a nameMarker object of the ClassVisitor implementation class, and ultimately executing the method in the NameMarker class: public void visitProgramClass(ProgramClass programClass) { It is relatively simple to mark the field keep name, just set it through lastVisitorAccepter.setVisitorInfo(name). Step 3: Collect the mapping relationships of all members in all classes (MemberNameCollector), first obtain the keep name (visitorInfo) marked in the previous step from the field list, and put the methods or fields of the same type into the same Map<obfuscated name, original name>: public void visitAnyMember(Clazz clazz, Member member) { Step 4: Create an obfuscator name. If no mapping is found in the keep name list, create a new obfuscator name (MemberObfuscator): public void visitAnyMember(Clazz clazz, Member member) { The NameFactory interface class is mainly responsible for generating new obfuscated names. If no custom obfuscationDictionary dictionary is set, the implementation class of the NameFactory interface, the SimpleNameFactory class, mainly generates new obfuscated names through the newName method: private static final int CHARACTER_COUNT = 26; CHARACTER_COUNT is defined as 26, which means exactly 26 letters. The nextName() method uses the index counter to increment each time a new name is generated, so ProGuard's obfuscated names start from a to z. Step 5: Apply the obfuscated name, create a ClassRenamer visitor object, add the new obfuscated name to the constant pool through the ConstantPoolEditor object, and update the field name index u2nameIndex to point to the new obfuscated name. // Actually apply the new names. Step 6: Constant pool compression. The new obfuscated name is created by adding new data to the constant pool. The original data is not deleted and needs to be repaired. Due to the limited space, we will not analyze it in detail. Why doesn't the R8 compiler support this obfuscation rule? From the above phenomenon, it can be seen that the R8 compiler ignored the "-useuniqueclassmembernames" obfuscation rule, but there is no relevant information about this rule in the Google Android development document "Reduce Application Size" user guide. Next, try to find the reason why this rule is ignored at the source code level. By searching for information and searching for the keyword "useuniqueclassmembernames" in the R8 source code repository, I found the code submission record that R8 does not support this rule. The "-useuniqueclassmembernames" obfuscation rule is mainly used for incremental obfuscation in ProGuard, but the main goal of introducing the R8 compiler is to further reduce the size of the application. If this rule is supported in R8, it will only increase the complexity of code obfuscation and bring no real benefits. What other obfuscation rules does R8 not support? Looking at the ProguardConfigurationParser.java source code (https://r8.googlesource.com/r8/+/3a100449ba5b490cd13d466b8c7e17dcd500722a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java) , the following obfuscation rules will be ignored by the R8 compiler: -forceprocessing 1.3 Summary of ConfusionIf your project meets the following conditions at the same time, it is possible that during the AGP upgrade, field parsing may fail to obtain the corresponding value, resulting in abnormal business functions:
Obfuscation suggestions for Android development:
02v1 Signature Logic2.1 v1 signature loss issueThe JD APP debugging package will read the v1 signature information. After upgrading to AGP 3.6.4, the debugging package crashed. After eliminating the cause, it was found that the v1 signature in the debugging package generated by the Android Studio run button was lost. We usually make signature-related settings in the build.gradle file in the project app directory: signingConfigs { 2.2 Cause AnalysisBy consulting the AGP 3.6.4 source code, it is found that the IncrementalPackagerBuilder will be created in the PackageAndroidArtifact class when executing the doTask() static method, which involves signature-related code. The code is as follows: public IncrementalPackagerBuilder withSigning( As can be seen from the enableV1Signing() method above, targetApi refers to the system version of the mobile phone connected to the computer. It will determine whether to enable v1 signature based on whether the system version of the currently connected test machine is less than Android 7.0. If the mobile phone system is greater than Android 7.0, the v1 signature will be invalid. Solution: Set targetApi to null through reflection and add the following settings to the build.gralde file: project.afterEvaluate { Judging from the change records submitted in the source code IncrementalPackagerBuilder.java, the logic of enabling v1/v2 signatures has changed many times. 03Packing process3.1 PhenomenonDuring the packaging process, JD APP uses APT technology to identify annotations in the code, and then generates a json file with the annotation information and stores it in the project assets directory. The json file will then be packaged into the apk. However, after upgrading AGP, the json file is lost during packaging, resulting in functional abnormalities. The cause was found to be that the execution order of some tasks in the packaging process changed: Before upgrading AGP: Because the annotation processing logic is in the compileXXXJavaWithJavac task, after the execution order of the above two tasks changes, the task of merging assets resource files takes precedence over copying json files to the project assets directory, which eventually causes the json file to be lost in the apk package. 3.2 SolutionSet the two tasks to depend on each other, and add the following script to build.gradle: project.afterEvaluate { AGP Upgrade Recommendations
SummarizeThis article mainly introduces the pitfalls of JD APP during the upgrade of Android build tool AGP 3.6.4. After the upgrade, the package size was reduced by about 1.5MB. I hope the above pitfalls can help readers who plan to upgrade AGP. |
<<: Apple's iOS system was exposed to frequently obtain user privacy
>>: From 47% to 80%, Ctrip Hotel APP Fluency Improvement Practice
Group buying has become a marketing activity spre...
The launch of mini programs has brought convenien...
There are new ideas every year, but this year the...
As an information flow optimizer , I envy those w...
The following is the latest traffic ranking of 56...
How to make Beijing more colorful after the storm...
About 50% of the creative copywriting in informat...
The author of this article spent 6 hours to creat...
I have always wanted to interview Zhu Feng to tal...
As the size of public accounts grows, with the in...
Blake Snyder, a famous Hollywood screenwriter, sh...
The Apple conference has finally come to an end, ...
Training course content: This is a practical cour...
Expert of this article: Chen Sijiao, chief physic...