【Android】ProGuard的使用

Merit

  • smaller APK size (20% ~ 40%)
  • more difficult to reverse engineer

Demerit

  • unable to get into step-by-step debug mode
  • risk of making reflection failures
  • risk of introduce unexpected bugs
  • may loss bug tracking of obfuscated code
  • may break down the Smart App Updates
  • more cost on rules maintaince and fully test

Discussion on Smart App Updates:

Why are the updates still in MBs?! Understandable for major updates, but I'm hoping when an app has only bug fixes, it would be a very, very small update.

It's because of the way code gets compiled. When devs compile their Java code into an APK, they also use a tool called "Proguard," which is used to optimize and obfuscate their code. It's what makes it harder for other people to just decompile apps and steal work, and also slims down APKs and makes them run slightly faster. Unfortunately, Proguard obfuscates code differently depending on what changes you've made. While a developer might only change a line, the obfuscated and optimized version might actually have changed quite a bit.

see:

ProGuard配置

常规启用

导入 ProGuard 规则文件:

build.gradle: buildTypes {
    release {
        minifyEnabled true
        shrinkResources true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

minifyEnabled true 表示使用 ProGuard 进行混淆

shrinkResources true 表示精简未使用的资源文件(不包括 values),基于代码精简化后的结果进行引用判断(包括动态计算获取资源ID等隐式资源调用)

getDefaultProguardFile 表示在以下 SDK 位置读取指定的默认配置:

Android SDK -> Android SDK Location /tools/proguard/

该配置包含绝大部分默认必备选项,并自动移除未使用代码,但涉及以下情况的代码,需要通过 -keep 配置或 @Keep 注解,手动声明进行保留:

  • 仅通过 AndroidManifest.xml 引用的类
  • 通过 JNI 调用的方法
  • 通过反射等方式,在运行时动态操作代码

ProGuard 会自动重命名类、方法、字段等,通过反射等方式,在运行时动态引用类型、字段、资源时需要特别注意,根据需要手动声明保留:

  • 在 GSON 等框架中进行自动类型转换的 Model 类(保留字段名,或使用 @SerializedName 显式绑定字段注入)
  • 在 XML 中引用的第三方View类(可能触发 InflateException)
  • 在 JNI 中反调 Java 的方法函数
  • 在 JS 等外部代码中回调的 Java 方法函数
  • Serializable 实现类的相关序列化方法名
  • 需要静态配置文件控制的 Bean 类
  • Database drivers

proguard-android.txt 内置实现以下部分的处理:

  • native方法、枚举方法、注解
  • R类、Parcelable、动画XML对View的getter/setter、@Keep、support库

每次编译时,ProGuard 会输出以下文件:

  • dump.txt:描述 APK 内所有 class 文件的内部结构
  • mapping.txt:原始代码与混淆代码的名称映射表,可用于 Google Play 的反混淆处理
  • seeds.txt:列出未被混淆的所有类和成员
  • usage.txt:列出在 APK 中被移除的代码

※ 以上文件存储到该目录:
<module-name>/build/outputs/mapping/release/

注意

在使用 Instant Run 时,ProGuard 会被自动临时禁用。(Android Studio 2.0 & minSdkVersion >= 21)

productFlavors 与 buildTypes 中指定的 proguardFiles、proguardFile 会同时叠加生效,并且 buildTypes > productFlavors

build.gradle 配置了 consumerProguardFiles 项的第三方库,其引用的规则文件会自动引入到当前项目。

如果一个包中有需要不混淆的内容,则整个包名都不会被混淆。

proguard-android.txt 使用了 dontoptimize,优化不启用,-assumenosideeffects 不会生效。可使用 proguard-android-optimize.txt 替代。

移除重复资源

Gradle 方面会强制自动合并重复的资源项(同名+同类型+同限定属性),不受 ProGuard 的配置影响。

重复资源项按序扫描以下目录,并选择保留最后扫描到的重复项:

  1. 项目库依赖目录
  2. 当前资源主目录,通常指向为:src/main/res/
  3. Build flavor 配置目录
  4. Build type 配置目录

合并重复资源时,Gradle 会生成 resources.txt 文件,列出资源的依赖关系,及被移除的资源列表。
该文件在:<module-name>/build/outputs/mapping/release/ 目录中

常用规则

-keepattributes Signature 保留泛型类型信息
-keepattributes SourceFile,LineNumberTable 保留文件名、行号,用于崩溃反馈
-keep class com.xxx.** { *; } 保留第三方类库源码

移除Log代码 需要启用Optimization(不使用 -dontoptimize)
-assumenosideeffects class android.util.Log {
     public static *** d(...);
}
参考:

ProGuard 过程

Shrink -> Optimize -> Obfuscate -> Preverify

保留规则

以下规则不对类成员执行 Shrink 自动移除

  • -keep:完整保留指定类及指定成员(不论是否持有该成员)
  • -keepclassmembers:保留指定类的指定成员(类名依然接受混淆)
  • -keepclasseswithmembers:精确指定,仅对拥有指定的成员的类,进行完整保留

以下规则在 Shrink 过程执行完毕后生效

  • -keepnames:对剩余的指定成员及其宿主类进行命名保留
  • -keepclassmembernames:对指定类的剩余指定成员进行命名保留(类名依然接受混淆)
  • -keepclasseswithmembernames:精确指定,对仍然持有指定的成员的类,保留该类及其剩余所有成员的命名

可选的附件条件

  • allowshrinking:可执行 Shrink 过程,例如 -keepnames = -keep,allowshrinking
  • allowoptimization:可执行 Optimize 过程,默认被保留的对象不执行优化(优化过程可能会裁减掉该对象),仅用于实现极端需求
  • allowobfuscation:可执行 Obfuscate 过程,默认被保留的对象不执行混淆(直接导致 -keepnames 类保留变得没有意义),仅用于实现极端需求

-printseeds [filename]:打印被上述 keep 规则匹配保留的类及类成员,或输出到指定文件

※1 仅指定类,不指定成员时,仅对类名本身生效
※2 指定的类成员,仅对其名称生效,不影响其相关逻辑代码的后续优化、适配过程

类、成员指定条件

# 指定类型(必选)
[@annotationtype] [[!]public|final|abstract|@ ...] [!]interface|class|enum classname
    [extends|implements [@annotationtype] classname]

# 指定成员(可选)
[{
    [@annotationtype] [[!]public|private|protected|static|volatile|transient ...] <fields> | (fieldtype fieldname);

    [@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] <methods> | <init>(argumenttype,...) | classname(argumenttype,...) | (returntype methodname(argumenttype,...));
    
    [@annotationtype] [[!]public|private|protected|static ... ] *;
    
    ...
}]
通用指定
  • 可选指定是否包含特定注解标记
  • 可选使用“非”限定符 ! 进行反向指定
  • 可使用任意限定修饰符,并可组合指定,对于存在冲突的组合,则表示匹配对象必须符合其中任意一个指定
指定类型(必选)
  • 可指定接口、类、枚举类型(指定内部类时,按 Java 语法使用 $ 进行定位)
  • 可指定该类型派生、实现的上级类型
  • 类型名必须以全限定名形式标记,如 java.lang.String
  • 类型名可通过逗号 , 列表方式并列指定,但不符合 Java 声明范式,需要有节制的使用
  • extendsimplements 仅在语义上有区别,使用上作用一致
类型名通配符
  • ? 匹配任意单个字符,不包括包名分隔符 . ,如 mypackage.Test?
  • * 匹配任意长度字符,不包括包名分隔符 . ,如 mypackage.*Test*
  • ** 匹配任意长度字符,可包括包名及其分隔符 . ,如 **.Test

※ 单以 * 标记类型名,表示匹配所有类型名,不考虑包地址

指定成员(可选)
  • 可指定任意数量成员,每个指定规则末尾使用分号 ; 标记
  • 方法成员参数列表仅需列出参数类型
  • 字段和方法成员名,可使用正则表达式标记
  • 构造函数可通过短类名或全限定类名匹配指定
成员名通配符
  • <init> 匹配构造函数,可指定参数列表
  • <fields> 匹配任意字段成员
  • <methods> 匹配任意方法成员,不可指定参数列表
  • ? 匹配任意单个字符
  • * 匹配任意字段或方法成员,或任意长度字符
成员类型描述语句中的类型通配符
  • % 表示任意基本类型
  • ? 匹配任意单个字符,不匹配基本类型
  • * 匹配任意长度类型名,不匹配基本类型,不包括包名分隔符 .
  • ** 匹配任意长度类型名,不匹配基本类型,可包括包名及其分隔符 .
  • *** 匹配任意类型,包括基本类型、引用类型、数组和非数组
  • ... 匹配任意数量的方法参数

※1 基本类型可用 %*** 匹配
※2 数组类型仅可使用 *** 匹配

参考:ProGuard Usage

DexGuard

ProGuard 的进阶版替换方案,除基本代码混淆优化外,更提供了算式混淆、控制流混淆、本地代码混淆、反射回调、类型加密、本地库加密、字符串加密、资源混淆加密、证书检查、debug和模拟器检测等高阶保护过程,可有效阻止apktool完整反编译过程,收费,至少350欧。

参考:DexGuard Features

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容