Android混淆大法
本文涉及内容: 1.混淆的基本介绍,混淆的基本配置及示例 2.如何进行多模块的混淆 3.实际项目中混淆时会遇到的问题 4.混淆后如何进行debug和日志查看
前言
混淆,主要作用就是对把项目的原本清晰的类名、方法名等转换为难易理解的a/b/c名字,不会改变原有的代码逻辑,但是会增大阅读难度。如果是为了应用安全,通常直接使用应用加固处理。但是,有些情况下,无法使用加固,但为了防止程序主要逻辑泄露,混淆还是很有必要的。
Android SDK 自带了混淆工具Proguard。它位于SDK根目录\tools\proguard下面。如果开启了混淆,Proguard默认情况下会对所有代码,包括第三方包都进行混淆,但并不是所有的文件都可以进行混淆,有些文件混淆后,会影响程序运行,这些是需要避免混淆的,配置文件的主要作用也即是如此。
一、gradle 混淆开启
Proguard是android Studio自带的混淆工具,通过gradle可以进行配置,主要作用是对java代码的混淆优化和对资源文件的压缩,这些配置都是默认开启的。
android {
......
defaultConfig {
......
}
buildTypes {
release {
minifyEnabled true
zipAlignEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
......
}
二、混淆规则
1.模板示例
模板可以直接使用,想要深入理解的可以继续向下看
# --------------------------------------------基本指令区--------------------------------------------#
-ignorewarning # 忽略警告
-optimizationpasses 5 # 指定代码的压缩级别(在0~7之间,默认为5)
-dontusemixedcaseclassnames # 不使用大小写混合(windows大小写不敏感,建议加入)
-dontskipnonpubliclibraryclasses # 不混淆非公共的库的类
-dontskipnonpubliclibraryclassmembers # 不混淆非公共的库的类的成员
-dontpreverify # 混淆时不做预校验(Android不需要预校验,去掉可以加快混淆速度)
-verbose # 混淆时记录日志(混淆后会生成映射文件)
#混淆时所采用的算法(谷歌推荐算法)
-optimizations !code/simplification/arithmetic,!field,!class/merging,!code/allocation/variable
#添加支持的jar(引入libs下的所有jar包)
-libraryjars libs(*.jar;)
#将文件来源重命名为“SourceFile”字符串
-renamesourcefileattribute SourceFile
#保持注解不被混淆
-keepattributes *Annotation*
-keep class * extends java.lang.annotation.Annotation {*;}
#保持泛型不被混淆
-keepattributes Signature
#保持反射不被混淆
-keepattributes EnclosingMethod
#保持异常不被混淆
-keepattributes Exceptions
#保持内部类不被混淆
-keepattributes Exceptions,InnerClasses
#抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
#------------------------------------默认保留区--------------------------------------#
#保持四大组件不被混淆
-keep public class * extends android.app.Fragment
-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
#保持 Google 原生服务需要的类不被混淆
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
# 保留support下的所有类及其内部类,AndroidX
-keep class android.support.** {*;}
-dontwarn android.support.**
-keep interface android.support.** { *; }
-keep class androidx.** {*;}
-keep interface androidx.** {*;}
-keep public class * extends androidx.**
-dontwarn androidx.**
# 保留继承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**
-keep class com.google.android.material.** {*;}
-dontwarn com.google.android.material.**
-dontnote com.google.android.material.**
# 保留本地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);
}
#保留指定格式的构造方法不被混淆
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#保留在Activity中的方法参数是view的方法(避免布局文件里面onClick被影响)
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
#保持枚举 enum 类不被混淆
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
#保持R(资源)下的所有类及其方法不能被混淆
-keep class **.R$* { *; }
#保持 Parcelable 序列化的类不被混淆(注:aidl文件不能去混淆)
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#需要序列化和反序列化的类不能被混淆(注:Java反射用到的类也不能被混淆)
-keepnames class * implements java.io.Serializable
#保持 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();
}
#保持 BaseAdapter 类不被混淆
-keep public class * extends android.widget.BaseAdapter { *; }
#保持 CusorAdapter 类不被混淆
-keep public class * extends android.widget.CusorAdapter{ *; }
#-------------------------------------webView区---------------------------------------#
#保护代码中的jsinterface注解不被混淆
-keepattributes *JavascriptInterface*
#WebView处理,项目中没有使用到webView忽略即可
#保持Android与JavaScript进行交互的类不被混淆
-keep class **.AndroidJavaScript { *; }
-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.WebChromeClient {
public void *(android.webkit.WebView,java.lang.String);
}
#网络请求相关
-keep public class android.net.http.SslError
#-------------------------------------删除代码区(可不配置)--------------------------------------#
删除代码中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(...);
}
#---------------------------------1.实体类---------------------------------
#--------------------------------------------------------------------------
#---------------------------------2.与JS交互的类-----------------------------
#--------------------------------------------------------------------------
#---------------------------------3.反射相关的类和方法-----------------------
#--------------------------------------------------------------------------
#---------------------------------4.内部类--------------------------------
#--------------------------------------------------------------------------
#---------------------------------5.第三方依赖----------------------------------------
2.重点问题注意
(1)混淆后webview无法使用或是JSBridge接口无法使用
注意,查看是否配置了webview的免混淆
#保护代码中的jsinterface注解不被混淆
-keepattributes *JavascriptInterface*
#WebView处理,项目中没有使用到webView忽略即可
#保持Android与JavaScript进行交互的类不被混淆
-keep class **.AndroidJavaScript { *; }
-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.WebChromeClient {
public void *(android.webkit.WebView,java.lang.String);
}
(2)AIDL接口无法调用
所有的aidl文件都需要免混淆
-keep class "你的aidl文件类名"{*;}
例如:
-keep class com.demo.superfan.testAidl{*;}
(3)内部类导致的错误
排查下是否有内部类,内部类需要单独配置,无论这个内部类所在的类是否免混淆,都需要配置
-keep class "内部类所在类的文件类名"$*{*;}
例如:
-keep class com.demo.DemoUtil$DemoInner{*;}
或
-keep class com.demo.DemoUtil$*{*;}
(4)实体类导致的问题
通常的实体类转json时,如果实体类混淆后,都会出现问题。 需要将所有实体类进行配置
(5)多个modle的混淆问题
多个modle的混淆有两种方式:
- 一、直接把所有的混淆配置都写在主modle中;
- 二、每个modle配置各自的混淆配置
推荐使用对每个model进行各自的混淆配置,即使是各个modle被其他项目引用,混淆配置也会直接跟随。
但是,对每个modle进行配置时,需要注意:
- 主modle的gradle中按上述方式进行配置;
- 子modle的gradle中按以下方式进行配置:
plugins {
id 'com.android.library'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
... ...
}
buildTypes {
release {
minifyEnabled true
consumerProguardFiles 'proguard-rules.pro'
}
debug {
minifyEnabled false
consumerProguardFiles 'proguard-rules.pro'
}
}
... ...
}
子Model的混淆配置使用consumerProguardFiles 'proguard-rules.pro'
(6)混淆后打包,错误日志也是混淆后的,找不到类
混淆后,由于class的类名变成了abc,导致日志文件输出也变成了com.demo.a.b.c类似这种,对于排查问题造成了很大的困难。不过,不用担心,早在我们混淆时,编译器就为我们生成了一个混淆关系对应文件。在这里:  内容如下:
com.catchai.superfan.proguardtestdemo.IAIDLCallback -> a.a.a.a.a:
即是,将com.catchai.superfan.proguardtestdemo.IAIDLCallback 混淆为a.a.a.a.a 因此,如果是正式发包,最好打包时,将release包和mapping一起保存好,方便后期排查线上日志。
三、深入理解
1.混淆的输出文件
有心的童鞋可能会发现,开启了混淆的项目,在androidStudio打包时,在modle的build/outputs下有一个mapping文件夹,这里面便是混淆的输出文件。 一般有四个:configuration.txt、mapping.txt、seeds.txt、usage.txt
(1)configuration.txt
通常是混淆的配置文件,基本和modle下的proguard-rules.pro 文件一致。
(2)mapping.txt
混淆打包后的关系对应文件,混淆的名字映射到原始代码的名字。此文件对于后期排查日志很重要。
(3)seed.txt
keep保持的内容,文件内是proguard-rules.pro 配置文件中要保持的不被混淆的内容。此文件有助于排查我们的 proguard-rules.pro 配置文件中的配置内容是否生效。
(4)usage.txt
没有引用,被移除的代码
那么这四个文件是如何生成的呢?  其实简单来说就是:
- 编译器首先读取
proguard-rules.pro 查看哪些是需要保持的(不需要混淆),生成seeds.txt文件; - 然后进行压缩,删减哪些没有被引用的类,生成usage.txt文件;
- 接着开始混淆,把对应关系保存到mapping.txt文件中。
2.混淆的规则
关键字
 理解:
- 虽然keep是保留类和类中的成员不被混淆和移除。但是内部类不输入类和类成员的范畴。
通配符

|