[Android Performance Optimization]: ProGuard, obfuscation, R8 optimization

Author: Xiaoyu

Preface

The source code written in Java is compiled to generate the corresponding class file, but the class file is a very standard file. Many software on the market can decompile the class file. For the security of our app, we need to use Android The code obfuscates this functionality. For Java obfuscation, ProGuard is a commonly used obfuscation tool, and it is not only an obfuscation tool, it can also compress, optimize, and obfuscate the code.

Let’s briefly introduce the ProGuard workflow.

1 ProGuard workflow The ProGuard workflow includes four steps: shrink, optimize, obfuscate, and preserveigy . These four steps are optional, but the order remains unchanged .

  • shrink : Detect and delete unused classes, fields, methods and properties in the project.
  • optimize : optimize bytecode, remove useless instructions, or perform instruction optimization.
  • obfuscate : code obfuscation, using meaningless names to represent classes, fields, methods and attributes in the code, reducing the readability of the code after decompilation.
  • preverify : Pre-verify Java 1.6 and above and verify the StackMap/StackMapTable properties. It can be turned off during compilation to speed up compilation .

2 Code optimization and obfuscation in Android

In Android builds, ProGuard was also used for code optimization and obfuscation before AGP 3.4.0, but after 3.4.0, Google assigned this work to the R8 compiler with better performance . Although ProGuard is abandoned, the R8 compiler is still compatible with ProGuard's configuration rules . The following optimizations can be done using the R8 compiler:

  • 1. Code reduction
  • 2. Resource reduction
  • 3. Code obfuscation
  • 4. Code optimization
1. Code reduction:

Code reduction refers to: R8 intelligently detects unused classes, fields, methods, properties, etc. in the code during compilation and removes them.

For example, your project relies on many libraries, but only uses a small part of the code in the library. In order to remove this part of the code, R8 will determine all entry points of the application code based on the configuration file: including the first Activity or service started by the application, etc. , R8 will detect the application code based on the entry, and construct a chart to list the methods, member variables and classes that may be accessed during the running of the application, and treat the code that is not associated with the chart as removable code. .

As shown below:

Entry position in the figure: MainActivity. In the entire calling link, foo, bar functions and the faz function in the AwesomeApi class are used, so this part of the code will be built into the dependency graph, and the OkayApi class and its baz function are not accessed. , then this part of the code can be optimized. How to use:

android {
    buildTypes {
        release {
            ...
            minifyEnabled true
        }
    }
    ...
}

minifyEnabled is set to true, and the R8 code reduction function will be enabled by default. There are two situations that need to be paid attention to when optimizing code: 1. In the case of reflection calls 2. In the case of JNI calls, R8 does not detect reflection and JNI. If it is not processed in the configuration file, this part of the code will be discarded. NoClassFindException exception occurs, how to solve it?

  • 1.1: Use -keep in the configuration file to describe this category :
-keep public class MyClass
  • 1.2: Add @keep annotation to the code that needs to be kept

Prerequisites: 1. Declare the use of AndroidX 2. Use the AGP default ProGuard file.

R8 configuration file

R8 uses ProGuard rule files to determine which parts of code need to be retained. The sources of configuration files are divided into the following:

  • 1.AndroidStudio:

Location: /proguard-rules.proDescription:

When creating a new module, a default: proguard-rules.pro file will be created in the current module directory.

  • 2.AGP plug-in

Location: proguard-android-optimize.txt generated by AGP at compile time Description:

The Android Gradle plugin generates proguard-android-optimize.txt (which contains rules useful for most Android projects) and enables @Keep* annotations.

After compilation, 3 files will be generated in the \build\intermediates\proguard-files\ directory:

proguard-android-optimize.txt-4.1.1 : ProGuard configuration file that needs to be optimized for code optimization. proguard-android.txt-4.1.1 : Indicates a ProGuard file that does not require optimize code optimization. 4.1.1: Indicates the AGP plug-in version of the current module .

  • 3. Library dependencies

Location: AAR library: /proguard.txt
JAR library: /META-INF/proguard/ Description:

The introduced aar or jar package library contains proguard optimization rules by default, which will also be included in the R8 configuration items during the compilation process, so pay special attention to the conflict between the proguad introduced in the aar and the original project rules.

  • 4.AAPT2 (Android resource packaging tool)

Location: After building the project using minifyEnabled true: /build/intermediates/proguard-rules/debug/aapt_rules.txt Description:

AAPT2 generates retention rules based on references to classes, layouts, and other app resources in the app manifest. For example, AAPT2 adds a retention rule for each activity you register as an entry point in your app manifest.

  • 5. Custom configuration file

Location: By default, when you create a new module using Android Studio, the IDE creates /proguard-rules.pro so you can add your own rules. illustrate:

You can add additional configurations and R8 will apply them at compile time. If you set the minifyEnabled attribute to true, R8 will combine rules from all the above available sources, but you need to be careful about rule conflicts caused by the introduction of dependent libraries.

If you need to output a complete report of all rules applied by R8 when building the project: You can add the following statement to proguard-rules.pro.

// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt

If you need to add additional proguad files: You can add the corresponding files to the proguardFiles attribute of the module's build.gradle file: If you add rules to each productflavor separately, you can use the following method:

android {
    ...
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android-optimize.txt'),
                // List additional ProGuard rules for the given build type here. By default,
                // Android Studio creates and includes an empty rules file for you (located
                // at the root directory of each module).
                'proguard-rules.pro'
        }
    }
    flavorDimensions "version"
    productFlavors {
        flavor1 {
            ...
        }
        flavor2 {
            proguardFile 'flavor2-rules.pro'
        }
    }
}
2. Resource reduction

Resource reduction is performed after code reduction . Only after removing unnecessary code can we know which resources in the code are not introduced and can be removed. To reduce resources, just add the shrinkResources attribute under the module gradle:

android {
    ...
    buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
            ...
        }
    }
}

Note: Resource reduction requires enabling code reduction minifyEnabled in advance. Of course, you can also add a whitelist to resource files :

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
    tools:discard="@layout/unused2" />

Use tools:keep to specify resource files that need to be kept, and tools:discard to specify resource files that can be discarded.

3. Code obfuscation

Obfuscation refers to using meaningless characters to represent class names, method names, and attribute names: See the figure below:

How to obfuscate?

Some basic rules for obfuscation:

  • One star: indicates that the class name under the current package is retained. If there is a sub-package, the classes in the sub-package will be confused.
-keep class cn.hadcn.test.*
  • Two stars: indicates that the class name under the current package is retained. If there is a sub-package, the class name in the sub-package will also be retained.
-keep class cn.hadcn.test.**
  • Although the above method retains the class name, the content will still be confused. Use the following method to retain the content:
-keep class cn.hadcn.test.* {*;}
  • On this basis, we can also use Java's basic rules to protect specific classes from being confused. For example, we can use Java rules such as extends and implements. The following example prevents all classes that inherit Activity from being confused:
-keep public class * extends android.app.Activity
  • If we want to keep the internal classes in a class from being confused, we need to use the $ symbol. The following example shows that all the public content in the ScriptFragment internal class JavaScriptInterface is kept from being confused.
-keepclassmembers class cc.ninty.chat.ui.fragment.ScriptFragment$JavaScriptInterface {
   public *;
}
  • Furthermore, if you don't want to keep the entire content of a class from being obfuscated, but just want to protect specific content under the class, you can use:
<init>;     //匹配所有构造器
<fields>;   //匹配所有域
<methods>;  //匹配所有方法方法
  • privateYou can also add , public, etc. before or nativeto further specify the content that will not be confused, such as
-keep class cn.hadcn.test.One {
    public <methods>;
}
  • Of course, you can also add parameters. For example, the following means that the constructor that uses JSONObject as an input parameter will not be confused:
-keep class cn.hadcn.test.One {
   public <init>(org.json.JSONObject);
}
  • Sometimes you still think: I don’t need to keep the class name, I just need to keep the specific methods under the class from being confused. Then you can’t use the keep method. The keep method will keep the class name, and You need to use keepclassmembers, so that such names will not be maintained. In order to facilitate the understanding of these rules, the official website provides the following table:

Detailed explanation of android packaging confusion and syntax rules_android

# -keep关键字
# keep:包留类和类中的成员,防止他们被混淆
# keepnames:保留类和类中的成员防止被混淆,但成员如果没有被引用将被删除
# keepclassmembers :只保留类中的成员,防止被混淆和移除。
# keepclassmembernames:只保留类中的成员,但如果成员没有被引用将被删除。
# keepclasseswithmembers:如果当前类中包含指定的方法,则保留类和类成员,否则将被混淆。
# keepclasseswithmembernames:如果当前类中包含指定的方法,则保留类和类成员,如果类成员没有被引用,则会被移除。
Precautions
  • 1. After using AS4.1.1 version for obfuscated compilation, the following four files will be generated under /build/outputs/mapping/release/:

  • usage : Unused files, that is, files after removal.

  • seeds : unobfuscated classes and members.

  • mapping : Mapping files before and after obfuscation. This file is useful when using anti-obfuscation.

  • configuration : the rule file after integrating all ProGuard files:

  • 2. When jni is used, the jni method cannot be confused.

-keepclasseswithmembernames class * {    
    native <methods>;
}
  • 3. The classes used for reflection are not confused.

  • 4. The classes in AndroidMainfest are not confused. All four major components, subclasses of Application and all classes under the Framework layer will not be confused by default, and custom Views will not be confused by default. Therefore, there is no need to add rules in Android Studio that exclude custom Views or confuse the four major components like many posted online.

  • 5. When interacting with the server, when using frameworks such as GSON and fastjson to parse server data, the written JSON object class must not be confused, otherwise the JSON cannot be parsed into the corresponding object;

  • 6. When using third-party open source libraries or referencing other third-party SDK packages, if there are special requirements, the corresponding obfuscation rules need to be added to the obfuscation file;

  • 7. JS calls that use WebView also need to ensure that the interface methods written are not confusing. The reason is the same as the first one;

  • 8. Do not confuse subclasses of Parcelable with static member variables of Creator, otherwise Android.os.BadParcelableException will occur;

# 保持Parcelable不被混淆    
-keep class * implements Android.os.Parcelable {         
    public static final Android.os.Parcelable$Creator *;
}
  • 9. When using the enum type, you need to pay attention to avoid confusion between the following two methods. Because of the particularity of the enum class, the following two methods will be called by reflection. See the second rule.
-keepclassmembers enum * {  
    public static **[] values();  
    public static ** valueOf(java.lang.String);  
}
obfuscated template

The following is a confusion template: you can add and delete according to your own needs :

#--------------------------1.实体类---------------------------------
# 如果使用了Gson之类的工具要使被它解析的JavaBean类即实体类不被混淆。(这里填写自己项目中存放bean对象的具体路径)
-keep class com.php.soldout.bean.**{*;}

#--------------------------2.第三方包-------------------------------

#Gson
-keepattributes Signature
-keepattributes *Annotation*
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
-keep class com.google.gson.examples.android.model.** { *; }
-keep class com.google.gson.* { *;}
-dontwarn com.google.gson.**

#butterknife
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }

#-------------------------3.与js互相调用的类------------------------


#-------------------------4.反射相关的类和方法----------------------


#-------------------------5.基本不用动区域--------------------------
#指定代码的压缩级别
-optimizationpasses 5

#包明不混合大小写
-dontusemixedcaseclassnames

#不去忽略非公共的库类
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers

#混淆时是否记录日志
-verbose

#优化  不优化输入的类文件
-dontoptimize

#预校验
-dontpreverify

# 保留sdk系统自带的一些内容 【例如:-keepattributes *Annotation* 会保留Activity的被@override注释的onCreate、onDestroy方法等】
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod

# 记录生成的日志数据,gradle build时在本项根目录输出
# apk 包内所有 class 的内部结构
-dump proguard/class_files.txt
# 未混淆的类和成员
-printseeds proguard/seeds.txt
# 列出从 apk 中删除的代码
-printusage proguard/unused.txt
# 混淆前后的映射
-printmapping proguard/mapping.txt


# 避免混淆泛型
-keepattributes Signature
# 抛出异常时保留代码行号,保持源文件以及行号
-keepattributes SourceFile,LineNumberTable

#-----------------------------6.默认保留区-----------------------
# 保持 native 方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}

-keepclassmembers public class * extends android.view.View {
 public <init>(android.content.Context);
 public <init>(android.content.Context, android.util.AttributeSet);
 public <init>(android.content.Context, android.util.AttributeSet, int);
 public void set*(***);
}

#保持 Serializable 不被混淆
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient <fields>;
    !private <fields>;
    !private <methods>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# 保持自定义控件类不被混淆
-keepclasseswithmembers class * {
    public <init>(android.content.Context,android.util.AttributeSet);
}
# 保持自定义控件类不被混淆
-keepclasseswithmembers class * {
    public <init>(android.content.Context,android.util.AttributeSet,int);
}
# 保持自定义控件类不被混淆
-keepclassmembers class * extends android.app.Activity {
    public void *(android.view.View);
}

# 保持枚举 enum 类不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# 保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

# 不混淆R文件中的所有静态字段,我们都知道R文件是通过字段来记录每个资源的id的,字段名要是被混淆了,id也就找不着了。
-keepclassmembers class **.R$* {
    public static <fields>;
}

#如果引用了v4或者v7包
-dontwarn android.support.**

# 保持哪些类不被混淆
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.preference.Preference

-keep class com.zhy.http.okhttp.**{*;}
-keep class com.wiwide.util.** {*;}

# ============忽略警告,否则打包可能会不成功=============
-ignorewarnings
4. Code optimization

To further shrink your application, R8 examines the code at a deeper level to remove more unused code or, where possible, rewrite the code to make it cleaner. Here are a few examples of such optimizations:

  • If your code never takes the else {} branch of a given if/else statement, R8 may remove the code for the else {} branch.
  • If your code calls a method in only one place, R8 may remove the method and inline it at that one call site.
  • If R8 determines that a class has only one unique subclass and that class itself is not instantiated (for example, an abstract base class used only by one concrete implementation class), it can combine the two classes and remove them from the application Remove a class.

For more optimization points, you can check out: Jake Wharton’s blog post about R8 optimization.

3 Summary of this article

The article mainly explains some optimization operations of the R8 compiler on apk code and resources during the entire compilation process, mainly focusing on code reduction, resource reduction, code obfuscation, and code optimization . Among them, a more comprehensive review of code obfuscation is given. analysis.

In order to help everyone understand performance optimization more comprehensively and clearly, we have prepared relevant core notes (including underlying logic):https://qr18.cn/FVlo89

Core notes on performance optimization:https://qr18.cn/FVlo89

Startup optimization

, memory optimization,

UI optimization,

network optimization,

Bitmap optimization and image compression optimization : multi-thread concurrency optimization and data transmission efficiency optimization, volume package optimizationhttps://qr18.cn/FVlo89




"Android Performance Monitoring Framework":https://qr18.cn/FVlo89

"Android Framework Learning Manual":https://qr18.cn/AQpN4J

  1. Boot Init process
  2. Start the Zygote process on boot
  3. Start the SystemServer process on boot
  4. Binder driver
  5. AMS startup process
  6. PMS startup process
  7. Launcher startup process
  8. Android four major components
  9. Android system service-Input event distribution process
  10. Android underlying rendering - screen refresh mechanism source code analysis
  11. Android source code analysis in practice

Guess you like

Origin blog.csdn.net/weixin_61845324/article/details/133274686