为什么要做代码混淆
当我们应用开发完成后,上线前为了给用户带来良好的用户体验。都会对应用进行优化处理,例如app体积包压缩、代码优化、安全处理等。然而对于我们来说最简单的优化方案就是开启代码压缩优化,只需要在项目build文件中做简单配置就可以在正式打包编译过程中自动实现代码压缩、字节码的优化以及代码混淆处理。所以为了给用户提供一个体积更小更安全的应用一定要对代码进行混淆处理。
如何使用代码混淆
1. 配置使用:
signingConfigs {
debug {
keyAlias ‘alias’
keyPassword ‘keypassword’
storeFile file("${rootDir}/debug.keystore")
storePassword 'storepassword'
}
release {
keyAlias ‘alias’
keyPassword ‘keypassword’
storeFile file("${rootDir}/release.keystore")
storePassword ‘storepassword’
}
}
buildTypes {
debug {
// 启用代码压缩、优化及混淆
minifyEnabled false
// 启用资源压缩,需配合 minifyEnabled=true 使用
shrinkResources false
// 指定混淆保留规则
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// 包签名
signingConfig signingConfigs.debug
}
release {
// 启用代码压缩、优化及混淆
minifyEnabled true
// 启用资源压缩,需配合 minifyEnabled=true 使用
shrinkResources true
// 指定混淆保留规则
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// 包签名
signingConfig signingConfigs.release
}
}
如果你的项目在一开始没有开启混淆而是在迭代了几个版本后才想起来要混淆时一定要注意混淆的规则,混淆文件未配置好的话会导致无法正式打包或者在你打包成功后你的项目运行没点几下就造成崩溃了的结果,报找不到类或方法的异常;这是因为优化工具Proguard在做优化时,对 一些类及方法进行了去除或重命名,而我们某些代码是通过字符串匹配的方式来找到要执行的类和方法的,而对于这种方式调用的,优化工具分析有点问题我们需要注意一下:
* Android 四大组件
* native方法
* Java 反射用到的类
* 自定义控件
* 枚举类
* JavaBean
* Parcelable、Serializable 序列化类
* WebView 与 JS 交互所用到的类和方法
以上这些都会通过字符串匹配的方式调用类或方法,对于这些不能被优化和混淆的类和方法,需要在混淆配置文件(proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’)中添加优化配置,同时proguard提供了以下keep关键字:
| 保留 | 防止被移除或重命名 | 防止被移除或重命名 |
|---|---|---|
| 类和类成员 | -keep | -keepnames |
| 仅类成员 | -keepclassmembers | -keepclassmembernames |
| 如类含有某成员,保留类及其成员 | -keepclasseswithmembers | -keepclasseswithmembernames |
2. proguard-rules.pro文件的基础配置:
#1.基本指令区
# 代码混淆压缩比,在0~7之间,默认为5,一般不做修改
-optimizationpasses 5
# 混合时不使用大小写混合,混合后的类名为小写
-dontusemixedcaseclassnames
# 指定不去忽略非公共库的类
-dontskipnonpubliclibraryclasses
# 指定不去忽略非公共库的类成员
-dontskipnonpubliclibraryclassmembers
# 不进行优化,建议使用此选项,
-dontoptimize
# 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify 能够加快混淆速度。
-dontpreverify
# 使我们的项目混淆后产生映射文件包含有类名->混淆后类名的映射关系
-verbose
# 使用printmapping指定映射文件的名称
-printmapping proguardMapping.txt
# 屏蔽警告
-ignorewarnings
# 指定混淆是采用的算法,后面的参数是一个过滤器这个过滤器是谷歌推荐的算法,一般不做更改
-optimizations !code/simplification/cast,!field/,!class/merging/
# 保留Annotation不混淆
-keepattributes *Annotation*
# 避免混淆泛型
-keepattributes Signature
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
#2.默认保留区
# 保留我们使用的四大组件,自定义的Application等等这些类不被混淆
# 因为这些子类都有可能被外部调用
-keep public class * extends android.app.Activity
-keep public class * extends android.app.AppCompatActivity
-keep public class * extends android.app.Fragment
-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
-keep public class com.google.vending.licensing.ILicensingService
# 保留support下的所有类及其内部类
-keep class android.support.** {*;}
# 保留继承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**
# 保留本地native方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留在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{
*** 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);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
# 保留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;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 保留R下面的资源
-keep class **.R$* {
*;
}
# 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
void *(**On*Event);
void *(**On*Listener);
}
# 避免layout中onclick方法(android:onclick="onClick")混淆
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
# 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);
}
过滤项目中自己定义的实体类或者工具类、基类等(bean、base都是文件目录按照你项目的实际情况修改!)
-keep class com.xxx.xxx.bean.** { *; }
-keep class com.xxx.xxx.base.** { *; }
下面是第三方的一些配置(根据你自己的项目情况找到使用的第三方参考文档是否需要混淆 如何混淆)
1.Gson
-dontwarn sun.misc.**
-keep class com.google.gson.examples.android.model.** { <fields>; }
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
-keepclasseswithmembers class * {
@retrofit2.http.* <methods>;
}
-keep class com.google.gson.** { *; }
-keep class com.google.inject.** { *; }
2. Glide
-dontwarn com.bumptech.glide.**
-keep class com.bumptech.glide.**{*;}
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
3. OkHttp3
-dontwarn com.squareup.okhttp3.**
-keep class com.squareup.okhttp3.** { *;}
-dontwarn okio.**
4. ButterKnife(不推荐使用)
Android Studio 4.1版本使用ButterKnife注解id的代码出现了警告:
Resource IDs will be non-final in Android Gradle Plugin version 5.0, avoid using them as annotation attributes
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * {
@butterknife.* <fields>;
}
-keepclasseswithmembernames class * {
@butterknife.* <methods>;
}
4. bugly
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
5. greenDao
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties { *; }
# If you DO use SQLCipher:
-keep class org.greenrobot.greendao.database.SqlCipherEncryptedHelper { *; }
# If you do NOT use SQLCipher:
-dontwarn net.sqlcipher.database.**
# If you do NOT use RxJava:
-dontwarn rx.**
#GreenDaoUpgradeHelper
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static void dropTable(org.greenrobot.greendao.database.Database, boolean);
public static void createTable(org.greenrobot.greendao.database.Database, boolean);
}
6. 极光推送
-dontoptimize
-dontpreverify
-dontwarn cn.jpush.**
-keep class cn.jpush.** { *; }
-keep class * extends cn.jpush.android.helpers.JPushMessageReceiver { *; }
-dontwarn cn.jiguang.**
-keep class cn.jiguang.** { *; }
7. 腾讯云
-keep class com.tencent.** { *; }
8. 亚马逊AWS 存储
-keep class com.amazonaws.services.sqs.QueueUrlHandler { *; }
-keep class com.amazonaws.javax.xml.transform.sax.* { public *; }
-keep class com.amazonaws.javax.xml.stream.** { *; }
-keep class com.amazonaws.services.**.model.*Exception* { *; }
-keep class com.amazonaws.internal.** { *; }
-keep class org.codehaus.** { *; }
-keep class org.joda.time.tz.Provider { *; }
-keep class org.joda.time.tz.NameProvider { *; }
-keepattributes Signature,*Annotation*,EnclosingMethod
-keepnames class com.fasterxml.jackson.** { *; }
-keepnames class com.amazonaws.** { *; }
在android中gradle tools默认给提供了一些混淆配置,在打完正式包以后,我们可以build/intermediates/proguard-files/目录下,看到所使用的混淆配置
mapping.txt → 原始与混淆过的类、方法、字段名称间的转换,在遇到崩溃时,可以通过该文件进行还原;
seeds.txt → 未进行混淆的类与成员;
usage.txt → APK中移除的代码;
resources.txt → 资源优化记录文件,哪些资源引用了其他资源,哪些资源在使用,哪些资源被移除;
对于上面的四个文件,不一定都会生成,可以通过以下命令设置输出:
# 输出mapping.txt文件
-printmapping ./build/outputs/mapping/release/mapping.txt
# 输出seeds.txt文件
-printseeds ./build/outputs/mapping/release/seeds.txt
# 输出usage.txt文件
-printusage ./build/outputs/mapping/release/usage.txt