压缩代码和资源

为了使APK文件尽可能小,您应该启用缩小以删除您的发布版本中未使用的代码和资源。 下面描述如何做,以及如何指定在构建过程中要保留或丢弃的代码和资源。

通过ProGuard实现压缩代码是合适的,ProGuard从您的打包应用程序中检测和删除未使用的类,字段,方法和属性,包括来自包含的代码库(使其成为处理64k参考限制的有价值的工具)。 ProGuard还优化字节码,删除未使用的代码指令,并使用短名称混淆剩余的类,字段和方法。 混淆代码使您的APK难以反向工程,这在您的应用使用安全敏感功能(例如许可验证)时尤其有用。

通过the Android Plugin for Gradle实现压缩资源是合适的,它从您的打包应用程序中删除未使用的资源,包括代码库中未使用的资源。 它与代码缩减结合使用,以便一旦未使用的代码被删除,任何不再被引用的资源也可以被安全地删除。

本文档中的功能依赖于:

  1. SDK Tools 25.0.10 or higher
  2. Android Plugin for Gradle 2.0.0 or higher

压缩代码

要使用ProGuard启用压缩代码,请将minifyEnabled true添加到build.gradle文件中的相应构建类型。

请注意,代码缩减会减慢构建时间,因此,如果可能,应避免在调试版本上使用它。 不过,重要的是,您必须在用于测试的最终APK上启用代码缩减,因为如果您没有足够自定义要保留的代码,它可能会导致错误。

例如,来自build.gradle文件的以下代码段使构建release版本时压缩代码:

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

注意:使用Instant Run时Android Studio会禁用ProGuard。

除了minifyEnabled属性,proguardFiles属性定义了ProGuard规则:

  1. getDefaultProguardFile('proguard-android.txt')方法从Android SDK tools / proguard /文件夹获取默认的ProGuard设置。
    提示:对于更多的代码缩减,请尝试位于同一位置的proguard-android-optimize.txt文件。 它与proguard-android.txt包括相同的ProGuard规则,但是还包含在字节码级别(内部和跨方法 )执行分析的优化来进一步减少您的APK大小,并帮助它运行更快。
  2. proguard-rules.pro文件是您可以添加自定义ProGuard规则的位置。 默认情况下,此文件位于模块的根目录(在build.gradle文件旁边)。

要添加特定于每个build variant的更多ProGuard规则,请在相应的productFlavor块中添加另一个proguardFiles属性。 例如,以下Gradle文件将flavor2-rules.pro添加到flavor2 product flavor中。 现在flavor2使用三个ProGuard规则,因为来自release块的那些规则也被应用。

android {
    ...
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),
                   'proguard-rules.pro'
        }
    }
    productFlavors {
        flavor1 {
        }
        flavor2 {
            proguardFile 'flavor2-rules.pro'
        }
    }
}

对于每次构建,ProGuard生成以下文件:

  1. dump.txt
    描述APK中所有类文件的内部结构。
  2. mapping.txt
    提供原始和混淆后的类,方法和字段名称之间的转换。
  3. seeds.txt
    列出未被混淆的类和成员。
  4. usage.txt
    列出从APK中移除的代码。

这些文件被保存在 /build/outputs/mapping/release/目录下。

定制要保留的代码

对于某些情况,默认的ProGuard配置文件(proguard-android.txt)就足够了,ProGuard删除所有未使用的代码。 然而,许多情况下,ProGuard很难正确分析,它可能会删除您的应用程序实际需要的代码。 可能会错误地删除代码的一些示例包括:

  1. 当应用程序引用一个仅来自于AndroidManifest.xml文件的类
  2. 当应用程序从Java Native Interface(JNI)调用方法时
  3. 当应用在运行时操作代码(如使用反射或内省)

应该通过测试应用程序来揭示由不适当删除的代码导致的任何错误,但也可以通过查看保存在 / build / outputs / mapping / release /中的usage.txt输出文件来检查删除的代码。

要修复错误并强制ProGuard保留某些代码,请在ProGuard配置文件中添加一个-keep行。 例如:

-keep public class MyClass

或者,您可以将@Keep注释添加到要保留的代码。 在类上添加@Keep会保持整个类不变。 将它添加到方法或字段上将保持方法/字段(和它的名称)以及类名称不变。 请注意,此注释仅在使用注释支持库时可用。

使用-keep选项时,您应该考虑许多因素; 有关自定义配置文件的更多信息,请阅读ProGuard手册。 “疑难解答”部分概述了在您的代码被删除时可能遇到的其他常见问题。

解码混淆的stack trace

ProGuard压缩代码后,读取stack trace是很困难(如果不是不可能),因为方法名称被混淆处理。 幸运的是,ProGuard每次运行时都会创建一个mapping.txt文件,它显示混淆后的名称与原始类,方法和字段名称之间的映射关系。 ProGuard将文件保存在 / build / outputs / mapping / release /目录中。

请注意,每次使用ProGuard创建release版本时,mapping.txt文件都会被覆盖,因此每次发布新release版本时都必须小心保存副本。 通过为每个 release build保留mapping.txt文件的副本,如果用户从旧版本的应用程序提交混淆的stack trace,您将能够调试问题。

在Google Play上发布app时,您可以为每个版本的APK上传mapping.txt档案。 然后,Google Play会从用户报告的问题中反混淆进入的stack traces,以便您可以在Google Play开发者控制台中查看这些stack traces。 有关详细信息,请参阅帮助中心文章,了解如何反混淆crash stack traces。

要将混淆的stack trace转换为可读的stack trace,请使用回溯脚本(Windows上为retrace.bat; Mac上为retrace.sh)。 它位于 / tools / proguard /目录中。 该脚本采用mapping.txt文件和您的stack trace,产生一个新的,可读的stack trace。 使用retrace工具的语法是:

retrace.bat|retrace.sh [-verbose] mapping.txt []

举例:

retrace.bat -verbose mapping.txt obfuscated_trace.txt

如果不指定stack trace文件,则retrace工具从标准输入读取。

压缩资源

压缩资源只能与压缩代码相结合。 代码压缩器删除所有未使用的代码后,资源压缩器可以识别应用程序仍在使用哪些资源。 尤其当您添加包含资源的代码库时,您必须删除未使用的库代码,以便库资源变为未引用,从而可由资源压缩器移除。

要启用资源压缩,请在build.gradle文件(同时设置minifyEnabled来启动代码压缩)中将shrinkResources属性设置为true。 例如:

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

如果你还没有使用代码压缩构建你的app,那么在启用shrinkResources移除资源之前,你可能需要先编辑proguard-rules.pro文件以保护动态执行和创建的类和方法(比如反射调用的代码)。

注意:资源缩小器当前不会删除在values/ 文件夹中定义的资源(例如strings, dimensions, styles, and colors)。 这是因为Android资源打包工具(AAPT)不允许Gradle插件为资源指定预定义版本。 有关详细信息,请参阅问题70869。

定制要保留的资源

如果有要保留或丢弃的特定资源,请使用tag在项目中创建一个XML文件,并在tools:discard属性中指定要保留的资源和在tools:keep属性中指定要丢弃的资源。 两个属性都接受以逗号分隔的资源名称列表。 您可以使用星号字符作为通配符。

举例:



将此文件保存在项目资源中,例如res / raw / keep.xml。 build时不会把这个文件打包到APK中。

当然可以直接删除资源时却指定要丢弃的资源可能看起来很愚蠢,但这在使用 build variants时可能很有用。 例如,您可以将所有资源放入公共项目目录,然后为每个build variant创建一个不同的keep.xml文件,当给定资源在代码中被使用(因此不会被缩小器删除),但是该资源实际上不会用于给定的build variant,那么该build variant的keep.xml文件应该指定该资源被丢弃。

启用strict reference检查

通常,资源压缩器可以精确地确定一个资源是否被使用。 但是如果代码中调用了Resources.getIdentifier()(或者任何一个库调用了这个方法),这意味着你的代码是基于动态生成的字符串查找资源名称。 当你这样做时,资源压缩器在默认情况下会防御性地运行,并将匹配的名称格式的所有资源标记为可能已使用且无法删除。

例如,以下代码会将所有带有img_前缀的资源标记为已使用:

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());

资源压缩器还查看代码中的所有字符串常量以及各种res / raw /资源,以类似于file:///android_res/drawable//ic_plus_anim_016.png的格式查找资源URL。 如果它发现这样的字符串或者其他看起来可以用于构造这样的URL,就不会删除URL对应的资源。

这些都是默认情况下启用的安全压缩模式的示例。 但是,您可以关闭此“better safe than sorry”处理,并指定资源压缩器仅保留其确定使用的资源。 为此,请在keep.xml文件中将shrinkMode设置为strict,如下所示:



如果您确实启用了strict压缩模式,并且代码还引用了动态生成的字符串的资源,如上所示,那么您必须使用tools:keep属性手动保留这些资源。

删除未使用的备用资源

Gradle资源压缩器只会移除app code未引用的资源,这意味着它不会移除对应不同设备的备选资源。 如果需要,您可以使用Android Gradle插件的resConfigs属性来删除app不需要的备选资源文件。

例如,如果您使用的库包含语言资源(例如AppCompat或Google Play Services),则APK会包含这些库中所有语言的字符串,无论您的应用程序的其他部分是否有对应的语言。 如果您只想保留app正式支援的语言,可以使用resConfig属性指定这些语言。 将删除未指定语言的任何资源。

以下代码段显示了如何将您的语言资源限制为仅限英语和法语:

android {
    defaultConfig {
        ...
        resConfigs "en", "fr"
    }
}

同样,您可以自定义要在APK中包含哪些screen density 或 ABI资源,并使用APK拆分为不同设备构建不同的APK。

合并重复资源

默认情况下,Gradle还合并相同名称的资源,例如在不同资源文件夹中具有相同名称的drawables。 此行为不受shrinkResources属性控制且不能禁用,因为必须避免在多个资源与代码查找的名称匹配时出现错误。

仅当两个或多个文件共享相同的资源名称,类型和限定符时,才会进行资源合并。 Gradle选择哪个被认为是重复项中最佳选择的文件(基于下面描述的优先级顺序),并且仅将那个资源传递给AAPT以在APK文件中分发。

Gradle在以下位置查找重复的资源:

  1. The main resources,与the main source set相关,一般位于src / main / res /
  2. The variant overlays, from the build type and build flavors.。
  3. The library project dependencies.

Gradle以以下级联优先级顺序合并重复资源:
Dependencies → Main → Build flavor → Build type
例如,如果重复资源同时出现在main resources和build flavor中,Gradle将选择build flavor中的一个。

如果相同的资源出现在同一源集中,则Gradle不能合并它们并发出资源合并错误。 如果在build.gradle文件的sourceSet属性中定义多个源集,例如,如果src / main / res /和src / main / res2 /包含相同的资源,则可能会发生这种情况。

排查资源压缩问题

当压缩资源时,Gradle Console会显示从应用程序包中删除的资源的摘要。 例如:

:android:shrinkDebugResources
Removed unused resources: Binary resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning

Gradle还在 / build / outputs / mapping / release /(与ProGuard生存的文件保存在相同的文件夹下)中创建一个名为resources.txt的诊断文件。 此文件包括详细信息,例如资源之间的引用关系以及哪些资源被使用或删除。

例如,要了解为什么@ drawable / ic_plus_anim_016仍在您的APK中,请打开resources.txt文件并搜索该文件名。 您可能会发现它被另一个资源引用,如下所示:

16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out]     @drawable/ic_plus_anim_016

你现在需要知道为什么@ drawable / add_schedule_fab_icon_anim是可及的,如果你向上搜索,你会发现该资源列在“The root reachable resources are:”。 这意味着有一个代码引用add_schedule_fab_icon_anim(也就是说,它的R.drawable ID在可达代码中找到)。

如果不使用strict检查,如果有字符串常量看起来像被用来构造资源名称从而动态加载资源,则该资源ID可以标记为可达。 在这种情况下,如果您在build输出中搜索资源名称,您可能会发现这样的消息:

10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
    used because it format-string matches string pool constant ic_plus_anim_%1$d.

如果您看到其中一个字符串,并且您确定该字符串未用于动态加载给定资源,则可以使用以下工具:discard属性通知build系统将其删除,可以参考上面的 定制要保留的资源。

该文章是对于Google文档的翻译:
Shrink Your Code and Resources

你可能感兴趣的:(压缩代码和资源)