All Projects → HurTeng → AndroidProguard

HurTeng / AndroidProguard

Licence: MIT license
Android代码混淆,包含了通用混淆配置,以及常用的第三方库混淆配置

AndroidProguard

Android代码混淆,包含了通用混淆配置,以及常用的第三方库混淆配置

简介

作为Android开发者,如果你不想开源你的应用,那么在应用发布前,就需要对代码进行混淆处理,从而让我们代码即使被反编译,也难以阅读。混淆概念虽然容易,但很多初学者也只是网上搜一些成型的混淆规则粘贴进自己项目,并没有对混淆有个深入的理解。本篇文章的目的就是让一个初学者在看完后,能在不进行任何帮助的情况下,独立写出适合自己代码的混淆规则。

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

混淆介绍

Android中的“混淆”可以分为两部分,一部分是 Java 代码的优化与混淆,依靠 proguard 混淆器来实现;另一部分是资源压缩,将移除项目及依赖的库中未被使用的资源(资源压缩严格意义上跟混淆没啥关系)。

我们通常说的proguard(代码混淆)包括以下四个方面:

  1. shrink(压缩): 检测并移除没有用到的类,变量,方法和属性;
  2. optimize(优化): 分析和优化代码,优化可能会造成一些潜在风险,不能保证在所有版本的Dalvik上都正常运行;
  3. obfuscate(混淆): 把类名、属性名、方法名替换为简短且无意义的名称,增大反编译难度;
  4. preverify(预校验): 预校验是作用在Java平台上的(校验代码是否符合Java1.6+),Android平台上不需要这项功能,去掉之后还可以加快混淆速度。

这四个流程默认开启,在 Android 项目中我们可以选择将“优化”和“预校验”关闭,对应命令是-dontoptimize、-dontpreverify

代码混淆

Android Studio集成了Java语言的ProGuard作为压缩,优化和混淆的工具,使用起来很方便。 首先要通过ProGuard启用代码混淆,首先要在app module下的build.gradle文件将“minifyEnabled”属性设置为true,以便开启代码混淆(开启混淆会使编译时间变长,默认关闭):

android {
    buildTypes {
        release {
            minifyEnabled true  // 代码混淆(true为打开,开启混淆会使编译时间变长,默认不开启)
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

在上面的“混淆配置”中有这样一行代码

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

proguard-android.txt,这是系统默认的混淆文件,具体在../sdk/tools/proguard/ 目录下,其中包含了android最基本的混淆,一般不需要改动; 我们需要配置的是项目中app下的 proguard-rules.pro 文件

ProGuard作用

-dontshrink 关闭压缩 -optimizationpasses n 表示proguard对代码进行迭代优化的次数,Android一般为5 -dontobfuscate 关闭混淆

混淆后默认会在工程目录app/build/outputs/mapping/release下生成一个mapping.txt文件,这就是混淆规则,我们可以根据这个文件把混淆后的代码反推回源本的代码,所以这个文件很重要,注意保护好。原则上,代码混淆后越乱越无规律越好,但有些地方我们是要避免混淆的,否则程序运行就会出错,所以就有了下面我们要教大家的,如何让自己的部分代码避免混淆从而防止出错。

混淆规则

1.基本规则

  1. 常见混淆命令:

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

  1. 保持元素不参与混淆的规则

语句结构:[保持命令] [类] { [成员] }

“类”表示符合相关限定条件的类,它的内容可以使用:

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

“成员”表示符合相关限定条件的类成员,它的内容可以使用:

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

举个例子,假如需要将name.huihui.test包下所有继承Activity的public类及其构造函数都保持住,可以这样写: -keep public class name.huihui.test.** extends Android.app.Activity { } 3. 常用的自定义混淆规则

不混淆某个类 -keep public class name.huihui.example.Test { *; }

不混淆某个包所有的类 -keep class name.huihui.test.** { *; }

不混淆某个类的子类 -keep public class * extends name.huihui.example.Test { *; }

不混淆所有类名中包含了“model”的类及其成员 -keep public class .model. {*;}

不混淆某个接口的实现 -keep class * implements name.huihui.example.TestInterface { *; }

不混淆某个类的构造方法 -keepclassmembers class name.huihui.example.Test { public (); }

不混淆某个类的特定的方法 -keepclassmembers class name.huihui.example.Test { public void test(java.lang.String); }

再者,如果一个类中你不希望保持全部内容不被混淆,而只是希望保护类下的特定内容,就可以使用

; //匹配所有构造器 ; //匹配所有域 ; //匹配所有方法方法 你还可以在或前面加上private 、public、native等来进一步指定不被混淆的内容,如

-keep class cn.hadcn.test.One { public ; } 表示One类下的所有public方法都不会被混淆,当然你还可以加入参数,比如以下表示用JSONObject作为入参的构造函数不会被混淆

-keep class cn.hadcn.test.One { public (org.json.JSONObject); } 有时候你是不是还想着,我不需要保持类名,我只需要把该类下的特定方法保持不被混淆就好,那你就不能用keep方法了,keep方法会保持类名,而需要用keepclassmembers ,如此类名就不会被保持,为了便于对这些规则进行理解,官网给出了以下表格

保留 防止被移除或者被重命名 防止被重命名 类和类成员 -keep -keepnames 仅类成员 -keepclassmembers -keepclassmembernames 如果拥有某成员,保留类和类成员 -keepclasseswithmembers -keepclasseswithmembernames

注意事项

  1. jni方法不可混淆(jni方法必须要跟native方法一致);
  2. 反射用到的类不混淆(否则反射可能出现问题);
  3. 使用了 Gson 之类的工具要使 JavaBean 类即实体类不被混淆(否则无法将JSON解析成对应的对象);
  4. 添加第三方库所需的混淆规则(一般都在文档中会写混淆规则,使用时注意添加)
  5. 有用到WebView的JS调用也需要保证写的接口方法不混淆;
  6. Parcelable的子类和Creator静态成员变量不混淆(否则会抛出BadParcelableException异常);
  7. enum类型(枚举)不进行混淆
  • 所有在 AndroidManifest.xml 涉及到的类已经自动被保持,因此不用特意去添加这块混淆规则。(很多老的混淆文件里会加,现在已经没必要)
  • proguard-android.txt 已经存在一些默认混淆规则,没必要在 proguard-rules.pro 重复添加

关于第三方库的混淆规则,github上有相关的库android-proguard-snippets,但已经几年没更新了,所以还是希望自己手动添加

  1. 检查混淆结果

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

一方面,需要从代码层面检查。使用上文的配置进行混淆打包后在 /build/outputs/mapping/release/ 目录下会输出以下文件:

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

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

另一方面,需要从测试方面检查。将混淆过的包进行全方面测试,检查是否有 bug 产生。

基本的混淆模板

这里提供一份基本的混淆模板,适用于大部分项目 当然第三方库,或者上面提到的地方,需要根据项目的实际需求进行混淆

#############################################
#
# 基础混淆配置
#
#############################################


####################基本混淆指令的设置####################

# 代码混淆压缩比,在0~7之间,默认为5,一般不做修改
-optimizationpasses 5

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

# 优化时允许访问并修改有修饰符的类和类的成员
-allowaccessmodification

# 指定不忽略非公共库的类
-dontskipnonpubliclibraryclasses

# 指定不忽略非公共库的类成员
-dontskipnonpubliclibraryclassmembers

# 记录日志,使我们的项目混淆后产生映射文件(类名->混淆后类名)
-verbose

# 忽略警告,避免打包时某些警告出现,没有这个的话,构建报错
-ignorewarnings

# 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。
-dontpreverify

# 不混淆Annotation(保留注解)
-keepattributes *Annotation*,InnerClasses

# 避免混淆泛型
-keepattributes Signature

# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable

# 指定混淆是采用的算法,后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法,一般不做更改
-optimizations !code/simplification/cast,!field/*,!class/merging/*


####################Android开发中需要保留的公共部分####################

# 保留support下的所有类及其内部类
-keep class android.support.** {*;}
# 保留继承的support类
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**

# 保留R下面的资源
-keep class **.R$* {*;}

# 保留本地native方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}

# 保留Activity中参数类型为View的所有方法
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}

# 保留枚举类不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# 保留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 <fields>;
    !private <methods>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# 保留我们自定义控件(继承自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);
}

# 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
    void *(**On*Event);
    void *(**On*Listener);
}

# webView的混淆处理
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
    public *;
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.webView, jav.lang.String);
}



#############################################
#
# 自定义的混淆配置(根据项目需求进行定义)
#
#############################################

# 不混淆log
-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(java.lang.String, int);
    public static int v(...);
    public static int i(...);
    public static int w(...);
    public static int d(...);
    public static int e(...);
}



#############################################
#
# 第三方库的混淆配置(根据第三方库官网添加混淆代码)
#
#############################################

# Gson
-keepattributes *Annotation*
-keep class sun.misc.Unsafe { *; }
-keep class com.idea.fifaalarmclock.entity.***
-keep class com.google.gson.stream.** { *; }
-keep class com.你的bean.** { *; }


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


# Retrofit
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions


# 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;
}

# Glide图片库
-keep class com.bumptech.glide.**{*;}

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].