Android代码混淆

Android代码混淆,第1张

Android代码混淆 混淆简介

代码混淆(Obfuscated code)是将程序中的代码以某种规则转换为难以阅读和理解的代码的一种行为。

混淆的好处
  • 令 APK 难以被逆向工程,即很大程度上增加反编译的成本;
  • 在打包时移除无用资源,显著减少 APK 体积;
  • 对字节码进行优化,移除无用指令,应用运行更快;
  • 以变通方式避免 Android 中常见的 64k 方法数引用的限制。
Android 当中的混淆

在 Android 中,我们平常所说的"混淆"其实有两层意思,一个是 Java 代码的混淆,另外一个是资源的压缩。其实这两者之间并没有什么关联,只不过习惯性地放在一起来使用。那么,说了这么多,Android 平台上到底该如何开启混淆呢?

启用混淆
......
  
android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

以上就是开启混淆的基本 *** 作了,通过 minifyEnabled 设置为 true 来开启混淆。同时,可以设置 shrinkResources 为 true 来开启资源的压缩。不难看出,我们一般在打 release 包时才启用混淆,因为混淆会增加额外的编译时间,所以不建议在 debug 模式下启用。此外,需要注意的是:只有在启用混淆的前提下开启资源压缩才会有效!以上代码中的 proguard-android.txt 表示 Android 系统为我们提供的默认混淆规则文件,而 proguard-rules.pro 则是我们想要自定义的混淆规则。

代码混淆

其实,Java 平台为我们提供了 Proguard 混淆工具来帮助我们快速地对代码进行混淆。根据 Java 官方介绍,Proguard 对应的具体中文定义如下:

  • 它是一个包含代码文件压缩、优化、混淆和校验等功能的工具
  • 它能够检测并删除无用的类、变量、方法和属性
  • 它能够优化字节码并删除未使用的指令
  • 它能够将类、变量和方法的名字重命名为无意义的名称从而达到混淆效果
  • 最后,它还会校验处理后的代码,主要针对 Java 6 及以上版本和 Java ME
资源压缩

Android 中,编译器为我们提供了另外一项强大的功能:资源的压缩。资源压缩能够帮助我们移除项目及依赖仓库中未使用到的资源,有效地降低了apk包的大小。由于资源压缩与代码混淆是协同工作,所以,如果需要开启资源的压缩,切记要先开启代码混淆,否则会出现以下问题:

ERROR: Removing unused resources requires unused code shrinking to be turned on. See http://d.android.com/r/tools/shrink-resources.html for more information.
Affected Modules: app
混淆规则详解 混淆设置参数
-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     # 防止拥有该成员的类和类成员被混淆;
类:
  • 具体的类
  • 访问修饰符 → public、private、protected
  • 通配符(*) → 匹配任意长度字符,但不包含包名分隔符(.)
  • 通配符(**) → 匹配任意长度字符,且包含包名分隔符(.)
  • extends → 匹配实现了某个父类的子类
  • implements → 匹配实现了某接口的类
  • $ → 内部类
成员:
  • 匹配所有构造器 →
  • 匹配所有域 →
  • 匹配所有方法 →
  • 访问修饰符 → public、private、protected
  • 除了 * 和 ** 通配符外,还支持 *** 通配符,匹配任意参数类型
  • … → 匹配任意长度的任意类型参数,如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 ;
}

# 不混淆指定类的所有字段
-keep cn.coderpig.myapp.example.Test {
    public ;
}

# 不混淆指定类的所有构造方法
-keep cn.coderpig.myapp.example.Test {
    public ;
}

# 不混淆指定参数作为形参的方法
-keep cn.coderpig.myapp.example.Test {
    public (java.lang.String);
}

# 不混淆类的特定方法
-keep cn.coderpig.myapp.example.Test {
    public test(java.lang.String);
}

# 不混淆native方法
-keepclasseswithmembernames class * {
    native ;
}

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

#不混淆资源类
-keepclassmembers class **.R$* {
    public static ;
}

# 不混淆自定义控件
-keep public class * entends android.view.View {
    *** get*();
    void set*(***);
    public ;
}

# 不混淆实现了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这两个方法不可混淆(反射调用);
  • ⑧ 继承Parceable和Serializable等可序列化的类不可混淆;
  • ⑨ 第三方库或SDK,请参考第三方提供的混淆规则,没提供的话,建议第三方包全部不混淆;
混淆规则的叠加

混淆规则的来源不止主模块里的proguard-rules.pro,还有这些:

  1. /proguard-rules.pro

不止主模块有proguard-rules.pro,子模块也可以有,因为规则是叠加的,故某个模块的配置都可能影响其它模块。

  1. proguard-android-optimize.txt

AGP编译时生成,其中包含了对大多数Android项目都有用的规则,并且启用 @Keep* 注解。
AGP提供的规则文件还有proguard-defaults.txt或proguard-android.txt,可通过 getDefaultProguardFile 进行设置,不过建议还是使用这个文件(多了些优化配置)。

  1. /build/intermediates/proguard-rules/debug/aapt_rules.txt

自动生成,AAPT2会根据对应用清单中的类、布局及其他应用资源的引用,生成保留规则,如不混淆每个Activity。

  1. AAR库 → /proguard.txt
  2. Jar库 → /meta-INF/proguard/

如果想查看所有规则 叠加后的混淆规则,可在主目录的 proguard-rules.pro 添加下述配置:

# 输出所有规则叠加后的混淆规则
-printconfiguration ./build/outputs/mapping/full-config.txt
资源压缩

资源压缩其实分为两步:资源合并 与 资源移除,前者无论是否配置 shrinkResources true,AGP构建APK时都会执行,当存在两个或更多名称相同的资源才会进行资源合并,AGP会从重复项中选择 优先级更高 的文件,并只将此资源传递给AAPT2,以供在APK中分发。

级联优先顺序:

依赖项 → 主资源 → 渠道 → 构建类型

比如:重复资源存在于主资源及渠道中,Gradle会选择渠道中的资源;

但如果重复资源在同一层次出现,如src/main/res/和src/main/res2中有重复资源,Gradle就会报资源合并错误。

对应打包Task中的 processDebugResources,将aapt编译后的flat产物和合并后的清单文件进行链接处理生成 ._ap 文件(包含资源文件、清单文件、资源关系映射表文件resources.arsc) 及 R.java 文件(保存了资源类型、资源名称及地址的映射关系)。

说完资源合并,接着说下资源移除,开启资源压缩后,所有未被使用的资源默认会被移除,如果你想定义那些资源需要保留,可以在 res/raw/ 路径下创建一个xml文件,如 keep.xml,配置示例如下 (此文件不会打包到APK中,支持通配符*,此类文件可有多份):



    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
    
    tools:discard="@layout/unused2"
    
    
    tools:shrinkMode="strict"/>

另外,还可以在build.gradle中添加 resConfigs 来移除不需要的备用资源文件,如只保留中文:

android {
    ...
    defaultConfig {
        resConfigs "zh-rCN" // 不用支持国际化只需打包中文资源
    }
}    
自定义混淆字典

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5574183.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-14
下一篇 2022-12-14

发表评论

登录后才能评论

评论列表(0条)

保存