Android ProGuard技术详解

目录

  • 目录
  • ProGuard概述
  • ProGuard混淆规则
    • 常用语法
    • 通配符
    • 常用规则
  • ProGuard优化指令
  • 还原混淆栈
  • Android Studio开启Proguard
  • Androidmk中开启ProGuard

ProGuard概述

ProGuard工具通过移除无用的代码以及使用语义隐晦的名称来重命名类、变量和方法,从而达到压缩、优化和混淆代码的目的.由于ProGuard会使应用更难于被反向工程,因此当应用对安全性有较高的要求时,必须使用此工具.

ProGruad提供以下4个功能:

  1. 压缩(Shrink): 侦测并移除代码中无用的类、字段、方法和属性.
  2. 优化(Optimize): 对字节码进行优化,移除无用的指令.
  3. 混淆(Obfuscate): 使用a,b,c,d这样简短而无意义的名称,对类,字段和方法进行重命名.
  4. 预校验(preverify): 在Java平台上对处理后的代码进行校验.

ProGuard的学习过程曲线还是有一点曲折,建议大家耐心的看完我的博客,肯定会对ProGuard机制有所收获.

ProGuard混淆规则

ProGuard之所以很强大,很大程度在于它的配置选项够多,各种组合可以产生不同的效果.
作为Android开发者,需要心里明确,ProGuard是没有一个最佳通用配置文件的.一般情况下,我们需要自己制定一些规则,然后逐步调试以达到最佳的效果.

ProGuard的混淆规则是利用“白名单”机制,通过“白名单”的配置,让ProGuard不去碰白名单中列出的类、变量或者方法.

常用语法

ProGuard的常用语法如下:

-keep [modifier…] class_specification: 不混淆指定的类(仅仅是只不混淆类的名字).
-keepclassmembers [modifier,,,] class_specification: 不混淆指定类的特定成员.
-keepclasseswithmembernames [modifier,,,] class_specification: 不混淆指定的类和它的成员.
-dontwarn [class_filter]: 不提示指定包名的混淆打包warning.
-keepattributes [attribute_filter]: 不混淆特定的属性.
-assumenosideeffects class_specification: 指定如果某些方法没有其他的作用,去掉不产生任何影响,则ProGuard可以在代码优化的过程中将其删除掉.通常用来删除Log日志打印语句.

通配符

在介绍ProGuard的常用规则之前,需要先介绍一下ProGuard中可能使用到的通配符,和正则表达式中元字符的使用有所不同,我们从一个具体的栗子入手:

-keep class android.** {*;}

我估计很多人都能理解{*;}部分的含义: 表示这个类的所有成员和方法.
但是两个**又代表什么呢?为什么不能用一个**代替呢?

要解释这个问题,就需要列举ProGuard中使用的通配符和其产生的作用:

  • ? : 能匹配任意一个字符,但是不能表示package的分隔符,即..
  • * : 能匹配任意n个字符,但是不能表示package的分隔符,即.
  • ** : 能匹配任意n个字符,并且可以表示package的分隔符.
  • % : 能匹配任何原始类型,如boolean、int等,但是不能表示void.
  • *** : 能匹配任意类型,包括原始类型和非原始类型,数组类型和非数组类型.
  • … : 能匹配任何数量的任何参数.

有了上面通配符规则介绍,我们就能知道,为了能够匹配到任意以android.开头的package,所以才使用了**.

常用规则

针对Android程序,ProGuard还是有一些常用规则来遵守的,具体如下.

Android framework class都是需要保留的,因为这些子类都有可能被外部调用.参考规则如下:

# keep framework class
-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.preference.Preference
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.app.backup.BackupAgent

保留JNI类的native方法:

-keepclasseswithmembernames class * {     native <methods>;
}

保留自定义控件(继承自View)不被混淆:

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

保留在Activity中的方法参数是View的方法,从而我们在layout里面编写onClick时就不会被影响

-keepclassmembers class * extends android.app.Activity {
    public void *(android.view.View);
}

保留枚举类的values和valueOf方法.因为java通过反射来调用它们,所以如果被混淆,会导致ClassNotFoundException.

-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

保留Parcelable对象的static CREATOR域,这是Android用来反序列化对象的.由于CREATOR是在运行时被调用,所以如果不加规则,ProGuard会把它当成无用的成员直接去掉.

# keep Parcelable CREATOR members
-keepclassmembers class * implements android.os.Parcelable {
    static android.os.Parcelable$Creator CREATOR;
}

不混淆实现Serializable接口的类.尽管Android推荐我们使用实现Parcelable接口来序列化类,但是难免代码中还可以通过实现Serializable接口来序列化.为了防止能够正常序列化和反序列化,需要加入如下规则:

-keepnames class * implements java.io.Serializable -keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

代码中使用了反射和注解需要增加如下规则:

-keepattributes Signature -keepattributes *Annotation* -keepattributes EnclosingMethod

反射用到的类不能混淆.在使用Gson等框架时,所写的JSON对象是不能被混淆的,否则无法将JSON解析成对应的对象.为了避免出现无法解析的情况,可以直接不混淆JSON对象所在的package.示例如下:

-keep class com.android.bean.** {*;}

其中,com.android.bean是我用来存放Gson对象的类所在的包名,大家要根据自己代码情况进行替换.

对于R(资源)下的所有类及其方法,都不能被混淆:

-keep class **.R$* {     *;
}

不提示兼容库的错误警告.Android代码中一般都会导入android.support.* jar包,为了防止影响ProGuard混淆,可以去除这些jar包的警告.示例如下:

# 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.**

参考链接: stack overflow Proguard configuration for Android Support v4 22.2.0

在发布版本,可以使用assumenosideeffects命令去掉应用中的打印语句:

-assumenosideeffects class android.util.Log {
    public static *** v(...);
    public static *** i(...);
    public static *** d(...);
    public static *** w(...);
    public static *** e(...);
}

ProGuard优化指令

常用的优化选项如下:

# 代码混淆压缩比,在0~7之间,默认是5
-optimizationpasses 5 
# 混淆时不使用大小写混合,混淆后的类名为小写
-dontusemixedcaseclassnames 
# 混淆时不去忽略非public的类
-dontskipnonpubliclibraryclasses 
# 混淆时不去忽略非public类的成员
-dontskipnonpubliclibraryclassmembers 
# 不做预校验,Android是不需要预校验的
-dontpreverify 
# 有了verbose,混淆后就会生成类名->混淆后类名的映射文件.使用printmapping指定映射文件的名称.
-verbose -printmapping mapping.txt 
# 指定混淆使用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable

接下来,分别解释一下上述几个优化选项的具体作用:

-optimizationpasses 5

ProGuard有一个优化代码(optimize)的过程,并且优化过程是可以“递归的”,也就是说本次优化完的结果可以作为下次优化的输入从而进行继续优化.指定optimizationpasses表示优化最多递归的次数为5次,通常5次就够了.

-dontusemixedcaseclassnames

指定在代码混淆的过程中不生成大小写混合的类名.默认情况下,混淆的类名是可以包含大小写字母的.但是,如果在一个不区分大小写的操作系统平台上(例如Window),使用大小写混淆的混淆类名可能导致不同的class相互覆盖,从而导致crash.

-dontskipnonpubliclibraryclasses

指定不忽略非public的类.因为ProGuard默认不混淆任何非public的class,但是有时候我们会遇到public的class继承自内部非public的class,所以打开这个选项可以让我们的代码更不容易被破解.

-dontskipnonpubliclibraryclassmembers

指定不去忽略非public的类的成员.

-dontpreverify

不做预校验,preverify是proguard的4个步骤之一.Android是不需要预校验的,去掉这一步可加快混淆速度.

-verbose

有了verbose,混淆后就会生成映射文件,包含有类名->混淆后类名的映射关系.

-optimizations !code/simplification/arithmetic,!field/,!class/merging/

指定混淆时采用的算法,后面的参数是一个过滤器.这个过滤器是谷歌推荐的算法,一般不需要改变.

还原混淆栈

retrace可以帮助我们来理解ProGuard混淆之后的函数调用栈信息.在Ubuntu系统中,retrace存在于Android SDK目录下的tools/proguard/bin目录.使用方法如下:

./retrace.sh [-verbose] <mapping_file> [<stacktrace_file>]

其中,mapping_file为ProGuard规则中printmapping指定生成路径.

Android Studio开启Proguard

Android Studio开启ProGuard非常简单,只需要在当前Module的build.gradle中增加如下语句即可(一般只在release版本中增加ProGuard功能):

android {
    buildTypes {
        release {
            signingConfig signingConfigs.release
            // 设置进行代码混淆
            minifyEnabled true
            // 指定代码混淆规则文件的位置
            proguardFile '/home/wzy//weather/proguard.flags'
        }
    }
}

如上述代码所示,需要指定proguardFile的具体路径,并且设置minifyEnabled为true.

Android.mk中开启ProGuard

在Android.mk中开启ProGuard也非常简单,示例代码如下:

LOCAL_PROGUARD_ENABLED := custom
LOCAL_PROGUARD_FLAG_FILES := proguard.flags

关闭ProGuard:

LOCAL_PROGUARD_ENABLED := disabled

你可能感兴趣的:(android,ProGuard,代码混淆)