什么是ProGuard?为什么我们要使用它?
为了使你的Android应用更小,可以在发布版本中启用缩减功能。通过使用R8来进行缩减,可以删除未使用的代码和资源,缩短类名并进行优化。Android Gradle插件(v3.4.0+)使用R8来执行这些任务。它实现了以下功能:
代码缩减:检测并安全地删除应用及其库中未使用的代码,有助于避免64k引用限制。
资源缩减:从应用中删除未使用的资源,与代码缩减协同工作。
混淆:缩短类和成员名称,减小DEX文件的大小。
优化:进一步分析和优化你的代码。
在构建发布版本时,R8可以处理这些任务,并且如果需要,可以通过ProGuard规则自定义其行为,而无需更改现有规则。
Android项目中的配置
在使用Android Studio 3.4或Android Gradle插件3.4.0及更高版本时,R8是将Java字节码转换为Android的DEX格式的默认编译器。然而,在Android Studio中创建新项目时,默认情况下不启用缩减、混淆和代码优化。这是因为这些优化可能会增加构建时间,并且如果没有正确自定义,可能会引入错误。
为了确保在发布之前对你的应用进行平滑的最终版本测试,建议启用缩减、混淆和优化。你可以通过在项目级别的构建脚本中包含特定配置来实现这一点。
在Android项目中,ProGuard规则文件位于以下路径:
app模块(或你的应用名称)-> proguard-rules.pro文件
该文件位于“app”模块的根目录中,包含了ProGuard用于保护和优化应用的规则。如果需要自定义规则或进行修改,可以在此文件中创建或编辑它们。
android {
buildTypes {
getByName("release") {
// 仅为你的项目的发布构建类型启用缩减、混淆和优化。
// 确保使用的变体为`isDebuggable=false`。
isMinifyEnabled = true
// 启用资源缩减,由Android Gradle插件执行。
isShrinkResources = true
// 包括与Android Gradle插件一起打包的默认ProGuard规则文件。
// 要了解更多,请查看有关R8配置文件的部分。
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
...
}
你可以通过在模块的构建脚本中的proguardFiles
属性中指定其他文件的规则来包含其他文件中的规则。你还可以通过在相应的productFlavor
块中包含它们来添加特定变体的规则。
flavorDimensions.add("proguardexample")
productFlavors {
create("proguardflavor1") {
...
}
create("proguardflavor2") {
proguardFile("proguardflavor2-rules.pro")
}
}
构建类型的ProGuard
发布构建:发布构建是应用开发的关键阶段。ProGuard对于这种构建类型至关重要,它优化字节码,提高性能,减小应用的大小。它通过合并和优化代码,同时混淆代码库的结构,使得潜在的恶意反编译尝试变得更加困难。
调试构建:在开发过程中,调试构建类型用于编码和调试的目的。建议在此阶段不要使用ProGuard,因为它应用的优化和代码混淆可能会复杂化调试过程。
自定义构建类型:对于具有额外自定义构建类型(例如,staging、testing)的项目,考虑使用ProGuard是很重要的。例如,将ProGuard应用于staging构建可以使其在性能和大小方面更接近发布构建,增强其用于测试和验证的效用。
ProGuard规则
ProGuard规则是用于指定ProGuard工具如何处理代码缩减、混淆和优化的配置。它们对于增强应用的安全性、减小代码大小和提高性能至关重要。
以下是一些常见的ProGuard规则:
保留规则:为了防止特定类、方法或字段被删除或混淆,你可以使用"keep"规则。例如:
-keep class com.example.MyClass { *; }
-keep class com.example.MyClass { void myMethod(); }
保留数据类:数据类通常用于序列化和反序列化,例如保存或加载数据时使用。在ProGuard优化过程中修改这些类可能导致保存的数据恢复不正确,从而导致应用程序运行和数据访问的重大问题。
保留名称:为了在混淆过程中保留特定类、方法或字段的名称,可以使用"keepnames"规则。例如:
-keepnames class com.example.MyClass
-keepnames class com.example.MyClass { void myMethod(); }
保留属性:为了保留某些属性或注解,可以使用"keepattributes"规则。例如:
-keepattributes Signature
优化规则:为了启用或禁用特定的优化规则,可以使用"optimization"规则。
-optimizations !code/simplification/arithmetic
为了改善混淆,还可以使用“assumenosideeffects”规则来指示特定方法没有作用。例如:
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** e(...);
}
ProGuard 规则对于根据应用程序的要求定制代码优化过程至关重要,确保其保持安全、高效和小尺寸。
使用 ProGuard Playground 探索 ProGuard
ProGuard 是 Android 开发中用于代码缩减、混淆和优化的强大工具。然而,尝试 ProGuard 规则可能具有挑战性,特别是如果您不熟悉该工具或不想在本地设置它。这就是 ProGuard Playground 发挥作用的地方。
ProGuard Playground 位于 https://playground.proguard.com/,是一个在线平台,允许您测试 ProGuard 规则,而无需在本地计算机上安装 ProGuard。它充当沙箱环境,可以在其中尝试不同的 ProGuard 配置并立即查看它们对代码的影响。
它的工作原理如下: 规则测试:可以直接在网站上添加、修改或删除 ProGuard 规则。这些规则规定了 ProGuard 在优化过程中应如何处理代码。
即时反馈:当更改规则时,ProGuard Playground 会提供有关这些更改如何影响您的代码的即时反馈。可以看到代码大小如何变化以及类名的混淆程度如何。
学习工具:无论是 ProGuard 新手还是经验丰富的开发人员,ProGuard Playground 都是一个有价值的学习工具。它可以让您了解不同的规则如何影响代码,并帮助您微调 Android 项目的 ProGuard 配置。
无需本地设置:最好的部分是您可以使用 ProGuard Playground,而无需在本地计算机上设置 ProGuard 的麻烦。这是一种即时实验和学习 ProGuard 规则的便捷方法。
总之,ProGuard Playground 简化了使用 ProGuard 的过程。对于想要优化代码、增强安全性和减小应用程序大小而无需本地安装的 Android 开发人员来说,这是一个极好的资源。访问 ProGuard Playground 轻松探索和完善您的 ProGuard 规则。
探索 Android ProGuard 输出文件
当您使用 ProGuard 来优化您的 Android 应用程序时,它会生成一组具有特定用途的输出文件。让我们仔细看看这些文件及其包含的内容:
-
•
configuration.txt
:此文件总结了优化过程中应用的 ProGuard 配置规则。它提供了有关使用哪些规则来保护和优化代码的见解。 -
•
mapping.txt
:映射文件对于调试至关重要。它将混淆的类和方法名称映射回其原始的、人类可读的名称。此映射简化了调试混淆代码时问题的识别。 -
•
resources.txt
:此文件列出了优化过程中删除的资源(例如可绘制对象、布局等)。它可以帮助您跟踪哪些资源不再包含在您的应用程序中。 -
•
seeds.txt
:种子文件包含 ProGuard 应保留的类和成员的列表,确保它们不会被删除或混淆。这对于保护对应用程序功能至关重要的关键代码部分特别有用。 -
•
usage.txt
:此文件记录 ProGuard 认为未使用且适合删除的类、方法和字段。它提供了有关 ProGuard 识别为潜在可移动代码的见解。
这些文件是在 ProGuard 配置指定的目录中生成的。默认情况下,它们在构建过程后放置在 Android 项目的 build/outputs/mapping
目录中。了解这些文件的内容和位置对于 Android 应用开发工作流程中的有效调试、代码优化和资源管理至关重要。
将 ProGuard 输出文件加载到 Android Studio 中的“Analyze APK”中
当使用 ProGuard 优化构建类型时,理解和分析输出至关重要。 Android Studio 提供了一个名为“Analyze APK”的强大工具,允许开发人员深入研究优化的代码和资源,深入了解 ProGuard 如何影响应用程序。
好处和目的:
将 ProGuard 输出文件加载到“Analyze APK”有几个关键目的:
-
• 了解代码更改:通过上传 ProGuard 生成的输出文件,开发人员可以可视化 ProGuard 对代码库所做的转换。这有助于理解所应用的混淆和优化,帮助调试并确保预期的修改。
-
• 资源检查:“分析APK”可以检查优化后的资源,让开发者全面了解资产变化。这对于确认必要资源得到适当优化并识别任何潜在问题非常宝贵。
访问和使用“分析APK”功能:
-
• 生成 ProGuard 输出文件:在构建期间运行 ProGuard 以生成必要的输出文件(例如,
mapping.txt
、seeds.txt
、usage.txt
),其中包含有关混淆和优化的重要信息。 -
• 在 Android Studio 中打开“Analyze APK”:在 Android Studio 中,导航到“Build”菜单,然后选择“Build Bundle(s) / APK(s)”并选择“Build APK(s)”。构建完成后,再次导航到“构建”菜单,这次选择“分析 APK”。
-
• 选择ProGuard输出文件:在“分析APK”对话框中,浏览并选择ProGuard生成的输出文件,例如mapping.txt、seeds.txt和usage.txt。 Android Studio 随后将处理并加载数据。
-
• 分析和解释:Android Studio 将详细介绍 APK 结构,突出显示 ProGuard 优化带来的代码和资源变化。使用此分析来深入了解 ProGuard 如何影响应用程序并验证预期的更改。
通过利用 ProGuard 输出文件的“分析 APK”功能,开发人员可以有效评估和验证构建过程中实现的优化和混淆,确保应用程序的健壮和高效。
追溯堆栈轨迹
R8 处理的代码经历了一些更改,这些更改可能会通过更改行号、通过内联或大纲进行优化以及显着混淆类和方法名称来使堆栈跟踪变得不那么直观。
为了确保正确的回溯,建议添加以下 ProGuard 规则。
-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile
-keep public class * extends java.lang.Exception
LineNumberTable
属性保留方法中的位置信息,以便在堆栈跟踪中打印这些位置。
SourceFile
属性确保所有潜在的运行时实际打印位置信息。
-renamesourcefileattribute
指令将堆栈跟踪中的源文件名设置为 SourceFile 。
-keep public class * extends java.lang.Exception: 在 Android
应用程序中,异常处理对于错误检测和调试至关重要。当在异常处理中使用从 Exception 类派生的自定义异常时,重要的是不要混淆这些类。混淆会使错误检测和问题理解变得复杂。
在 Google Play 上发布应用程序时,请包含每个版本的mapping.txt
文件。对于 Android App Bundle,会自动包含此文件。然后,Google Play 将追溯用户报告的堆栈跟踪,以便在 Play Console 中审查问题。
注意:每次构建项目时,studio生成的mapping.txt 文件都会被覆盖,因此每次发布新版本时都必须小心保存副本。通过为每个发布版本保留mapping.txt文件的副本,您将能够追溯用户是否从旧版本的应用程序提交了模糊的堆栈跟踪。
实施 ProGuard 时的实际问题
在 ProGuard 的实施过程中,出现了几个问题。下面的部分中总结他们的解决方案或解决方法
OkHttp 库的问题
使用 OkHttp 时发生错误。错误原因及其解决方案可以在以下链接找到:https://github.com/square/okhttp/issues/7777 该问题可以通过添加以下规则来解决:
# OkHttp platform used only on JVM and when Conscrypt and other security providers are available.
-dontwarn okhttp3.internal.platform.**
-dontwarn org.conscrypt.**
-dontwarn org.bouncycastle.**
-dontwarn org.openjsse.**
Hilt Library 的问题
我们需要保留类名的原因是因为多重绑定映射由类名上的字符串作为键控,例如@IntoMap
@StringKey(“some.pkg.MyViewModel”)
。问题是,当类被混淆时,@StringKey
中的类名不再被混淆,然后键不再匹配。
因此,ProGuard 不会使用 Hilt 注释来混淆类名。有关建议的解决方案和有关问题的更多信息,您可以在此处阅读: https://github.com/google/dagger/issues/3197
http 调用的问题
为了确保启用 ProGuard 后 HTTP 调用能够正常运行,需要定义特定的规则。 ProGuard 配置包括保留基本元素的通用签名的指令,例如像 Retrofit2.Call
这样的接口和像 Retrofit2.Response
这样的类。此外,还特别注意 kotlin.coroutines.Continuation
以保留其功能,特别是在处理挂起函数和类型参数时。这些精心设计的规则旨在防止由于 ProGuard 的混淆和收缩进程而可能出现的 java.lang.ClassCastException
等问题。通过保留必要的签名和结构,即使存在 ProGuard 优化,开发人员也可以成功实现 HTTP 调用。
https://github.com/square/retrofit/issues/3751
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response
# With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument
# is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
自定义视图
我有几个自定义视图(在 xml 布局中引用)。他们的名字一定也不能混淆。
这是为了让视图的动画能够正常运行,这就是 ProGuard 默认包含此规则的原因。
keep setters in Views so that animations can still work.
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}