前言

混淆是保护apk的必要手段,通过混淆,如果apk被反编译,会对一些关键词使用a,b,c之类的字符替换,加大解读难度

如何混淆

在build.gradle文件中配置如下代码:

android {
    buildTypes {
    debug {
            ...
        }
        release {
            //混淆开关
            minifyEnabled true
            // 是否zip对齐
            zipAlignEnabled true
            // 移除无用的resource文件
            shrinkResources false
            // 是否打开debuggable开关
            debuggable false
            // 是否打开jniDebuggable开关
            jniDebuggable false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.myConfig
        }
    }
}

minifyEnabled为true,就是打开混淆。主要的配置规则是在proguard-rules.pro文件中进行,

混淆规则

混淆设置参数

-optimizationpasses 5                       # 代码混淆的压缩比例,值介于0-7,默认5
-verbose                                    # 混淆时记录日志
-dontoptimize                               # 不优化输入的类文件
-dontshrink                                 # 关闭压缩
-dontpreverify                              # 关闭预校验(作用于Java平台,Android不需要,去掉可加快混淆)
-dontoptimize                               # 关闭代码优化
-dontobfuscate                              # 关闭混淆
-ignorewarnings                             # 忽略警告
-dontwarn com.squareup.okhttp.**            # 指定类不输出警告信息
-dontusemixedcaseclassnames                 # 混淆后类型都为小写
-dontskipnonpubliclibraryclasses            # 不跳过非公共的库的类
-printmapping mapping.txt                   # 生成原类名与混淆后类名的映射文件mapping.txt
-useuniqueclassmembernames                  # 把混淆类中的方法名也混淆
-allowaccessmodification                    # 优化时允许访问并修改有修饰符的类及类的成员
-renamesourcefileattribute SourceFile       # 将源码中有意义的类名转换成SourceFile,用于混淆具体崩溃代码
-keepattributes SourceFile,LineNumberTable  # 保留行号
-keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod # 避免混淆注解、内部类、泛型、匿名类
-optimizations !code/simplification/cast,!field/ ,!class/merging/   # 指定混淆时采用的算法

保持不被混淆的设置

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


-keep                           # 防止类和类成员被移除或被混淆;
-keepnames                      # 防止类和类成员被混淆;
-keepclassmembers	            # 防止类成员被移除或被混淆;
-keepclassmembernames           # 防止类成员被混淆;
-keepclasseswithmembers         # 防止拥有该成员的类和类成员被移除或被混淆;
-keepclasseswithmembernames     # 防止拥有该成员的类和类成员被混淆;

具体的类
访问修饰符 → publicprivateprotected
通配符(*) → 匹配任意长度字符,但不包含包名分隔符(.)
通配符(**) → 匹配任意长度字符,且包含包名分隔符(.)
extends → 匹配实现了某个父类的子类
implements → 匹配实现了某接口的类
$ → 内部类

成员

匹配所有构造器 → <init>
匹配所有域 → <field>
匹配所有方法 → <methods>
访问修饰符 → publicprivateprotected
除了 *** 通配符外,还支持 *** 通配符,匹配任意参数类型
... → 匹配任意长度的任意类型参数,如void test(...)可以匹配不同参数个数的test方法

常用自定义混淆规则范例

# 不混淆某个类的类名,及类中的内容
-keep class cn.coderpig.myapp.example.Test { *; }
 
# 不混淆指定包名下的类名,不包括子包下的类名
-keep class cn.coderpig.myapp*
 
# 不混淆指定包名下的类名,及类里的内容
-keep class cn.coderpig.myapp* {*;}
 
# 不混淆指定包名下的类名,包括子包下的类名
-keep class cn.coderpig.myapp**
 
# 不混淆某个类的子类
-keep public class * extends cn.coderpig.myapp.base.BaseFragment
 
# 不混淆实现了某个接口的类
-keep class * implements cn.coderpig.myapp.dao.DaoImp
 
# 不混淆类名中包含了"entity"的类,及类中内容
-keep class **.*entity*.** {*;}
 
# 不混淆内部类中的所有public内容
-keep class cn.coderpig.myapp.widget.CustomView$OnClickInterface {
    public *;
}
 
# 不混淆指定类的所有方法
-keep cn.coderpig.myapp.example.Test {
    public <methods>;
}
 
# 不混淆指定类的所有字段
-keep cn.coderpig.myapp.example.Test {
    public <fields>;
}
 
# 不混淆指定类的所有构造方法
-keep cn.coderpig.myapp.example.Test {
    public <init>;
}
 
# 不混淆指定参数作为形参的方法
-keep cn.coderpig.myapp.example.Test {
    public <methods>(java.lang.String);
}
 
# 不混淆类的特定方法
-keep cn.coderpig.myapp.example.Test {
    public test(java.lang.String);
}
 
# 不混淆native方法
-keepclasseswithmembernames class * {
    native <methods>;
}
 
# 不混淆枚举类
-keepclassmembers enum * {
  public static **[] values();
  public static ** valueOf(java.lang.String);
}
 
#不混淆资源类
-keepclassmembers class **.R$* {
    public static <fields>;
}
 
# 不混淆自定义控件
-keep public class * entends android.view.View {
    *** get*();
    void set*(***);
    public <init>;
}
 
# 不混淆实现了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();
}
 
# 不混淆实现了parcelable接口的类成员
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}
 
# 注意事项:
# 
# ① jni方法不可混淆,方法名需与native方法保持一致;
# ② 反射用到的类不混淆,否则反射可能出问题;
# ③ 四大组件、Application子类、Framework层下的类、自定义的View默认不会被混淆,无需另外配置;
# ④ WebView的JS调用接口方法不可混淆;
# ⑤ 注解相关的类不混淆;
# ⑥ GSON、Fastjson等解析的Bean数据类不可混淆;
# ⑦ 枚举enum类中的values和valuesof这两个方法不可混淆(反射调用);
# ⑧ 继承ParceableSerializable等可序列化的类不可混淆;
# ⑨ 第三方库或SDK,请参考第三方提供的混淆规则,没提供的话,建议第三方包全部不混淆;

proguard-rules.pro

ProGuard在AS中默认的配置文件是proguard-rules.pro
我们常见的ProGuard文件主要包含如下几个部分:
1.基本配置,设定混淆的规则等,基本配置是每个混淆文件必须存在的,并且此块内容大部分通用,可以直接copy。
2.基本的keep项,多数Android工程都需要非混淆的内容,包括有四大组件等内容,此项内容也大部分通用,也可以直接copy。
3.三方引入的lib包混淆,这个内容需要去各自的官网去查找对应的混淆添加代码。
4.其他需要不混淆的内容,包括:实体类,json解析类,WebView及js的调用模块,与反射相关的类和方法。

基本配置
基本配置基本上变化不大,一般的项目可以直接copy

#指定压缩级别
-optimizationpasses 5

#不跳过非公共的库的类成员
-dontskipnonpubliclibraryclassmembers

#混淆时采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

#把混淆类中的方法名也混淆了
-useuniqueclassmembernames

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

#不做预检验,preverify是proguard的四大步骤之一,可以加快混淆速度
#-dontpreverify

# 忽略警告(?)
#-ignorewarnings

#混淆时不使用大小写混合,混淆后的类名为小写(大小写混淆容易导致class文件相互覆盖)
-dontusemixedcaseclassnames

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

#将文件来源重命名为“SourceFile”字符串
#-renamesourcefileattribute SourceFile
#保留行号
-keepattributes SourceFile,LineNumberTable
#保持泛型
-keepattributes Signature
# 保持注解
-keepattributes *Annotation*,InnerClasses

# 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**

基本的项目配置
多数是包括序列化,Android四大组件等基本内容的混淆keep,对于常规的项目而言区别不大,也可以进行copy

# Parcelable
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}
# Serializable
-keepnames class * implements java.io.Serializable
-keepclassmembers class * implements java.io.Serializable {
    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$* {*;}

# 保留四大组件,自定义的Application,Fragment等这些类不被混淆
-keep public class * extends android.app.Activity
-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

## support
-dontwarn android.support.**
-keep class android.support.v4.app.** { *; }
-keep interface android.support.v4.app.** { *;}
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**

-keep public class * extends android.support.v4.view.ActionProvider {
    public <init>(android.content.Context);
}

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

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

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

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

#保留在Activity中的方法参数是view的方法,
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

# For XML inflating, keep views' constructoricon.png    自定义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*(...);
}

# androidx 混淆
-keep class com.google.android.material.** {*;}
-keep class androidx.** {*;}
-keep public class * extends androidx.**
-keep interface androidx.** {*;}
-dontwarn com.google.android.material.**
-dontnote com.google.android.material.**
-dontwarn androidx.**
-printconfiguration
-keep,allowobfuscation @interface androidx.annotation.Keep

-keep @androidx.annotation.Keep class *
-keepclassmembers class * {
    @androidx.annotation.Keep *;
}

三方SDK的混淆
三方SDK的混淆代码需要去各自的SDK官网上去查找,例如网易云信的

### nimlib
-dontwarn com.netease.nim.**
-keep class com.netease.nim.** {*;}

-dontwarn com.netease.nimlib.**
-keep class com.netease.nimlib.** {*;}

-dontwarn com.netease.share.**
-keep class com.netease.share.** {*;}

-dontwarn com.netease.mobsec.**
-keep class com.netease.mobsec.** {*;}

其他混淆内容
其他混淆内容比较杂,包括:实体类,json解析类,WebView及js的调用模块,与反射相关的类和方法。这个根据自己需要来做


# WebView
-dontwarn android.webkit.WebView
-dontwarn android.net.http.SslError
-dontwarn android.webkit.WebViewClient
-keep public class android.webkit.WebView
-keep public class android.net.http.SslError
-keep public class android.webkit.WebViewClient
-keep
 class com.mandala.healthserviceresident.vo.** { *; } #实体类不参与混淆
-keep class com.mandala.healthserviceresident.http.** { *; } #实体类不参与混淆
-keep class com.hacker.okhttputil.** { *; } #实体类不参与混淆
-keep class net.sqlcipher.** { *; }

-keep class 类所在的包.** { *; }
log过滤

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

混淆结果
在添加混淆之后,在排查时我们可以对混淆和未混淆的类名进行记录和打印,在混淆文件中添加如下代码可以查看混淆编译的类及文件结构:

# 混淆映射,生成映射文件
-verbose
-printmapping proguardMapping.txt
#输出apk包内所有的class的内部结构
-dump dump.txt
#未混淆的类和成员
-printseeds seeds.txt
#列出从apk中删除的代码
-printusage unused.txt

添加上述代码之后,会在生成release包时也同时生成三个日志文件,我们在排查混淆问题时可以以上述三个文件作为参考,快速排查。

Bug排查

我这边混淆完成以后,打包测试,发现网络能访问,但是提示失败,刚开始想的办法是抓包,然后发现实体类被设置成a,b之类的,这个是被混淆了,然后在混淆文件里keep一下,但是还有其他bug ,这样打包完成以后,如果没有有效的手段,只能是看着现象和混淆文件一个一个猜了,如果能在debug的时候发现Bug就很好排查了,排查方法就是在debug模式下,打开混淆
打开方法很简单

  buildTypes {
        debug {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        release {
            zipAlignEnabled true
            shrinkResources true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.myConfig
        }
    }

就这样,然后debug进行排查就好了。正常开发的时候记得关掉,比较耗性能

我用到的配置

# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in D:\AndroidStudio\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}



# Optimizations: If you don't want to optimize, use the
# proguard-android.txt configuration file instead of this one, which
# turns off the optimization flags.  Adding optimization introduces
# certain risks, since for example not all optimizations performed by
# ProGuard works on all versions of Dalvik.  The following flags turn
# off various optimizations known to have issues, but the list may not
# be complete or up to date. (The "arithmetic" optimization can be
# used if you are only targeting Android 2.0 or later.)  Make sure you
# test thoroughly if you go this route.

-optimizations !code/simplification/cast,!field/*,!class/merging/*
-optimizationpasses 5
-allowaccessmodification
-dontpreverify


# The remainder of this file is identical to the non-optimized version
# of the Proguard configuration file (except that the other file has
# flags to turn off optimization).
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose

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

-dontwarn org.apache.http.**
-dontwarn com.amap.**
-dontwarn com.alibaba.**
-dontwarn com.netease.**
-dontwarn io.netty.**
-dontwarn com.autonavi.amap.**

### keep options
#system default, from android example
-keep public class * extends android.app.Activity
-keep public class * extends androidx.fragment.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

-keepattributes *Annotation*,InnerClasses
#-keepattributes SourceFile,LineNumberTable

### 3rd party jars
-keep class android.support.** {*;}
-keep class com.amap.** {*;}
-keep class android.webkit.** {*;}


### 3rd party jars(lucene)
-dontwarn java.nio.channels.SeekableByteChannel
-dontwarn org.apache.lucene.**
-keep class org.apache.lucene.** {*;}

### nimlib
-dontwarn com.netease.nim.**
-keep class com.netease.nim.** {*;}

-dontwarn com.netease.nimlib.**
-keep class com.netease.nimlib.** {*;}

-dontwarn com.netease.share.**
-keep class com.netease.share.** {*;}

-dontwarn com.netease.mobsec.**
-keep class com.netease.mobsec.** {*;}

-keepclasseswithmembernames class * {
    native <methods>;
}

-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
}

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

-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

-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();
}

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


-keepclassmembers class * {
   public <init> (org.json.JSONObject);
}

-keep   class com.amap.api.maps.**{*;}
-keep   class com.amap.api.trace.**{*;}

-keep class com.amap.api.location.**{*;}
-keep class com.amap.api.fence.**{*;}

 -keep class com.alibaba.sdk.android.**{*;}

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

# Bugly异常上报
 -dontwarn com.tencent.bugly.**
 -keep public class com.tencent.bugly.**{*;}

 -keep class com.umeng.** {*;}
 -keep class java.security.interfaces.*
 -keep class java.util.Base64

 -keep class org.repackage.** {*;}

 -keepclassmembers class * {
    public <init> (org.json.JSONObject);
 }

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

-keep class org.greenrobot.greendao.**{*;}
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao{
    public static java.lang.String TABLENAME;
}
-keep class **$Properties


-keep class com.mandala.healthserviceresident.greendao.gen.**{*;}
# If you do not use SQLCipher:
-dontwarn org.greenrobot.greendao.database.**
# If you do not use Rx:
-dontwarn rx.**

#Gson
-keep class com.google.gson.** {*;}
-keep class com.google.**{*;}
-keep class com.google.gson.stream.** { *; }
-keep class com.google.gson.examples.android.model.** { *; }
#okhttp
-dontwarn com.squareup.okhttp3.**
-keep class com.squareup.okhttp3.** { *;}
-dontwarn okio.**

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

-keep class com.mandala.healthserviceresident.vo.** { *; } #实体类不参与混淆
-keep class com.mandala.healthserviceresident.http.** { *; } #实体类不参与混淆
-keep class com.hacker.okhttputil.** { *; } #实体类不参与混淆
-keep class net.sqlcipher.** { *; }

# WebView
-dontwarn android.webkit.WebView
-dontwarn android.net.http.SslError
-dontwarn android.webkit.WebViewClient
-keep public class android.webkit.WebView
-keep public class android.net.http.SslError
-keep public class android.webkit.WebViewClient

-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
-ignorewarnings

重点看一下greendao的配置,如果没配置,会提示daoConfig出错

-keep class org.greenrobot.greendao.**{*;}
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao{
    public static java.lang.String TABLENAME;
}
-keep class **$Properties


-keep class com.mandala.healthserviceresident.greendao.gen.**{*;}
# If you do not use SQLCipher:
-dontwarn org.greenrobot.greendao.database.**
# If you do not use Rx:
-dontwarn rx.**
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐