Android打包混淆压缩

来源:互联网 发布:煤中全水分的算法 编辑:程序博客网 时间:2024/05/16 10:23

声明

这篇文章,借鉴参考了下面的两篇文章,算是一个自己对混淆这块的总结。
写给Android开发者的混淆使用手册
Android混淆打包那些事儿

混淆

简介

说到混淆,就要说到proGuard,Android的混淆是有proGuard来完成的,ProGuard是一个开源项目在SourceForge上进行维护。

流程

这里写图片描述

代码混淆是包含了代码压缩、优化、混淆等一系列行为的过程。如上图所示,混淆过程会有如下几个功能:

  • 压缩。移除无效的类、类成员、方法、属性等;
  • 优化。分析和优化方法的二进制代码,移除无用指令;根据proguard-android-optimize.txt中的描述,优化可能会造成一些潜在风险,不能保证在所有版本的Dalvik上都正常运行。
  • 混淆。把类名、属性名、方法名替换为简短且无意义的名称;
  • 预校验。添加预校验信息。这个预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度。

这四个流程默认开启。

如何混淆

混淆配置

一般的,我们在build.gradle都会这样写:

buildTypes {        release {            zipAlignEnabled true            minifyEnabled true            // 移除无用的resource文件            shrinkResources true            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'            signingConfig signingConfigs.xxxxx        }    }
  1. minifyEnabled true设为true开启混淆,
  2. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'设置混淆文件的配置。其中proguard-android.txt是Android自带的混淆配置,我们可以在SDK的sdk\tools\proguard目录找到该文件,里边配置了一些基本需要的混淆。proguard-rules.pro是我们要添加的自己的混淆配置,比如项目中引用了第三方的项目等。

proguard-android.txt的内容:

# This is a configuration file for ProGuard.# http://proguard.sourceforge.net/index.html#manual/usage.html-dontusemixedcaseclassnames-dontskipnonpubliclibraryclasses-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).-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.-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-keepclasseswithmembernames class * {    native <methods>;}# keep setters in Views so that animations can still work.# see http://proguard.sourceforge.net/manual/examples.html#beans-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-keepclassmembers class * extends android.app.Activity {   public void *(android.view.View);}# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations-keepclassmembers enum * {    public static **[] values();    public static ** valueOf(java.lang.String);}-keepclassmembers class * implements android.os.Parcelable {  public static final android.os.Parcelable$Creator CREATOR;}-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.-dontwarn android.support.**# Understand the @Keep support annotation.-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>(...);}

一般的proguard-android.txt文件中已经有的混淆配置,我们可以不再添加,当然写了也没有问题!

查看混淆结果

混淆过的包必须进行检查,避免因混淆引入的bug。

一方面,需要从代码层面检查。

使用上文的配置进行混淆打包后在·app/build/outputs/mapping/release/目录下会输出以下文件:

  • dump.txt
    描述APK文件中所有类的内部结构
  • mapping.txt
    提供混淆前后类、方法、类成员等的对照表
  • seeds.txt
    列出没有被混淆的类和成员
  • usage.txt
    列出被移除的代码

我们可以根据 seeds.txt 文件检查未被混淆的类和成员中是否已包含所有期望保留的,再根据 usage.txt 文件查看是否有被误移除的代码。

另一方面,需要从测试方面检查

将混淆过的包进行全方面测试,检查是否有 bug 产生。这时候解出混淆栈

混淆后的类、方法名等等难以阅读,这固然会增加逆向工程的难度,但对追踪线上 crash 也造成了阻碍。我们拿到 crash 的堆栈信息后会发现很难定位,这时需要将混淆反解。

sdk/tools/proguard/路径下有附带的的反解工具(Window 系统为 proguardgui.bat,Mac 或 Linux 系统为 proguardgui.sh)。

这里以 Window 平台为例。双击运行proguardgui.bat后,可以看到左侧的一行菜单。点击 ReTrace,选择该混淆包对应的 mapping 文件(混淆后在 app/build/outputs/mapping/release/路径下会生成 mapping.txt文件,它的作用是提供混淆前后类、方法、类成员等的对照表),再将 crash 的 stack trace 黏贴进输入框中,点击右下角的 ReTrace ,混淆后的堆栈信息就显示出来了。

以上使用 GUI 程序进行操作,另一种方式是利用该路径下的 retrace 工具通过命令行进行反解,命令是

retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]

例如:

retrace.bat -verbose mapping.txt obfuscated_trace.txt

自定义的混淆配置

保持元素不被混淆的相关命令

  • -keep 防止类和成员被移除或者被重命名
  • -keepnames 防止类和成员被重命名
  • -keepclassmembers 防止成员被移除或者被重命名
  • -keepnames 防止成员被重命名
  • -keepclasseswithmembers 防止拥有该成员的类和成员被移除或者被重命名
  • -keepclasseswithmembernames 防止拥有该成员的类和成员被重命名

保持元素不被混淆的相关规则

规则形如:

[保持命令] [类] {    [成员] }

保持命令:就是指上边说的几个保持命令
:类相关的限定条件,它将最终定位到某些符合该限定条件的类。它的内容可以使用:

  • 具体的类
  • 访问修饰符(public、protected、private)
  • 通配符*,匹配任意长度字符,但不含包名分隔符(.)
  • 通配符**,匹配任意长度字符,并且包含包名分隔符(.)
  • extends,即可以指定类的基类
  • implement,匹配实现了某接口的类
  • $,内部类

成员:代表类成员相关的限定条件,它将最终定位到某些符合该限定条件的类成员。它的内容可以使用:

  • <init> 匹配所有构造器
  • <fields> 匹配所有域
  • <methods> 匹配所有方法
  • 通配符*,匹配任意长度字符,但不含包名分隔符(.)
  • 通配符**,匹配任意长度字符,并且包含包名分隔符(.)
  • 通配符***,匹配任意参数类型
  • ,匹配任意长度的任意类型参数。比如void test(…)就能匹配任意 void test(String a) 或者是 void test(int a, String b) 这些方法。
  • 访问修饰符(public、protected、private)

通用的混淆规则

混淆的时候,可以直接复制:

#不优化输入的类文件-dontoptimize-dontwarn android.support.**#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>(...);}# 代码混淆压缩比,在0~7之间,默认为5,一般不下需要修改-optimizationpasses 5# 混淆时不使用大小写混合,混淆后的类名为小写# windows下的同学还是加入这个选项吧(windows大小写不敏感)-dontusemixedcaseclassnames# 指定不去忽略非公共的库的类# 默认跳过,有些情况下编写的代码与类库中的类在同一个包下,并且持有包中内容的引用,此时就需要加入此条声明-dontskipnonpubliclibraryclasses# 指定不去忽略非公共的库的类的成员-dontskipnonpubliclibraryclassmembers# 不做预检验,preverify是proguard的四个步骤之一# Android不需要preverify,去掉这一步可以加快混淆速度-dontpreverify# 有了verbose这句话,混淆后就会生成映射文件# 包含有类名->混淆后类名的映射关系# 然后使用printmapping指定映射文件的名称-verbose-printmapping priguardMapping.txt# 指定混淆时采用的算法,后面的参数是一个过滤器# 这个过滤器是谷歌推荐的算法,一般不改变-optimizations !code/simplification/artithmetic,!field/*,!class/merging/*# 保护代码中的Annotation不被混淆# 这在JSON实体映射时非常重要,比如fastJson-keepattributes *Annotation*# 避免混淆泛型# 这在JSON实体映射时非常重要,比如fastJson-keepattributes Signature#将文件来源重命名为“SourceFile”字符串-renamesourcefileattribute SourceFile# 抛出异常时保留代码行号-keepattributes SourceFile,LineNumberTable# 保留所有的本地native方法不被混淆-keepclasseswithmembernames class * {    native <methods>;}#把混淆类中的方法名也混淆了-useuniqueclassmembernames#优化时允许访问并修改有修饰符的类和类的成员-allowaccessmodification# 保留了继承自Activity、Application这些类的子类# 因为这些子类有可能被外部调用# 比如第一行就保证了所有Activity的子类不要被混淆-keep public class * extends android.app.Activity-keep public class * extends android.app.Application-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.app.backup.BackupAgentHelper-keep public class * extends android.preference.Preference-keep public class * extends android.view.View-keep public class com.android.vending.licensing.ILicensingService#Fragment不需要在AndroidManifest.xml中注册,需要额外保护下-keep public class * extends android.support.v4.app.Fragment-keep public class * extends android.app.Fragment# 保持测试相关的代码-dontnote junit.framework.**-dontnote junit.runner.**-dontwarn android.test.**-dontwarn android.support.test.**-dontwarn org.junit.**# 保留Activity中的方法参数是view的方法,# 从而我们在layout里面编写onClick就不会影响-keepclassmembers class * extends android.app.Activity {    public void * (android.view.View);}# 枚举类不能被混淆-keepclassmembers enum * {    public static **[] values();    public static ** valueOf(java.lang.String);}# 保留自定义控件(继承自View)不能被混淆-keep 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*(***);    *** get* ();}# 保留Parcelable序列化的类不能被混淆-keep class * implements android.os.Parcelable{    public static final android.os.Parcelable$Creator *;}# 保留Serializable 序列化的类不被混淆-keepclassmembers class * implements java.io.Serializable {   static final long serialVersionUID;   private static final java.io.ObjectStreamField[] serialPersistentFields;   !static !transient <fields>;   private void writeObject(java.io.ObjectOutputStream);   private void readObject(java.io.ObjectInputStream);   java.lang.Object writeReplace();   java.lang.Object readResolve();}# 对R文件下的所有类及其方法,都不能被混淆-keepclassmembers class **.R$* {    *;}# 对于带有回调函数onXXEvent的,不能混淆-keepclassmembers class * {    void *(**On*Event);}#实体类不能被混淆-keep class com.test.beans.** {*;}#忽略get和set方法-keep class com.test.beans.** {    public void set*(***);    public *** get*();    public *** is*();}#以上两种任意一种都行#对于内部类,$就是用来分割内嵌类和母体的标志#-keep class com.test.**$*{*;}#保留support下的所有类及其内部类-keep class android.support.** {*;}#不需要提示 警告-dontwarn android.support.**# support-v7-appcompat-keep public class android.support.v7.widget.** { *; }-keep public class android.support.v7.internal.widget.** { *; }-keep public class android.support.v7.internal.view.menu.** { *; }-keep public class * extends android.support.v4.view.ActionProvider {    public <init>(android.content.Context);}# support-design-dontwarn android.support.design.**-keep class android.support.design.** { *; }-keep interface android.support.design.** { *; }-keep public class android.support.design.R$* { *; }

其他混淆规则

OkHttp3

-dontwarn okhttp3.logging.**-keep class okhttp3.internal.**{*;}-dontwarn okio.**

Retrofit2.x

-dontwarn retrofit2.**-keep class retrofit2.** { *; }

RxJava RxAndroid

-dontwarn sun.misc.**-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {    long producerIndex;    long consumerIndex;}-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {    rx.internal.util.atomic.LinkedQueueNode producerNode;}-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {    rx.internal.util.atomic.LinkedQueueNode consumerNode;}

Gson

-keep class com.google.gson.** { *; }-keepattributes EnclosingMethod

Glide

-keep public class * implements com.bumptech.glide.module.GlideModule-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {  **[] $VALUES;  public *;}# for DexGuard only-keepresourcexmlelements manifest/application/meta-data@value=GlideModule

ButterKnife

-keep class butterknife.** { *; }-dontwarn butterknife.internal.**-keep class **$$ViewBinder { *; }-keepclasseswithmembernames class * {    @butterknife.* <fields>;}-keepclasseswithmembernames class * {    @butterknife.* <methods>;}

资源压缩

资源压缩将移除项目及依赖的库中未被使用的资源,这在减少 apk 包体积上会有不错的效果,一般在打realease包的时候建议开启。
具体做法是在 build.grade 文件中,将shrinkResources属性设置为 true。需要注意的是,只有在用minifyEnabled true开启了代码压缩后,资源压缩才会生效。

资源压缩包含了“合并资源”和“移除资源”两个流程。

合并资源

“合并资源”流程中,名称相同的资源被视为重复资源会被合并。需要注意的是,这一流程不受shrinkResources属性控制,也无法被禁止, gradle 必然会做这项工作,因为假如不同项目中存在相同名称的资源将导致错误。gradle 在四处地方寻找重复资源:
①src/main/res/ 路径
②不同的构建类型(debug、release等等)
③不同的构建渠道
④项目依赖的第三方库

合并资源时按照如下优先级顺序:
依赖 -> main -> 渠道 -> 构建类型
举个例子,
假如重复资源同时存在于main文件夹和不同渠道中,gradle 会选择保留渠道中的资源。同时,如果重复资源在同一层次出现,比如src/main/res/src/main/res2/,则 gradle 无法完成资源合并,这时会报资源合并错误。

移除资源

资源移除的时候,跟代码混淆一样,也可以定义哪些资源需要被保留

保持某些资源不被移除

shrinkResources true开启资源压缩后,所有未被使用的资源默认被移除。假如你需要定义哪些资源必须被保留,在 res/raw/ 路径下创建一个 xml 文件,例如 keep.xml。

通过一些属性的设置可以实现定义资源保持的需求,可配置的属性有:

  • tools:keep 定义哪些资源需要被保留(资源之间用“,”隔开)
  • tools:discard 定义哪些资源需要被移除(资源之间用“,”隔开)
  • tools:shrinkMode 开启严格模式

当代码中通过 Resources.getIdentifier() 用动态的字符串来获取并使用资源时,普通的资源引用检查就可能会有问题。例如,如下代码会导致所有以“img_”开头的资源都被标记为已使用。

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

我们可以设置 tools:shrinkMode为 strict 来开启严格模式,使只有确实被使用的资源被保留。

以上就是自定义资源保持规则相关的配置,举个例子:

<?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"    tools:shrinkMode="strict"/>

移除替代资源

一些替代资源,例如多语言支持的 strings.xml,多分辨率支持的 layout.xml等,在我们不需要使用又不想删除掉时,可以使用资源压缩将它们移除。

我们使用 resConfig 属性来指定需要支持的属性,例如

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

其他未显式声明的语言资源将被移除。

好了,Android混淆相关就下总结到这里!
淘到一个在线的混淆的网站,可以参考一下!
https://proguard.herokuapp.com/

2 0
原创粉丝点击