首页 > 安全资讯 >

缩减代码和资源

16-10-22

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

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

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

资源缩减可使用Gradle的Android插件,它从您的打包应用程序中删除未使用的资源,包括代码库中未使用的资源。 它与代码缩减结合使用,以便一旦未使用的代码被删除,任何不再被引用的资源也可以被安全地删除。

本文档中的功能依赖于:

SDK Tools 25.0.10 or higher Android Plugin for Gradle 2.0.0 or higher

代码缩减


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

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

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

注意:Android Studio在使用即时运行时会禁用ProGuard。

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

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

要添加特定于每个构建变量的更多ProGuard规则,请在相应的productFlavor块中添加另一个proguardFiles属性。 例如,Gradle文件内将flavor2-rules.pro添加到flavor2 产品中。 现在flavor2使用所有三个ProGuard规则,因为来自发布块的那些规则也被应用。

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

对于每个构建,ProGuard输出以下文件:

dump.txt

描述APK中所有类文件的内部结构。

mapping.txt

提供原始和混淆的类,方法和字段名称之间的转换。

seeds.txt

列出未被混淆的类和成员。

usage.txt

列出从APK中移除的代码。

这些文件保存在/build/outputs/mapping/release/。

自定义要保留的代码

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

当您的应用程序仅引用来自AndroidManifest.xml文件的类 当应用程序从Java本机接口(JNI)调用方法时, 当您的应用在运行时操作代码(如使用反射或内省)

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

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

-keep public class MyClass

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

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

混淆解码的堆栈跟踪

ProGuard收缩代码后,读取堆栈跟踪很困难(即使并非不可能),因为方法名称被模糊处理。 幸运的是,ProGuard每次运行时都会创建一个mapping.txt文件,它显示映射到模糊名称的原始类,方法和字段名称。 ProGuard将文件保存在应用程序/build/outputs/mapping/release/目录中。

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

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

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

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

例如:

retrace.bat -verbose mapping.txt obfuscated_trace.txt

如果不指定堆栈跟踪文件,则回溯工具从标准输入读取。

资源缩减


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

要启用资源缩减,请在build.gradle文件(与代码缩减minifyEnabled 属性并列)中将shrinkResources属性设置为true。 例如:

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

如果你还没有使用minifyEnabled为代码收缩构建你的应用程序,那么在启用shrinkResources之前尝试一下,因为你可能需要编辑你的proguard-rules.pro文件,以保持在开始删除之前动态创建或调用的类或方法资源。

注意:资源缩减器当前不会删除在values/文件夹中定义的资源(例如字符串,维度,样式和颜色)。 这是因为Android资源打包工具(AAPT)不允许Gradle插件为资源指定预定义版本。 有关详细信息,请参阅问题70869

自定义要保留的资源

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


将此文件保存在项目资源中,例如,在res/raw/keep.xml。 该文件不会打包到您的APK中。

指定要丢弃的资源可能看起来很愚蠢,当然可以改为删除它们,但这在使用构建变体时可能很有用。 例如,您可以将所有资源放入公共项目目录,然后为每个构建变量创建一个不同的keep.xml文件,您知道给定的资源似乎在代码中有使用(因此不会被缩减器删除),但是 你知道它实际上不会用于给定的构建变体。

启用严格引用检查

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

例如,以下代码会将所有带有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,它不会删除它们。

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


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

删除未使用的备用资源

Gradle资源缩减器只会移除应用程序代码未引用的资源,这意味着它不会移除不同装置设定的替代资源。 如果需要,您可以使用Android Gradle插件的resConfigs属性来删除应用程序不需要的替代资源文件。

例如,如果您使用的库包含语言资源(例如AppCompat或Google Play服务),尽管应用程序是不需要翻译这些语言资源,APK也会包含这些库中所有翻译的语言字符串。 如果您只想保留应用程序支持的语言,可以使用resConfig属性指定这些语言。 将删除未指定语言的任何资源。

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

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

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

合并重复资源

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

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

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

主资源,与主源集相关,一般位于src/main/res/。 变体覆盖,来源于构建类型和构建风格。 依赖的库项目

Gradle在以下级联优先级顺序中合并重复资源:依赖→主→构建风格→构建类型。

例如,如果重复资源同时出现在主资源和构建风格中,Gradle将选择构建风格中的一个。

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

排查资源缩减问题

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

: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 是可访问的,如果你向上搜索,你会发现该资源列在“根可访问的资源是:”。 这意味着有一个代码引用add_schedule_fab_icon_anim(也就是说,它的R.drawable ID在可访问代码中找到)。

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

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.

如果您看到其中一个字符串,并且您确定该字符串未用于动态加载给定资源,则可以使用以下tools:discard 属性通知构建系统将其删除,相关资料查阅定制要保留的资源

相关文章
最新文章
热点推荐