
/ 照片 / PD
在Instant App时代,优化APK的重量是一项不平凡但非常相关的任务。 如果可以在编译阶段确定依赖关系,则启用proguard可以使您免于使用不必要的代码,但是APK中还有几种其他类型的文件可以从程序集中排除。
在如何建立依赖关系的主题下-在编译阶段定义了哪些文件可以从程序集中排除以及如何执行,以及,如果您有多个具有相同代码库的应用程序,我们将分析如何从程序集中排除未使用的组件。
阅读之前
- 在应用文章中的提示之前,请先优化APK for Google指南 。 本文适用于那些没有足够标准优化的用户。
- “ proguard”是指经过优化的优化编译器。
- 从组件的角度来看,从业务角度来说,我指的是产品的某些功能。 在我们的情况下,这只是某个程序包中文件的集合。 我们为整个应用程序提供了一个gradle模块。
我们Google优化的APK的重量为4.4
。
多余的文件
让我们从一个简单的开始。 如果不使用kotlin-reflect ,则可以从程序集中排除有关kotlin类的元信息。 您可以按照以下步骤进行操作:
在build.gradle (Module: app)
android { packagingOptions { exclude("META-INF/*.kotlin_module") exclude("**.kotlin_builtins") exclude("**.kotlin_metadata") } }
Java反射不需要*.kotlin_module
, *.kotlin_builtins
和*.kotlin_metadata
。 确定您正在使用哪种反射非常简单。 如果编写obj::class.<method>
,则使用kotlin反射;如果obj::class.java.<method>
,则使用java反射。
我们的优化结果:-602.1 kb
依存关系
库有时会为应用程序中从未发生过的情况添加依赖项。 例如, ktor-client将kotlin-reflect与它一起拉(0.5 mb!)。
我在以下情况下苦苦挣扎:我使用minifyEnabled = true
收集了APK,将其扔到Android Studio分析器中,下载了mapping.txt
并查找了理论上不应该出现在程序集中的包。 例如, kotlin.reflect
。 运行./gradlew app:dependencies
项目文件夹中的依赖项以搜索依赖项后(不要忘记增加终端中历史记录的长度。依赖项树可能很大!)。 从这棵树中,很容易理解什么是指不必要的依赖项,并将其排除在外。 在模块的build.gradle
:
dependencies { implementation("io.ktor:ktor-client-core:$ktorVersion") { exclude(group: "org.jetbrains.kotlin", module: "kotlin-reflect") } implementation("io.ktor:ktor-client-okhttp:$ktorVersion") { exclude(group: "org.jetbrains.kotlin", module: "kotlin-reflect") } }
此代码删除了对kotlin-reflect的ktor-client库依赖。 如果要排除其他内容,请替换您的值。
!!! 请非常谨慎地使用此建议! 在消除依赖项之前,请确保您不需要它们。 如果您不这样做,则该应用程序可能会开始投入生产!!!
对我们的优化结果:-500.3 kb
验证您的XML
不幸的是,proguard不会从布局文件夹中删除多余的XML标记文件。 未使用的XML可以使用“大量”窗口小部件,并且proguard也无法将其从程序集中排除! 为了避免这种情况,请使用“ Refactor -> Remove unused resources...
检查你的di
如果您像我们一样,使用运行时DI,然后检查您是否具有针对未使用的依赖项的提供程序。 Proguard不能将它们从程序集中排除,因为从编译器的角度来看它们未被使用。 您在构建依赖关系图时使用它们。
从发行版本中排除调试依赖项
调试工具可能会意外地占用大量空间。 例如,压缩后stetho
重量约为0.2
! 无论如何,最好将整个调试基础结构从发行版本中排除,以使没有人可以通过简单地从Google Play下载应用程序来了解太多有关您的应用程序的信息。
您可以为同一文件制作不同版本以进行调试和发布。 为此,请在main
旁边的src
文件夹中,创建debug
和release
文件夹。 现在,您可以编写一个initStetho
函数来初始化src/debug/java/your/pkg/Stetho.kt
的initStetho
,以及一个initStetho
函数,它在src/release/java/your/pkg/Stetho.kt
中不执行任何src/release/java/your/pkg/Stetho.kt
。
以防万一,请确保仅在调试版本中包含此依赖项。 您可以通过用debugImplementation
中的build.gradle
替换implementation
。 通常,即使没有此步骤,proguard也会消除不必要的文件,但并非总是如此。 问题“为什么?”的答案 在文章的下面 。
有时,在同一代码库中,会发布一个应用程序的多个不同版本。 对于不同的国家或地区,或者对于我们的客户,这些可以是不同的版本。 以下是有关如何卸载平台的提示。

/ 照片 / PD
我们的经验
我们正在开发E-SHOP移动应用设计器。 我们有几十个客户,每个客户都有自己独立的组件集。 有些组件供所有客户使用,有些只是一部分。 我们的任务是仅在客户程序集中包括他需要的那些组件。
标记异常
我们为每个客户创建一个单独的productFlavor 。 这很方便,因为它很容易为不同的客户端创建不同的资源,IDE提供了用于在风味之间进行切换的图形界面,并且缓存运行良好。 您还可以为每个客户端生成自己的BuildConfig.java
。 此类的字段值在编译阶段是已知的。 这就是我们所需要的! 为每个组件创建一个boolean
类型的字段。
android { productFlavors { client1 { buildConfigField("boolean", "IS_CATALOG_ENABLED", "true") } client2 { buildConfigField("boolean", "IS_CATALOG_ENABLED", "false") } } }
这是配置的简化版本。 由于与我们的CI集成,目前情况很复杂。
现在知道该组件在编译阶段是否处于活动状态,并且Proguard可以将其从程序集中排除!
再次使用XML
现在,未使用的XML布局出现了一个新问题! 您不能仅仅因为某些客户不需要组件而删除组件的标记。
在很少使用的组件之一的XML应用程序中,我们使用了一个小部件,该小部件引用了图像识别库firebase.ml.vision
。 它的重量约为0.2 mb,非常大。 决定使用代码添加此小部件,而不是在标记中声明它。 之后,对于不需要的客户,proguard可以将其排除在装配中。
优化结果:平均APK为-222.3 kb
@Keep
有两种方法可以告诉proguard无法缩小您的类:在文件proguard-rules.pro
编写规则或放置@Keep
批注。 在play-services-vision
库中,此注释位于根类上。 因此,即使在不需要图像识别的客户端应用程序中,也有0.2 mb死机。
我找不到删除此注释的简单安全的方法。 如果您知道如何-请在评论中写。
幸运的是, firebase.ml.vision
库是play-services-vision
的较新版本,它没有使用此批注,因此我们通过解决了此问题。
再DI
最后但并非最不重要的。 DI用于断开的组件。 这里的一切都很简单:对于每个组件,我们使用我们自己的容器,然后通过单独的模块连接一般的依赖项。
对我们来说,优化结果为:平均APK为-20.1 kb
结论
- 平均APK的权重已从
4.4
降低到3.1
,最小-降至2.5
! - 应用程序代码没有受到损害,但是得到了改进。 DI现在更容易使用
本文中介绍的所有优化都是“低调的成果”。 它们非常容易实现,并且很快就可以得到结果。 在我们的案例中,对于已经优化的APK,最高可达-43% 。 希望我通过将所有内容都放在一个地方来节省您的时间。
谢谢大家!