安卓项目实战之:关于Android混淆你必须了解的知识点

版权声明:转载必须注明本文转自郭子轩的博客 https://blog.csdn.net/gpf1320253667/article/details/84872405

前言

在实际Android开发中,应用打包上线之前都必须进行代码混淆,以此来增加Apk反编译之后代码阅读的困难性,防止竞争对手或者其他人通过反编译等手段轻松获取到项目源码恶意破解我们的程序,因此代码混淆在一定程度上确保了我们应用的安全性,同时通过代码混淆,可以自动移除未被使用的类、方法、属性,使我们APK的体积减小,可以说是一举两得,本篇我们就来详细讲解一下关于Android代码混淆相关的一些知识。

关于APK的反编译技巧可以查看我的另外一篇文章:Android实用技能:你应该掌握的APK反编译技巧

混淆的原理

为防止APP被通过反编译等手段逆向破解,从Android2.3开始,Google在SDK中加入了一款叫做ProGuard的代码优化和混淆工具,通过ProGuard提供的混淆功能我们可以把代码中原来有具体含义的包名,类名,变量名,方法名等名称全部替换成按顺序排列的无意义的英文字母a、b、c….,增加阅读的难度,从而保护程序的安全性。

ProGuard的功能配置

借助ProGuard工具我们能够对Java类中的代码进行:
1,压缩(Shrink):检测和删除没有使用的类,字段,方法和属性
2,优化(Optimize):对字节码进行优化,并且移除无用指令
3,混淆(Obfuscate):使用a,b,c等无意义的名称,对类,字段和方法进行重命名
4, 预检(Preveirfy):在 Java 平台上对处理后的代码进行预检
我们可以根据需要选择性的在gradle.build中进行相应功能的配置。

通常情况下当我们通过Android Studio创建一个应用的时候,系统默认已经自动将ProGuard代码混淆配置添加到了gradle.build中,如下代码所示,我们要做的只是去开启混淆以及在指定的混淆文件中加入我们自定义的混淆规则即可。

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

其中:
1,minifyEnabled :表示是否开启代码混淆,默认为false,如果需要开启的话我们只要将其设置为true即可。
2,proguardFiles :这部分有两段,前一段代表系统默认的混淆文件,该文件已经包含了基本的混淆声明,免去了我们很多事,这个文件的目录在/tools/proguard/proguard-android.txt,后一部分是我们项目里的自定义的混淆文件,目录就在app/proguard-rules.pro,在这个文件里我们可以声明一些我们所需要的定制的混淆规则。

ProGuard其他功能配置:

buildTypes {
    release {
        // 是否进行混淆
        minifyEnabled true
        //不显示log
        buildConfigField "boolean", "LOG_DEBUG", "false"
        //Zipalign优化,4字节对其
        zipAlignEnabled true
        // 移除无用的resource文件,如果发生Resources$NotFoundException:Resource ID #0x4 可选择关闭该功能 
        shrinkResources true
        //对应的混淆文件
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

注意:
在minifyEnabled置为false的情况下,zipAlignEnabled 和 shrinkResources 也要置为false,否则会编译报错
建议:
从项目开发一开始就开启ProGuard代码混淆,并且在debug模式下也进行ProGuard处理,防止在项目上线时混淆因问题太多而导致的不必要的混乱,当然你也可以选择只在release模式下开启代码混淆。

混淆语法

先看如下两个比较常用的命令的区别:

-keep class cn.hadcn.test.**
-keep class cn.hadcn.test.*

一颗星表示只是保持该包下的类名,而子包下的类名还是会被混淆;两颗星表示把本包和所含子包下的类名都保持;用以上方法保持类后,你会发现类名虽然未混淆,但里面的具体方法和变量命名还是变了,这时如果既想保持类名,又想保持里面的内容不被混淆,我们就需要以下方法了:

-keep class cn.hadcn.test.* {*;}

在此基础上,我们也可以使用Java的基本规则来保护特定类不被混淆,比如我们可以用extend,implement等这些Java规则。如下例子就避免所有继承Activity的类被混淆:

-keep public class * extends android.app.Activity

如果我们要保留一个类中的内部类不被混淆则需要用$符号,如下例子表示保持ScriptFragment内部类JavaScriptInterface中的所有public内容不被混淆:

-keepclassmembers class cc.ninty.chat.ui.fragment.ScriptFragment$JavaScriptInterface {
   public *;
}

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

<init>;     //匹配所有构造器
<fields>;   //匹配所有域
<methods>;  //匹配所有方法方法

你还可以在或前面加上private 、public、native等来进一步指定不被混淆的内容,如:

-keep class cn.hadcn.test.One {
    public <methods>;
}

表示One类下的所有public方法都不会被混淆,当然你还可以加入参数,比如以下表示用JSONObject作为入参的构造函数不会被混淆:

-keep class cn.hadcn.test.One {
   public <init>(org.json.JSONObject);
}

有时候你是不是还想着,我不需要保持类名,我只需要把该类下的特定方法保持不被混淆就好,那你就不能用keep方法了,keep方法会保持类名,而需要用keepclassmembers ,如此类名就不会被保持,为了便于对这些规则进行理解,官网给出了以下表格:

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

基本的混淆配置

# 代码混淆压缩比,在 0~7 之间
-optimizationpasses 5
# 混合时不使用大小写混合,混合后的类名为小写
-dontusemixedcaseclassnames
# 指定不忽略非公共库的类和类成员
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
# 这句话能够使我们的项目混淆后产生映射文件
# 包含有类名->混淆后类名的映射关系
-verbose
# 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度
-dontpreverify
# 保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses
# 避免混淆泛型
-keepattributes Signature
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
# 指定混淆是采用的算法,后面的参数是一个过滤器
# 这个过滤器是 Google 推荐的算法,一般不做修改
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
# 是否允许改变作用域的,可以提高优化效果
# 但是,如果你的代码是一个库的话,最好不要配置这个选项,因为它可能会导致一些 private 变量被改成 public,谨慎使用
#-allowaccessmodification
# 指定一些接口可能会被合并,即使一些子类没有同时实现两个接口的方法。这种情况在java源码中是不允许存在的,但是在java字节码中是允许存在的。
# 它的作用是通过合并接口减少类的数量,从而达到减少输出文件体积的效果。仅在 optimize 阶段有效。
# 如果在开启后没有任何影响可以使用,这项配置对于一些虚拟机的65535方法数限制是有一定效果的,谨慎使用
#-mergeinterfacesaggressively
# 输出所有找不到引用和一些其它错误的警告,但是继续执行处理过程。不处理警告有些危险,所以在清楚配置的具体作用的时候再使用
-ignorewarnings

不需要混淆的部分

1,AndroidMainfest中的类不混淆,四大组件和Application的子类和Framework层下所有的类默认不会进行混淆。
2,反射用到的类不混淆
3,JNI方法不混淆,因为这个方法需要和native方法保持一致;
4,Parcelable的子类和Creator的静态成员变量不能混淆,否则会产生BadParcelableException异常。
5,有用到webView的JS调用也需要保证写的接口方法不混淆。
6,使用GSON、fastjson等框架时,所写的JSON对象类(即bean包下的所有类)不混淆,否则无法将JSON解析成对应的对象。
7,使用第三方开源库或者引用其他第三方的SDK包时,需要在混淆文件中加入对应的混淆规则。
8,R类里及其所有内部static类中的所有static变量字段不混淆。

# Android 四大组件相关
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-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
# Fragment
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.app.Fragment
# 保留support下的所有类及其内部类
-keep class android.support.** { *; }
-keep interface android.support.** { *; }
-dontwarn android.support.**
# 保留 R 下面的资源
-keep class **.R$* {*;}
-keepclassmembers class **.R$* {
    public static <fields>;
}
# 保留本地 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);
}
# 保留 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();
}
# 对于带有回调函数的 onXXEvent 的,不能被混淆
-keepclassmembers class * {
    void *(**On*Event);
}
# WebView,没有使用 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);
}
# 不混淆使用了 @Keep 注解相关的类
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <init>(...);
}
#移除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(...);
#}

需要针对App进行的混淆配置

1,对于实体类我们不能混淆
对于实力类我们不能进行混淆,我们需要保留他们的set和get方法。对于boolean类型的get方法为isXXX,不能够遗漏。在开发的时候我们可以将所有的实体类放在一个包内,这样我们写一次混淆就行了。

-keep public class com.ljd.example.entity.** {
    public void set*(***);
    public *** get*();
    public *** is*();
}

2,在我们的app中使用了webView需要进行特殊处理
在我们的项目中经常会嵌入一些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);
}

3,在app中与HTML5的JavaScript的交互进行特殊处理
在我们的App中有时候需要与Html5中的JavaScript进行交互。例如:

package com.ljd.example;
 
public class JSInterface {
    @JavascriptInterface
    public void callAndroidMethod(){
        // do something
    }
}

我们需要确保这些js要调用的原生方法不能够被混淆,于是我们需要做如下处理:

-keepclassmembers class com.ljd.example.JSInterface {
    ;
}

4,对含有反射类的处理

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

5,对于第三方依赖库的处理
这个取决于第三方的混淆策略。下面列举一些第三方依赖库的混淆策略。具体还以官方给出的为准:

# OkHttp3
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-dontwarn org.conscrypt.**
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
# Retrofit2
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Exceptions
# Butterknife
-keep public class * implements butterknife.Unbinder { public <init>(**, android.view.View); }
-keep class butterknife.*
-keepclasseswithmembernames class * { @butterknife.* <methods>; }
-keepclasseswithmembernames class * { @butterknife.* <fields>; }
# Gson
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
-keep class com.sunloto.shandong.bean.** { *; }
# 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.ImageHeaderParser$** {
  **[] $VALUES;
  public *;
}
#-keepresourcexmlelements manifest/application/meta-data@value=GlideModule
# AndroidEventBus
-keep class org.simple.** { *; }
-keep interface org.simple.** { *; }
-keepclassmembers class * {
    @org.simple.eventbus.Subscriber <methods>;
}
# Rxjava and RxAndroid
-dontwarn org.mockito.**
-dontwarn org.junit.**
-dontwarn org.robolectric.**
-keep class io.reactivex.** { *; }
-keep interface io.reactivex.** { *; }
-keep class com.squareup.okhttp.** { *; }
-dontwarn okio.**
-keep interface com.squareup.okhttp.** { *; }
-dontwarn com.squareup.okhttp.**
-dontwarn io.reactivex.**
-dontwarn retrofit.**
-keep class retrofit.** { *; }
-keepclasseswithmembers class * {
    @retrofit.http.* <methods>;
}
-keep class sun.misc.Unsafe { *; }
-dontwarn java.lang.invoke.*
-keep class io.reactivex.schedulers.Schedulers {
    public static <methods>;
}
-keep class io.reactivex.schedulers.ImmediateScheduler {
    public <methods>;
}
-keep class io.reactivex.schedulers.TestScheduler {
    public <methods>;
}
-keep class io.reactivex.schedulers.Schedulers {
    public static ** test();
}
-keepclassmembers class io.reactivex.internal.util.unsafe.*ArrayQueue*Field* {
    long producerIndex;
    long consumerIndex;
}
-keepclassmembers class io.reactivex.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
    long producerNode;
    long consumerNode;
}
-keepclassmembers class io.reactivex.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
    io.reactivex.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class io.reactivex.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
    io.reactivex.internal.util.atomic.LinkedQueueNode consumerNode;
}
-dontwarn io.reactivex.internal.util.unsafe.**
# Espresso
-keep class android.support.test.espresso.** { *; }
-keep interface android.support.test.espresso.** { *; }
# Annotation
-keep class android.support.annotation.** { *; }
-keep interface android.support.annotation.** { *; }
# RxLifeCycle
-keep class com.trello.rxlifecycle2.** { *; }
-keep interface com.trello.rxlifecycle2.** { *; }
# RxPermissions
-keep class com.tbruyelle.rxpermissions2.** { *; }
-keep interface com.tbruyelle.rxpermissions2.** { *; }
# RxCache
-dontwarn io.rx_cache2.internal.**
-keep class io.rx_cache2.internal.Record { *; }
-keep class io.rx_cache2.Source { *; }
-keep class io.victoralbertos.jolyglot.** { *; }
-keep interface io.victoralbertos.jolyglot.** { *; }
# Canary
-dontwarn com.squareup.haha.guava.**
-dontwarn com.squareup.haha.perflib.**
-dontwarn com.squareup.haha.trove.**
-dontwarn com.squareup.leakcanary.**
-keep class com.squareup.haha.** { *; }
-keep class com.squareup.leakcanary.** { *; }
# Marshmallow removed Notification.setLatestEventInfo()
-dontwarn android.app.Notification
# Greendao
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties
-dontwarn org.greenrobot.greendao.database.**
# If you do not use Rx:
#-dontwarn rx.**
# ARouter
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}
-keep interface * implements com.alibaba.android.arouter.facade.template.IProvider
-keep class * implements com.alibaba.android.arouter.facade.template.IProvider
# Bugly
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
# BaseRecyclerViewAdapterHelper
-keep class com.chad.library.adapter.** {
*;
}
-keep public class * extends com.chad.library.adapter.base.BaseQuickAdapter
-keep public class * extends com.chad.library.adapter.base.BaseViewHolder
-keepclassmembers  class **$** extends com.chad.library.adapter.base.BaseViewHolder {
     <init>(...);
}

在Android中配置ProGuard模板代码

对于ProGuard的配置在下面给出代码的通用部分,在使用的时候直接复制即可,之后根据自己的项目进行添加一些自己项目中所需要保留的内容,以及所依赖第三方库的处理即可。

#############################################
#
# 对于一些基本指令的添加
#
#############################################
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
-dontskipnonpubliclibraryclassmembers
-dontpreverify
-keepattributes *Annotation*,InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
-optimizations !code/simplification/cast,!field/*,!class/merging/*
 
 
#############################################
#
# Android开发中一些需要保留的公共部分
#
#############################################
 
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-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 class android.support.** {*;}
-keep class **.R$* {*;}
-keepclasseswithmembernames class * {
    native ;
}
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
-keep public class * extends android.view.View{
    *** get*();
    void set*(***);
    public (android.content.Context);
    public (android.content.Context, android.util.AttributeSet);
    public (android.content.Context, android.util.AttributeSet, int);
}
-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();
}
-keepclassmembers class * {
    void *(**On*Event);
}
 
#webView处理,项目中没有使用到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);
}
 
#############################################
#
# 项目中特殊处理部分
#
#############################################
 
#-----------处理反射类---------------
 
......
 
#-----------处理js交互---------------
 
......
 
#-----------处理实体类---------------
 
......
 
#-----------处理第三方依赖库---------
 
......

注意:
在我们打包的时候,会发现很多could not reference class之类的warning信息。如果我们能够确认App在运行中和那些引用没有什么关系,可以添加-dontwarn标签。如-dontwarn org.apache.harmony.**。
  我们不要使用-ignorewarnings语句,这个会忽略所有警告,会有很多潜在的风险。

猜你喜欢

转载自blog.csdn.net/gpf1320253667/article/details/84872405
今日推荐