【Android】优化安卓应用大小至10MB以下
推荐超级课程:
@TOC
开发一个新产品或功能,通过解决客户需求、提供更丰富的体验,或者用新颖的东西给他们带来惊喜,从而取悦客户,这会给每个产品经理和开发者带来巨大的满足感和乐趣。尽管我们尽力设想并开发最好的产品和功能,但我们有时没有意识到我们最终做出的权衡,这可能是APK大小的增加、内存使用的增加、应用性能的降低等。因此,如果这些权衡不被检查,它们可能会随着时间的推移而膨胀,开始影响市场营销指标,如应用安装量、卸载率、新用户获取等。
根据Google Play开发团队的山姆·托洛梅(Sam Tolomei)发表的文章《缩小APK,增加安装量 [1]》
“在印度和巴西,APK大小减少10MB的影响要大于德国、美国和日本。在新兴市场,从应用APK大小中移除10MB与安装转化率提高约2.5%相关。”
在过去的二十一个月里,我们通过不断寻找应用优化方法,成功地将下载APK大小减少了40%以上。这导致我们的应用卸载/安装率惊人地下降了34%。在这篇博客中,我们将描述一些我们发现的最佳实践和方法,这些实践和方法可以帮助显著减少应用大小。这篇博客强调了产品经理可以与开发人员协商的方法,以及一些细节,帮助开发人员了解这些实践是如何实施的。
消除未使用的资源:
在编写代码时,您可能会在代码库中定义许多资源,这些资源后来可能会变得多余,并且在运行时不会被使用。删除它们可以真正减少您的代码库。Android通过在Android Studio中使用重构选项,提供了一种非常实用且简单的方法来执行此操作。
Android Studio -> 重构 -> 移除未使用的资源
选择您想要删除的资源。您可以通过右键单击资源项来排除您想要保留的资源。您可以使用执行重构来一次性删除它们。
缩减代码和资源:
您可以通过修改build.gradle中的发布配置,进一步启用代码缩减并与资源缩减结合使用。您可以使用“shrinkResources true”,这不仅会删除运行时实际上不需要的资源,还会删除您依赖的库中不需要的资源。此外,要使用ProGuard启用代码缩减(稍后在部分中详细介绍),您可以在build.gradle文件中添加“minifyEnabled true”。我们通过以下代码片段获得了**~3 MB**的减少。
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
}
}
}
移除未使用的库
您还可以运行gradle依赖命令(./gradlew app:dependencies)。这将为您提供包括传递依赖在内的依赖关系。您可以检查并查看这些依赖项为何对您的项目是必需的,并采取必要的行动。当我们运行它时,我们发现我们正在使用的一些库依赖于NineOldAndroid库,这对我们来说并不相关,因为我们只针对ICS及以上设备。因此,我们使用gradle中的exclude从使用它的模块中将其移除
exclude group: 'com.nineoldandroids', module: 'library'
移除的库 - Logback , NineOldAndroid , Junit
替换笨重的库
在添加任何新库之前,您应该检查它对大小的影响。您可以使用在线工具MethodsCount 快速完成这项工作。它将清晰地展示库的大小和它将添加的方法计数。
区分调试和发布构建
您应该为调试和发布构建使用不同的构建类型。由于构建类型可以有自己的依赖,确保仅由调试构建类型使用的库不会不必要地添加到发布构建中(例如Memory Leak库 , Logger库 )。通过区分这两种构建,我们获得了800KB的好处。例如,我们在以下情况下只需要在调试构建中使用MemoryLeak库和Hockey app sdk,而在发布构建中只需要Tune库
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.tune:tune-marketing-console-sdk:4.15.3'
移除未使用的替代资源
许多平台库(Support, App-Compat, Design)支持多种语言的本地化。它们捆绑了支持这些语言所需的资源。如果您的应用只支持有限的语言集,您可以通过指定资源配置仅为该语言来减少大小。我们将语言限制为仅英语时,获得了450KB的好处。
您可以通过在build.gradle 中的Defaultconfig中指定resConfig来启用此功能
defaultConfig {
….
resConfigs "en"
}
针对多架构的APK
如果您有一个适用于不同架构的单个应用,考虑分割apk,以便每个apk只使用它们实际需要的库。然而,维护不同的apk也需要努力,因此在分割apk之前应该进行成本效益分析。当我们决定为arm和x86架构分别有两个独立的apk文件时,我们实现了600KB的好处。拥有重型JNI库的apk将从此中受益匪浅。更多信息:链接
图像打包
图像在大多数应用中占据了大量空间,减少图像大小可以显著影响您的应用大小。通过使用以下提到的一些方法,我们成功实现了1 MB的减少
- 使用像TinyPNG这样的压缩工具,可以在不损失图像质量的情况下帮助减少图像大小
- 确保不要添加非常大的图像到应用中。我们实际上使用我们的图像大小验证脚本来测试我们的apk,如果添加的图像超过50 KB,则会触发警报。
- 避免添加图像资源,尽可能使用xml drawable和Vector Drawable。
- 使用懒加载方法在网络中渲染大图像,而不是将它们与应用程序捆绑。
- 为了在多个地方重用相同的图像,您可以考虑使用Google建议的颜色滤镜和旋转逻辑
与上一版本的基准测试
如果您不跟踪是什么导致您的应用大小增加,您将不知道在哪里优化以获得最佳结果。我们遵循与上一版本进行基准测试的实践,并跟踪差异。
我们使用Jenkins实施了CI(持续集成),在其中我们添加了Android apk大小watcher插件 [2],如果新的apk大小比前一个大了100KB(定义的阈值),则该PR无法合并。任何超过100KB差异的PR都需要团队领导审查。
正确使用Proguard代码基和代码清理
在编写代码时,开发者会
- 在不完全了解其作用域的情况下定义类和变量
- 创建在代码完成后变得无用的变量
- 定义变量和对象名称,以便它们易于识别
所有这些都会导致代码库大小的增加。因此,在将应用交付给用户之前,优化您的代码以消除这些低效是明智的。您会惊讶地发现,通过这些优化,您可以节省多少apk大小
有效使用Proguard-:Proguard不仅混淆您的代码,而且还通过移除未使用的指令、减少变量分配和其他优化来优化和缩减代码。我们在成功开始使用ProGuard后,如上文“缩减代码和资源”*部分所述,看到了惊人的3 MB*缩减。
使用ProGuard可能很简单,但正确使用它对于获得期望的好处至关重要。在我们的初期阶段,我们没有达到预期的ProGuard效益水平。如果不是Android Studio中的Google APK分析器工具,我们可能会认为使用ProGuard是徒劳的。APK分析器指出了代码库中没有被正确保护的部分,这使我们意识到我们需要在proguard配置中进行哪些更改才能实际使其工作。在这些更改之后,我们意识到apk减少了1 MB。此外,在这个过程中我们还确定了应该做和不应该做的事情。
应该做和不应该做的事情
- 确保只有使用反射的类应该使用keep变量来避免被ProGuard混淆
- 不要盲目地添加dontwarn **, dontoptimize, keep public class *
- 确实用Android Studio中的APK分析器工具检查发布APK,并检查类是否被正确混淆
错误的Proguard配置示例:
正确的Proguard配置示例:
人们可以在github仓库中查看外部库的proguard配置。下面突出显示了一些我们应用中使用的流行库的proguard配置。
# Glide
-keep class com.bumptech.glide.load.engine.EngineKey
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.AppGlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# okhttp
-dontwarn okhttp3.**
# okio
-keep class sun.misc.Unsafe { *; }
-dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn okio.**
# Greenrobot
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# 只有在使用AsyncExecutor时才需要
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
# OrmLite使用反射
-keep class com.j256.**
-keepclassmembers class com.j256.** { *; }
-keep enum com.j256.**
-keepclassmembers enum com.j256.** { *; }
-keep interface com.j256.**
-keepclassmembers interface com.j256.** { *; }
# 保留Android支持库的类和接口
-keep public class android.support.v7.widget.** { *; }
-keep public class android.support.v7.internal.widget.** { *; }
-keep public class android.support.v7.internal.view.menu.** { *; }
-dontwarn android.support.design.**
-keep class android.support.design.** { *; }
-keep interface android.support.design.** { *; }
-keep public class android.support.design.R$* { *; }
PR合并时的PMD验证
Proguard有助于修剪整个apk的大小,但它是在代码合并后的后期阶段(生成发布apk时)使用的。因此,我们还使用PMD,这是一种静态代码分析工具,我们将其添加到我们的PR验证中,以确保开发人员不会在代码库中推送未使用的代码和不必要的对象创建。这确保了在合并到整体代码库之前,代码库是干净且经过优化的。您还可以进一步使用Lint,并将未使用资源警告设置为错误,使其成为构建的一部分。
尽管我们在一段时间内实施了更多的优化,但上述优化是给我们带来最大影响的,我们相信其中一些也可以帮助您减小应用大小。