Android代码混淆——Proguard

一、概述

ProGuard 会检测和移除封装应用中未使用的类、字段、方法和属性,包括自带代码库中的未使用项(这使其成为以变通方式解决64k 引用限制的有用工具)。ProGuard 还可优化字节码,移除未使用的代码指令,以及用短名称混淆其余的类、字段和方法。混淆过的代码可令您的 APK 难以被逆向工程,这在应用使用许可验证等安全敏感性功能时特别有用。

二、开启混淆

在module的build.gradle文件中添加minifyEnabled true。由于代码混淆会导致构建速度变慢,所有不要在调试的构建中使用。

1
2
3
4
5
6
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
  • proguard-android.txt :是系统默认的混淆规则配置文件,位置在 <Android SDK目录>/tools/proguard文件夹下。如果想进一步的压缩代码,可以使用位于相同目录下的proguard-android-optimize.txt文件,它还包含其他在字节码一级(方法内和方法间)执行分析的优化,以进一步减小APK大小和帮助提供运行速度。
  • prguard-rules.pro在每个module中都有一个该文件,用于自定义对应module的ProGuard规则。

三、混淆规则

从系统的proguard-android.txt文件来学习混淆规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# This file is no longer maintained and is not used by new (2.2+) versions of the
# Android plugin for Gradle. Instead, the Android plugin for Gradle generates the
# default rules at build time and stores them in the build directory.
# SDK中的该文件已经不再维护,从Gradle2.2+版本后,gradle插件会自动生成这个默认配置文件,位置位于<项目根目录>/build/intermediates/proguard-files

# 混淆时不使用大小写混合,混淆后的类名为小写
-dontusemixedcaseclassnames

# 不跳过非公共的库的类
-dontskipnonpubliclibraryclasses

# 混淆后生成映射文件,map 类名->转化后类名的映射
-verbose

# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
# 优化默认关闭,Dex不喜欢通过ProGuard的优化和预处理操作
-dontoptimize
-dontpreverify

# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.

# 保护代码中的Annotation不被混淆,这在JSON实体映射非常重要,如GSON
-keepattributes *Annotation*
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
# 保留所有的地方native方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}

# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
# 不混淆View中的setXxx()和getXxx()方法,以保证熟悉动画能正常工作
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}

# We want to keep methods in Activity that could be used in the XML attribute onClick
# 不混淆Activity中参数是View的方法,保证xml绑定的点击事件可以正常工作
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
# 不混淆枚举类中的value()和valueOf()方法
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}

# 不混淆Parcelable实现类中的CREATOR字段,以保证Parcelable机制正常工作
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}

# 不混淆R文件中的所有静态字段,以保证正确找到每个资源id
-keepclassmembers class **.R$* {
public static <fields>;
}

# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version. We know about them, and they are safe.
# 不对android.support包下的代码警告。(如果打包的版本低于support包下某些类的使用版本,会出现警告)
-dontwarn android.support.**

# Understand the @Keep support annotation.
# 不混淆Keep类
-keep class android.support.annotation.Keep

# 不混淆使用了注解的类和类成员
-keep @android.support.annotation.Keep class * {*;}

# 如果类中有使用了注解的方法,则不混淆类和类成员
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}

# 如果类中有使用了注解的字段,则不混淆类和类成员
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}

# 如果类中有使用了注解的构造函数,则不混淆类和类成员
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}

keep类关键字

关键字 含义
keep 保留类和类成员,防止被混淆或移除
keepnames 保留类和类成员,防止被混淆,但是没被引用的类成员会被移除
keepclassmembers 只保留类成员,防止被混淆或移除
keepclassmembersnames 只保留类成员,防止被混淆,但没被引用的类成员会被移除
keepclasseswithmembers 保留类和类成员,防止被混淆或移除,如果指定的类成员不存在还是会被混淆
keepclasseswithmembernames 保留类和类成员,防止被混淆,如果指定的类成员不存在还是会被混淆,没有被引用的类成员会被移除

通配符

通配符 含义
* 匹配任意长度字符,但不包含分隔符 “.”。例如一个类com.github.xch168.User,使用com.github.xch168. 是可以匹配,但是com.github. 不能匹配
** 匹配任意长度字符,包括分隔符”.”。如使用com.github.**,匹配包下的所有内容,可以匹配到com.github.xch168.User。
*** 匹配任意参数类型。例如*** getName(***) 可匹配String getName(String)
匹配任意长度的任意类型参数。例如void setName(…) 可以匹配void setName(String firstName, String secondName)
<fileds> 匹配类、接口中所有字段
<methods> 匹配类、接口中所有方法
<init> 匹配类中所有构造函数

四、配置自己的混淆

  • WebView中使用JS调用,需要添加如下配置:
1
2
3
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
  • 不混淆某个特定的类和类中的所有成员
1
-keep class com.github.xch168.utils.CommonUtil { *; }
  • 不混淆膜拜目录下的文件,例如使用Gson时,数据bean不能被混淆,需要添加如下配置:
1
-keep class com.github.xch168.model.** { *; }
  • 保留泛型
1
-keepattributes Signature
  • 保留用于调试堆栈跟中的行号信息
1
-keepattributes SourceFile,LineNumberTable
  • 如果使用了上一行配置,还需要添加如下配置将源文件重命名为SourceFile,以便通过鼠标点击直达源文件:
1
-renamesourcefileattribute SourceFile
  • 第三方库混淆案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Retrofit
# Retain generic type information for use by reflection by converters and adapters.
-keepattributes Signature
# Retain service method parameters.
-keepclassmembernames,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}
# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement

# Okhttp
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-dontwarn org.conscrypt.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase

五、查看混淆结果

混淆打包后就会在<module目录>/build/outputs/mapping/release目录生成混淆的相关文件。

  • dump.txt

    说明 APK 中所有类文件的内部结构。

  • mapping.txt

    提供原始与混淆过的类、方法和字段名称之间的转换。

  • seeds.txt

    列出未进行混淆的类和成员。

  • usage.txt

    列出从 APK 移除的代码。

混淆后可以用Android Studio的Analyze APK工具对混淆后的apk包进行分析。

apkanalyze

六、追溯Crash堆栈信息

混淆后的代码运行出错的堆栈信息如下,看不到具体的类名

1
2
3
4
5
6
7
8
9
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
at com.github.xch168.testas32.MainActivity.k(Unknown Source:7)
at com.github.xch168.testas32.MainActivity.onCreate(Unknown Source:9)
at android.app.Activity.performCreate(Activity.java:7130)
at android.app.Activity.performCreate(Activity.java:7121)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1262)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2905)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3060
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)

为了解决这个问题,可以使用<SDK目录>\tools\proguard\bin下的proguardgui.bat脚本将Crash堆栈信息还原到混淆前的状态。步骤如下:

  1. 运行proguardgui.bat脚本,然后点击ReTrace
  2. 选择mapping.txt文件,位于<module目录>/build/outputs/mapping/release
  3. 拷贝混淆后出错的堆栈信息
  4. 点击右下角的ReTrace!按钮,完成Crash堆栈信息的追溯

retrace

七、压缩资源

资源压缩只与代码压缩协同工作。代码压缩器移除所有未使用的代码后,资源压缩器便可确定应用仍然使用的资源。这在您添加包含资源的代码库时体现得尤为明显 - 您必须移除未使用的库代码,使库资源变为未引用资源,才能通过资源压缩器将它们移除。

开启资源压缩:

build.gradle 文件中将 shrinkResources 属性设置为 true(在用于代码压缩的 minifyEnabled 旁边)

1
2
3
4
5
6
7
8
9
10
11
android {
...
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}

八、自定义要保留的资源

如果您有想要保留或舍弃的特定资源,请在您的项目中创建一个包含 <resources> 标记的 XML 文件,并在 tools:keep 属性中指定每个要保留的资源,在 tools:discard 属性中指定每个要舍弃的资源。这两个属性都接受逗号分隔的资源名称列表。您可以使用星号字符作为通配符。

1
2
3
4
<?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" />

将该文件保存在项目资源中,例如,保存在 res/raw/keep.xml。构建不会将该文件打包到 APK 之中。

九、总结

混淆后可以减小APK的大小,可以提高被反编译的难度,但是混淆后需要进行系统全面的测试。

参考链接

  1. 压缩代码和资源
  2. Android 混淆查缺补漏
文章目录
  1. 1. 一、概述
  2. 2. 二、开启混淆
  3. 3. 三、混淆规则
    1. 3.1. keep类关键字
    2. 3.2. 通配符
  4. 4. 四、配置自己的混淆
  5. 5. 五、查看混淆结果
  6. 6. 六、追溯Crash堆栈信息
  7. 7. 七、压缩资源
  8. 8. 八、自定义要保留的资源
  9. 9. 九、总结
  10. 10. 参考链接
,