写这篇文章的目的

一直以来,在项目中需要进行代码混淆时每次都要去翻文档,很麻烦。也没有像写代码那样记得那么多。既然要查来查去,就不如自己捋一捋这个知识点了,被人写的终究还是别人的。所以自己去翻看了很多文章和官方文档,总结下就把这篇文章写下来了。以后方便查找和修改,也加深这个知识的理解。

前言

Android 开发中,打包避免不了各种优化,开启混淆可以很好就是其中一种优化方式。为了使你打包的 apk 尽可能小,应该在打包 apk 的时候开启代码压缩功能移除没有被使用的代码和资源。但是这和混淆有什么关系?

代码压缩混淆:ProGuard 可以从你打包的应用程序检测和删除未使用的类、字段、方法和属性,当然也会包括libraries里的,这样对64K引用限制很有帮助的。ProGuard 也会优化字节码,把类、字段、属性和方法用短的名称表示-混淆(官方文档上也说了还有删除未使用的代码指令,这个有时间再研究下了)。
资源压缩:(Resource shrinking) 配合 Gradle 使用,可以安全移除没有使用的打包时应用程序没有使用到的资源,包括代码库中的资源。

一、代码混淆压缩配置

那么混淆 (ProGuard) 改怎么用?

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

上面这段代码很眼熟啊,就是项目 build.gradle 文件里的一段代码,不过项目创建时默认 minifyEnabled 的值是 false,需要开发者手动改为 true,或者点击这样:

选中项目例如app-按F4-进入 Project Structure -右边选择 Build Type -选择 release -把Minify Enable 设置为true

看下两个分别打包出来的效果:自己感受下,可以看到前后apk大小和代码的变化

空项目混淆前后不比较(左为混淆)

如果留意 gradle 控制台的换,会有:proguard:xxxx的信息。代码就这么被混淆和压缩了,本来很多有意义的单词都被一些字母代替了。

每次build完之后,ProGuard 都会输出一些文件在项目模块 /build/outputs/mapping/release/下:

  • dump.txt :描述 APK 中所有类文件的内部结构
  • mapping.txt :提供原始和混淆后类、方法、字段名的映射对照
  • seeds.txt :列出未混淆的类和成员
  • usage.txt :列出从 APK 中移除的代码

关于mapping.txt使用的场景也是比较多的,比如使用bugly等平台可能需要上传mapping或者用Android Studio查看 apk 的 classes.dex 时,有需要的话可以使用Load ProGuard mappings...加载mapping.txt,这样可以看到混淆前的代码样子

注意:如果在debug模式下设置minifyEnabled true,而且需要使用Instant Run增量构建的时候,ProGuard只会删除不使用的代码,不会混淆代码。不过这个情况也是在debug下使用的吧,一般在debug模式下不开启混淆的,因为项目build的时间就很长了,再开个混淆那就更长了。如果你真的要这么debug的话:用useProguard false 即可。这样代码也会被混淆。
这个需求我暂时没有遇到过,只是在debug下有开启过混淆,但是没有用Instant Run
代码如下:

android {
buildTypes {
debug {
minifyEnabled true
useProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
...
}

二、移除没有被使用的资源

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

加了一句shrinkResources true。可是在用 Android studio 打开 apk 的时候你还是发现有那个文件的存在,不过是之前的那个文件被替换成了 1x1(67B) 大小的图片了。

三、自定义代码保留规则

并不是所有代码都需要被混淆的,一般默认的 ProGuardFile <sdk>\tools\proguard\proguard-android.txt下已经默认做好了一些设置,哪些些代码不能被混淆的,但是在实际开发中可能还不够,还需要开发者自定义一些混淆规则。在这里我有个小建议:如果需要添加第三方库到你的 app,那么同时顺便也找下这个库的混淆规则,并把它加入到你的项目里。混淆规则一般遵循以下几点:

  • AndroidManifest.xml 中使用到的类,比如四大组件
  • JNI 调用的方法
  • 运行时操作的代码,比如反射
  • 枚举....
自定义混淆的方法:

1. 使用注解 @Keep
以下代码的效果是类和成员都会保留。只想保留成员可以在成员上使用@keep

@Keep
public class User { private String userName; public String getUserName() {
return userName;
} }

哪些类、方法、属性、字段需要保留的,不被混淆的,用@Keep可以了,就是这么简单,这个几乎没怎么用。

2. 在 proguard-rules.pro 中定义
这个文件在项目的根目录下。
这个是用得比 @Keep 用都多,比如添加第三库的时候,一般会提供相应的混淆规则,这些规则需要加入到混淆文件中。

四、常用命令说明

  1. -keep : 保留指定的类和成员(字段、方法)。
  2. -keepclassmembers:保留指定的类成员,如果成员所在的类也保留下来的话。也就是说类没有保留,那么成员也不存在了或者说这个命令不能影响类。
  3. -keepclasseswithmembers:保留指定的类和成员,前提条件是所指定类中要有的指定的类成员。比如: User 类中没有 name,但是你指定保留 name,那么这个命令是不生效的。
  4. -keepnames-keep,allowshrinking 的简写,指定保留指定类和成员,如果代码压缩阶段类或成员没有被删除。
  5. -keepclassmembernames-keepclassmembers,allowshrinking 的简写,保留指定的成员,如果代码压缩阶段成员没有被删除。
  6. -keepclasseswithmembernames-keepclasseswithmembers,allowshrinking 的简写,保留指定类和成员,如果代码压缩后压缩没有被删除。比如:User 类中没有 name,但是你指定保留 name,那么这个命令是不生效的(User 该混淆混淆)。
  7. -dontwarn:指定不警告未解决的引用和其他重要问题。
  8. -keepattributes-keepattributes SourceFile,LineNumberTable-renamesourcefileattribute SourceFile 配合使用。分别是 保存用于调试堆栈跟踪的行号信息 和 隐藏原始的源文件名。在项目的 ProGuard 文件取消注释就可以用了。-keepattributes其他属性

说明:allowshrinking 表示允许被删除。类似的修饰符还有 其他的

保留项 不会被移除(压缩)、混淆(重命名) 会被移除(压缩)、混淆(重命名)
类、成员 -keep -keepnames
成员 -keepclassmembers -keepclassmembernames
类、成员(如果有指定成员) -keepclasseswithmembers -keepclasseswithmembernames

使用规则

[命令] [类规范]{
[成员];
}

注意:需要不需要保留成员,可以不用{[成员];}

  1. 【类规范】:限定条件,通过限定条件定位到符合条件的类。写起了和java语法差不多,不过可以带有通配符
[注解] [修饰符] 类定义符 类名 [extends | implements 类名]

中括号里的是可选项,分割线|提供选择,具体如下:

  • 注解:@xxx
  • 修饰符:public | private | final | abstract | ... ,前加 ! 表示非xxx
  • 类定义符:interface | class | enuminterface | enum 前加 ! 表示非interface | enum ,注解用class表示
  • 类名:全类名
  • extends | implements 类名:限定前面的类继承或实现某个类
  • $:内部类
  • 通配符:? - 任何单个字符;* - 不包括包分隔符的其他任何部分;** - 任何部分
  1. 【成员】:类成员的限定条件,通过限定条件匹配到符合条件的成员。写起了也是和java差不多,可以用通配符,每句结束有;
{
[@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 ... ] *;
...
} 简单的就是:
{
[注解] [修饰符] <fields> | 类型 字段名;
[注解] [修饰符] <methods> | 方法 | <init>(参数类型,...) | classname(参数类型,...) | 返回值类型 方法名(参数,...);
[注解] [修饰符] *;
...
}
  • 注解:@xxx
  • 修饰符:成员修饰符,前面加!表示非xxx
  • *:任何方法或字段
  • <init>:任何构造器,构造器也可以用类名或者全类名来指定
  • <fields>:任何字段
  • <methods>:任何方法
  • 字段名:字段名,可以和通配符使用
  • 方法名:方法名,可以和通配符使用
  • 类型:全类名,基本数据类型除外
  • 参数类型:全类名,基本数据类型除外
  • 返回值类型:全类名,基本数据类型除外
  • 通配符:%-任何基本数据类型;?-任何单个字符;*-不包括包分隔符的其他任何部分;**-任何部分;***-任何类型(数组等);...-任意长度任意类型参数

五、使用举例

  • 忽略某个内容的警告信息
-dontwarn javax.annotation.**
  • 不混淆某个类及成员
-keep public class com.xin.proguard.nonuse.Keep{
*;
}
  • 不混淆某个包下的所有类及成员
-keep public class com.xin.proguard.nonuse.**{
*;
}
  • 不混淆有类名有Bean的类及其成员
-keep class **Bean**{
*;
}
  • 不混淆某个接口的实现
-keep class * implements com.bumptech.glide.module.GlideModule
  • 不混淆某个类的子类
-keep class * extends com.bumptech.glide.module.AppGlideModule
  • 不混淆某个成员
-keepclassmembers class android.support.design.internal.BottomNavigationMenuView{
private boolean mShiftingMode;
}
  • 不混淆某个类的指定方法
-keepclassmembers class com.xin.proguard.nonuse.User{
public void setUserName(java.lang.String);
}
  • 移除Log打印
    先确认 build.gradle 配置如下,原因是配置 -dontoptimize 情况下无法移除log打印。
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
-assumenosideeffects class android.util.Log{
public static *** v(...);
public static *** i(...);
public static *** d(...);
public static *** w(...);
public static *** e(...);
}

大胆的建议:如果很明确某个包或者某个类改怎么处理的话,用-keepnames-keep好。

六、自定义资源保留规则

使用前请确定在项目 build.gradle 中配置了 shrinkResources true。自定义资源保留规则需要在项目的 xml 文件中定义:根节点 <resources>,需要保留资源用 tools:keep,需要移除资源用 tools:discard;两个资源之间可以用 , 分隔、* 可以用作通配符。

可能有人会问:如果我把同一个资源同时作为 tools:keeptools:discard 的值,这咋整?告诉你,这个资源会被移除。当然,谁会这么傻逼做这样的事呢,哈哈哈。不要问我是怎么知道的。说多都是泪,我就是那个傻逼

严格模式tools:shrinkMode="strict"tools:shrinkMode 默认值是safe,默认情况如果在代码中使用Resources.getIdentifier(),混淆器会把所有匹配的资源都标记为已使用的,且不可删除。

比如:下面的代码会导致所有以img_为前缀的资源都会被标记为“已使用”

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());

例如:在res/raw/下创建keep.xmlkeep.xml内容如下
在res/其它文件夹下创建其他名称的xml文件也行

<?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,@layout/unused3" />

如果开启了严格模式,而且也在代码中动态获取资源,那么就要使用tools:keep属性保留指定的资源。否则使用代码获取的资源不能正常使用。

项目构建的时候不会把 keep.xml 打包到 apk 里。不是 apk 里没有 keep.xml 文件,而是
keep.xml 里只剩个空壳。

删除可替代资源
资源压缩可以删除未使用的代码和资源,同样也可以删除一些可替代的资源。比如国际化中一些语言资源string.xmlvalues-xx,布局资源layout-xx等。当你不需要全部语言时,根据实际需要把指定的语言打包到apk里,那么资源压缩器就可以帮到你了。你可以在项目的gradle文件配置resConfig(resConfigs)属性,只保留显示声明的,未声明的将被删除。

例如以下代码展示只是用语言资源为中文:

android {
defaultConfig {
...
resConfig "zh"
}
}

如果是多个资源那就把 resConfig 改为 resConfigs 多了个 s 即可,当然,只声明一个资源时也可以用 resConfigs 。例如,只保留英语、法语、竖屏布局资源:

android {
defaultConfig {
...
resConfigs "en", "fr", "port"
}
}

屏幕密度资源配置请参考这里configure-split

关于 Android 混淆和资源压缩就到此结束了,很久没写东西了,写得不要请见谅,有不妥之处欢迎指出。感谢阅读。

(End)

参考链接

Android ProGuard:代码混淆压缩的更多相关文章

  1. Android ProGuard代码混淆技术详解

    前言     受<APP研发录>启发,里面讲到一名Android程序员,在工作一段时间后,会感觉到迷茫,想进阶的话接下去是看Android系统源码呢,还是每天继续做应用,毕竟每天都是画UI ...

  2. Android proguard代码混淆

    为什么要代码混淆? Android的安装文件是apk格式.APK是AndroidPackage的缩写.是由android sdk编译的工程打包生成的安装程序文件. Apk其实是zip文件,但是后缀名被 ...

  3. android ProGuard 代码混淆实现

    1 修改project.properties,添加ProGuard配置项 proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt: ...

  4. ProGuard代码混淆技术详解

    前言     受<APP研发录>启发,里面讲到一名Android程序员,在工作一段时间后,会感觉到迷茫,想进阶的话接下去是看Android系统源码呢,还是每天继续做应用,毕竟每天都是画UI ...

  5. 【Android Studio安装部署系列】十二、Android studio代码混淆

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 概述 为什么需要代码混淆呢?原因很简单,你的apk很容易被反编译出来,你写的代码都会被看到,因此我们需要在编译过程中对代码进行一定程度的混 ...

  6. Android Progurad 代码混淆

    ref: ProGuard基础语法和打包配置.mdhttps://github.com/D-clock/Doc/blob/master/Android/Gradle/3_ProGuard%E5%9F% ...

  7. Android Stuido代码混淆

    一.Android Studio 代码混淆基本配置首先我们要在build.gradle里设置 miifyEnabled 里改为true,表示可以混淆 proguardFiles getDefaultP ...

  8. Android Studio 代码混淆(你真的会混淆吗)

    一.前言 今天要打包新产品,突然忘了混淆的参数是怎么写的了,虽然之前也混淆过,可是具体配置的参数代码有些记不起来了,因此决定花点时间写篇博客记录一下,方便以后查找和自己的记忆. 二.Android S ...

  9. Android 4.0 ProGuard 代码混淆 以及 proguard returned with error code 1.See console异常的解决方法

    最近呢说要上线,就去找了下上线的方法...之前做过代码混淆,用的是progarud.cfg,但是呢自己反编译了之后还是无效,然后就丢着先不管了,因为实在不知道什么情况.今天来上线的时候结果总是报错,总 ...

随机推荐

  1. 爬虫中BeautifulSoup4解析器

    CSS 选择器:BeautifulSoup4 和 lxml 一样,Beautiful Soup 也是一个HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML 数据. lxml 只会 ...

  2. openresty开发系列37--nginx-lua-redis实现访问频率控制

    openresty开发系列37--nginx-lua-redis实现访问频率控制 一)需求背景 在高并发场景下为了防止某个访问ip访问的频率过高,有时候会需要控制用户的访问频次在openresty中, ...

  3. Tengine的说明

    什么是Tengine 官方帮助文档:http://tengine.taobao.org/changelog_cn.html

  4. 【超分辨率】- CVPR2019中SR论文导读与剖析

    CVPR2019超分领域出现多篇更接近于真实世界原理的低分辨率和高分辨率图像对应的新思路.具体来说,以前论文训练数据主要使用的是人为的bicubic下采样得到的,网络倾向于学习bicubic下采样的逆 ...

  5. Java获取执行进程的dump文件及获取Java stack

    转发自https://blog.csdn.net/MCC_MCC_MCC/article/details/80623156 1.Windows/Linux环境下查看Java进程ID方法 使用Java自 ...

  6. E: Unable to correct problems, you have held broken packages-之apt-get 下载报依赖问题

    今天在新来了一台ubutnu 18.04 在安装zabbix客户端是报依赖问题 root@VM_0_10:~# apt-get install zabbix-agent Reading package ...

  7. MySQL之表日志管理

    MySQL日志管理 mysql日志(默认存放在datadir): 同大多数关系型数据库一样,日志文件是MySQL数据库的重要组成部分.MySQL有几种不同的日志文件,通常包括错误日志文件,二进制日志, ...

  8. eclipse 查看文件在磁盘里的位置

  9. Javaspring+mybit+maven中实现Junit测试类

    在一个Javaspring+mybit+maven框架中,增加Junit测试类. 在测试类中遇到的一些问题,利用spring 框架时,里面已经有保密security+JWT设定的场合,在你的secur ...

  10. 探索免费开源服务器tomcat的魅力

    Tomcat最初是由Sun的软件架构师詹姆斯·邓肯·戴维森开发的.后来他帮助将其变为开源项目,并由Sun贡献给Apache软件基金会,并且成为Jakarta 项目中的一个核心项目.因此逐渐成为世界上广 ...