ProGuard

Xamarin.Android ProGuard 是一个 Java 类文件压缩器、优化器和预验证器。 它会检测和删除未使用的代码,分析和优化字节码。 本指南阐释了 ProGuard 的工作原理、如何在项目中启用它,以及如何进行配置。 同时提供了几个 ProGuard 配置示例。

概述

ProGuard 从打包的应用程序中检测并删除未使用的类、字段、方法和属性。 它甚至可对引用的库执行相同操作(这有助于避免 64k 引用限制)。 Android SDK 中的 ProGuard 工具还将优化字节码和删除未使用的代码说明。 ProGuard 读取输入文件,然后将其压缩、优化和预验证,并将结果写入一个或多个输出文件

ProGuard 使用以下步骤处理输入 APK:

  1. 收缩步骤 – ProGuard 以递归方式确定使用哪些类和类成员。 将丢弃所有其他类和类成员。

  2. 优化步骤 – ProGuard 进一步优化代码。 在其他优化中,可将非入口点的类和方法设置为私有、静态或最终,可删除未使用的参数,并且可内联一些方法。

  3. 模糊处理步骤 – 在本机 Android 开发中,ProGuard 重命名不是入口点的类和类成员。 保留入口点,确保它们仍可通过其原始名称访问。 但是,Xamarin.Android 并不支持此步骤,因为该应用是使用中间语言 (IL) 编译的。

  4. Preverification 步骤 – 在运行时之前对 Java 字节码执行检查,并为 Java VM 的利益批注类文件。 只有此步骤无需知道入口点。

上述每个步骤均可选。 Xamarin.Android ProGuard 要使用其中的部分步骤,详见下一节。

Xamarin.Android 中的 ProGuard

Xamarin.Android ProGuard 配置不会模糊处理 APK。 事实上,无法通过 ProGuard 进行模糊处理(即使使用自定义配置文件)。 因此,Xamarin.Android 的 ProGuard 只执行压缩优化步骤:

收缩和优化步骤

使用 ProGuard 前,必须知道其在 Xamarin.Android 生成过程中的工作原理。 此过程使用两个独立的步骤:

  1. Xamarin Android 链接器

  2. ProGuard

接下来将说明上述各步骤。

链接器步骤

Xamarin.Android 链接器使用应用程序的静态分析来确定以下内容:

  • 实际使用的程序集。

  • 实际使用的类型。

  • 实际使用的成员。

将始终在 ProGuard 步骤前运行链接器。 因此,链接器可剥因此,链接器可剥离想要 ProGuard 在其上运行的程序集/类型/成员。 (若要详细了解 Xamarin.Android 中的链接,请参阅在 Android 上链接。)

ProGuard 步骤

链接器步骤成功完成后,运行 ProGuard 删除未使用的 Java 字节码。 此步骤用于优化 APK。

使用 ProGuard

必须先启用 ProGuard,才可在应用项目中使用它。 接下来,可让 Xamarin.Android 生成过程使用默认的 ProGuard 配置文件,也可自行创建自定义配置文件供 ProGuard 使用。

启用 ProGuard

在应用项目中使用以下步骤启用 ProGuard:

  1. 确保项目设置为 发布 配置 (这一点很重要,因为必须运行链接器才能运行 ProGuard) :

    选择“发布配置”

  2. “属性”“Android 选项”>窗口上的“代码收缩器”下拉列表中选择“ProGuard”:

    已选择 Proguard 代码收缩器

对于大多数 Xamarin.Android 应用,Xamarin.Android 提供的默认 ProGuard 配置文件足以删除所有(仅)未使用的代码。 若要查看默认的 ProGuard 配置,请在 obj\Release\proguard\proguard_xamarin.cfg 中打开文件。

以下示例说明了典型生成的 proguard_xamarin.cfg 文件

# This is Xamarin-specific (and enhanced) configuration.

-dontobfuscate

-keep class mono.MonoRuntimeProvider { *; <init>(...); }
-keep class mono.MonoPackageManager { *; <init>(...); }
-keep class mono.MonoPackageManager_Resources { *; <init>(...); }
-keep class mono.android.** { *; <init>(...); }
-keep class mono.java.** { *; <init>(...); }
-keep class mono.javax.** { *; <init>(...); }
-keep class opentk.platform.android.AndroidGameView { *; <init>(...); }
-keep class opentk.GameViewBase { *; <init>(...); }
-keep class opentk_1_0.platform.android.AndroidGameView { *; <init>(...); }
-keep class opentk_1_0.GameViewBase { *; <init>(...); }

-keep class android.runtime.** { <init>(***); }
-keep class assembly_mono_android.android.runtime.** { <init>(***); }
# hash for android.runtime and assembly_mono_android.android.runtime.
-keep class md52ce486a14f4bcd95899665e9d932190b.** { *; <init>(...); }
-keepclassmembers class md52ce486a14f4bcd95899665e9d932190b.** { *; <init>(...); }

# Android's template misses fluent setters...
-keepclassmembers class * extends android.view.View {
   *** set*(***);
}

# also misses those inflated custom layout stuff from xml...
-keepclassmembers class * extends android.view.View {
   <init>(android.content.Context,android.util.AttributeSet);
   <init>(android.content.Context,android.util.AttributeSet,int);
}

下一节将介绍如何创建自定义 ProGuard 配置文件。

自定义 ProGuard

或者,可添加自定义 ProGuard 配置文件,实现对 ProGuard 工具的更多掌控。 例如,你可能想就要保留的类显式通知 ProGuard。 为此,请新建 .cfg 文件,并在解决方案资源管理器的“属性”窗格中应用 ProGuardConfiguration 生成操作:

ProguardConfiguration 生成操作已选中

请记住,该配置文件不会替换 Xamarin.Android proguard_xamarin.cfg 文件,因为 ProGuard 将使用这两者

有些情况下,ProGuard 可能无法正确分析应用程序,它可能会删除应用程序实际需要的代码。 若发生此情况,可将 -keep 行添加到自定义 ProGuard 配置文件:

-keep public class MyClass

本例中,MyClass 设置为想让 ProGuard 跳过的类的实际名称。

还可使用 [Register] 注释来注册自己的名称,并使用这些名称来自定义 ProGuard 规则。 可为 Adapter、View、BroadcastReceiver、Service、ContentProvider、Activity 和 Fragment 注册名称。 有关使用 [Register] 自定义属性的详细信息,请参阅使用 JNI

ProGuard 选项

ProGuard 提供了许多选项,可配置实现更精细的操作控制。 ProGuard 手册提供了 ProGuard 用法的完整参考文档。

Xamarin.Android 支持以下 ProGuard 选项:

Xamarin.Android 忽略以下选项:

ProGuard 和 Android Nougat

如果尝试在 Android 7.0 或更高版本上使用 ProGuard,必须下载较新版本的 ProGuard,因为 Android SDK 未提供与 JDK 1.8 兼容的新版本。

可使用此 NuGet 包安装 proguard.jar 的较新版本。 有关更新默认 Android SDK proguard.jar 的详细信息,请参阅此堆栈溢出讨论。

可在 SourceForge 页面找到所有版本的 ProGuard。

ProGuard 配置示例

下面列出了两个 ProGuard 配置文件示例。 请注意,在这些情况下,Xamarin.Android 生成过程将提供输入输出 jar。 因此,可专注于其他选项,如 -keep

一个简单的 Android 活动

下例演示了一个简单 Android 活动的配置:

-injars  bin/classes
-outjars bin/classes-processed.jar
-libraryjars /usr/local/java/android-sdk/platforms/android-9/android.jar

-dontpreverify
-repackageclasses ''
-allowaccessmodification
-optimizations !code/simplification/arithmetic

-keep public class mypackage.MyActivity

一个完整的 Android 应用程序

下例演示了一个完整 Android 应用的配置:

-injars  bin/classes
-injars  libs
-outjars bin/classes-processed.jar
-libraryjars /usr/local/java/android-sdk/platforms/android-9/android.jar

-dontpreverify
-repackageclasses ''
-allowaccessmodification
-optimizations !code/simplification/arithmetic
-keepattributes *Annotation*

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

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

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

-keepclassmembers class * implements android.os.Parcelable {
static android.os.Parcelable$Creator CREATOR;
}

-keepclassmembers class **.R$* {
public static <fields>;
}

ProGuard 和 Xamarin.Android 生成过程

以下部分介绍了 ProGuard 在Xamarin.Android 发布生成期间的运行方式。

ProGuard 正在运行什么命令?

ProGuard 只是随附 Android SDK 提供的 .jar。 因此,它会在命令中被调用:

java -jar proguard.jar options ...

ProGuard 任务

ProGuard 任务位于 Xamarin.Android.Build.Tasks.dll 程序集中。 它是 _CompileToDalvikWithDx 目标的一部分,后者则是 _CompileDex 目标的一部分。

以下列表提供了使用文件>新建项目创建新项目后生成的默认参数示例:

ProGuardJarPath = C:\Android\android-sdk\tools\proguard\lib\proguard.jar
AndroidSdkDirectory = C:\Android\android-sdk\
JavaToolPath = C:\Program Files (x86)\Java\jdk1.8.0_92\\bin
ProGuardToolPath = C:\Android\android-sdk\tools\proguard\
JavaPlatformJarPath = C:\Android\android-sdk\platforms\android-25\android.jar
ClassesOutputDirectory = obj\Release\android\bin\classes
AcwMapFile = obj\Release\acw-map.txt
ProGuardCommonXamarinConfiguration = obj\Release\proguard\proguard_xamarin.cfg
ProGuardGeneratedReferenceConfiguration = obj\Release\proguard\proguard_project_references.cfg
ProGuardGeneratedApplicationConfiguration = obj\Release\proguard\proguard_project_primary.cfg
ProGuardConfigurationFiles

    {sdk.dir}tools\proguard\proguard-android.txt;
    {intermediate.common.xamarin};
    {intermediate.references};
    {intermediate.application};
    ;

JavaLibrariesToEmbed = C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\MonoAndroid\v7.0\mono.android.jar
ProGuardJarInput = obj\Release\proguard\__proguard_input__.jar
ProGuardJarOutput = obj\Release\proguard\__proguard_output__.jar
DumpOutput = obj\Release\proguard\dump.txt
PrintSeedsOutput = obj\Release\proguard\seeds.txt
PrintUsageOutput = obj\Release\proguard\usage.txt
PrintMappingOutput = obj\Release\proguard\mapping.txt

下一示例展示了从 IDE 运行的典型 ProGuard 命令:

C:\Program Files (x86)\Java\jdk1.8.0_92\\bin\java.exe -jar C:\Android\android-sdk\tools\proguard\lib\proguard.jar -include obj\Release\proguard\proguard_xamarin.cfg -include obj\Release\proguard\proguard_project_references.cfg -include obj\Release\proguard\proguard_project_primary.cfg "-injars 'obj\Release\proguard\__proguard_input__.jar';'C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\MonoAndroid\v7.0\mono.android.jar'" "-libraryjars 'C:\Android\android-sdk\platforms\android-25\android.jar'" -outjars "obj\Release\proguard\__proguard_output__.jar" -optimizations !code/allocation/variable

疑难解答

文件问题

当 ProGuard 读取其配置文件时,可能会显示以下错误消息:

Unknown option '-keep' in line 1 of file 'proguard.cfg'

此问题通常发生在 Windows 上,因为 .cfg 文件编码错误。 ProGuard 不能处理字节顺序标记 (BOM),它可能出现在文本文件中。 如果存在 BOM,ProGuard 将退出并显示上述错误。

为避免此问题,请从允许不使用 BOM 保存文件的文本编辑器中编辑自定义配置文件。 若要解决此问题,请确保文本编辑器的编码设置为 UTF-8。 例如,保存文件时,文本编辑器 Notepad++ 可通过选择“编码”>“无 BOM 时采用 UTF-8 编码”在没有 BOM 的情况下保存文件。

其他问题

ProGuard 疑难解答页面讨论了使用 ProGuard 时可能遇到的常见问题(及解决方案)。

总结

本指南阐释了 ProGuard 在 Xamarin.Android 中的工作原理、如何在应用项目中启用它,以及如何进行配置。 提供了示例 ProGuard 配置,并描述了常见问题的解决方案。 有关 ProGuard 工具和 Android 的详细信息,请参阅压缩代码和资源