我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android 性能优化 ~ 包体积优化实战

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android 性能优化 ~ 包体积优化实战

概述

用户通常都不愿意去下载一个比较大的程序,特别是不在 WIFI 的情况下。如果你的安装包很小,用户还是愿意下载安装体验下的。现在市面上满足某种需求的 App 通常都会有很多款,如何让用户愿意下载你的 App 来体验?安装包越小,在 WIFI 情况下,极速下载安装,开始体验。在移动网络情况下,包体积越小,用户安装的的可能性越大。所以安装包大小对用户的转换率有很大的影响。接下来就和大家分享下我在实际中工作中对包体积优化的一些经验。

APK 文件结构

既然是要优化 Android APK 安装文件的大小,首要需要了解下 APK 文件的结构。将 APK 文件拖进 AndroidStudio 可以清楚的看到 APK 文件组成部分。APK 主要由以下几部分组成:

META-INF/: 该文件夹下主要包含 CERT.SF 和 CERT.RSA 签名文件, 以及 MANIFEST.MF 清单文件 assets/: 该文件夹主要包含 app 中的资产文件,在程序中通过 AssetManager 对象来获取 res/: 该文件夹主要包含没有被编译进 resources.arsc 的文件 lib/: 该文件夹包含一些平台的 so 库, 如 armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, and mips. resources.arsc: 该文件主要存放着编译后的资源。主要存放着 res/values 目录下的文件内容,打包工具会将该目录下的 XML 内容(string、style)提取出来编译成二进制格式。 classes.dex: 该文件主要包含能够被 Dalvik/ART 虚拟机理解的 DEX 格式的 class 文件 AndroidManifest.xml: 该文件主要核心的 Android 清单文件,该文件使用 Android 的二进制 XML 格式。 优化手段

其实 APK 最核心的就两个内容,图片资源和代码。所以包体积优化主要是从这两方面入手。例如检查

assets
目录下是否有没有用到的资源。一般来说很少会在 assets 目录放一些没用的资源,主要是集成第三方 SDK (如高德、Baidu地图等)的时候需要放一些资源进去,比如图片、音频文件等。随着项目的迭代,界面 UI 的风格和以前相比发生了很大的变化,那么以前很多图片资源也就不可用了,所以在
res
目录下的可能会存在很多不用的图片,这是我们清理未使用资源最重要的一个文件夹。除了图片,然后就是
classes.dex
文件 了,一般我们自己的程序的业务代码不会对包体积产生很大的影响,主要是使用了大量的第三方库,以及集成公司内部其他团队的一些 module ,可能这些 module 包含了大量我们用不到的代码或者资源。

在优化之前,来看下我所做项目的安装包大小为 73437KB(71.7MB),为后面做的优化好有一个对比,看看具体的优化幅度。

通过 AndroidStudio 移除未使用的资源

手动移除资源有两个好处:一个是减少安装包的体积,另一个是减少源代码的体积。

在 AndroidStudio 中有两种方式帮我们找到未使用的资源:

Analyze -> Inspect Code,实际上就是通过 lint 工具帮我们找不用的资源,除了图片资源,还会帮我找到代码中存在的潜在问题,运行效果如下图所示:

Inspect Code

双击 shift,输入 Remove Unused Resources,然后回车。由于上面的方式不仅找出未使用到的资源,还会检测代码,所以运行的比较耗时。如果你仅仅只想找出未使用的资源,可以使用双击 shift 的方式,它们检测的结果都是一样的。

上面的工具在使用的过程中有两个坑:

用到的资源,依然报没有引用。如一些 drawable 文件的 xml 资源

它还会移除很多布局中的id,如果项目中使用了 ButterKnife,是通过 R2 来应用 id 的,该工具无法检测这种情况

所以,在针对 drawable 目录下的资源我们可以通过 git 将其 revert,因为我们的 icon 很少会放进 drawable 目录的。对于布局中声明的 id 被移除,我们可以将 layout 文件夹 revert。

通过上面的操作,成功将包体积减少了 2.3M:

操作 体积 减少
优化前 73437KB(71.7MB) -
Inspect Code 71054KB(69.3MB) 2383KB(2.3M)

在手动移除未使用的资源的过程中,发现了另一个问题。现在都是模块化工程了,我们项目有几十个 module,很多 module 中尽然包含了系统默认的 ic_launcher 图标,新建 module 默认生成的,而我们项目的图标名字改为了

app_icon
,也就是里面的 ic_launcher 是没有用的。每个 module 下关于 ic_launcher 就 8 个文件夹:

drawable
    -> ic_launcher_background.xml
drawable-v24
    -> ic_launcher_foreground.xml
mipmap-anydpi-v26
    -> ic_launcher.xml
    -> ic_launcher_round.xml
mipmap-hdpi
    -> ic_launcher.png
    -> ic_launcher_round.png
mipmap-mdpi
    -> ic_launcher.png
    -> ic_launcher_round.png
mipmap-xhdpi
    -> ic_launcher.png
    -> ic_launcher_round.png
mipmap-xxhdpi
    -> ic_launcher.png
    -> ic_launcher_round.png
mipmap-xxxhdpi
    -> ic_launcher.png
    -> ic_launcher_round.png

有的时候,这些 module 可能需要这些 launcher,虽然在发布的时候不需要,但是我们可能需要单独是运行这个组件,一般会有一个 debug manifest 和 release manifest,然后通过一个标记来判断是 library 还是 application。其实也可以用过其他方式来实现这种 debug 和 release 的情况(可以在 module 工程外 套一层工程,该工程包含这个 module,作为可以运行的 application)。通过这种方式,module 就不需要存在 application 的情况,也就不需要 launcher 图标了。

其实这也是开发者非常容易忽略的问题,例如,我们依赖的很多其他部门的内部库,通过 ctrl+shift+r 查找 ic_launcher,会发现很多 aar 会有 launcher 资源。甚至有些不规范的第三方开源库也同样存在这些问题。

操作 体积 减少
优化前 73437KB(71.7MB) -
Inspect Code 71054KB(69.3MB) 2383KB(2.3M)
Remove Launcher 71035KB(69.3MB) 19KB

为什么移除了这么多的 launcher 图片,为什么 apk 的大小只是减少了 19KB?(具体哪些地方减少了,可以通过 Compare with previous APK 功能进行对比)。

由于最终生成 APK 的时候,同名文件只会使用一个资源,也即是只会存在一份,所以优化的幅度不大(关于多个 module 相同路径存在相同文件名,打包时会有优先级,大家可以查看官方文档)。但是清理我们项目中一些垃圾资源。

开启 shrink resource

其实,在我们工程的 app/build.gradle 中配置了开启 shrink resource 了:

minifyEnabled true
shrinkResources true

我们使用的程序的图标名字使用的不是 ic_launcher,而是 app_icon,我们通过 APK Analyze 分析我们的 APK 发现 ic_launcher 资源还在,ic_launcher 名字的图标上在程序中应该没有被用到,为什么没有被 shrink 呢?有两种可能:

有某个地方隐形用到了 ic_launcher 文件。 shrink 没有生效

我们先来项目中的 shrink 有没有生效。 我放一个新的资源(abc.webp)到工程中去,然后重新打包,如果该文件被shrink了说明 shrink 是生效的(也就间接说明了程序中某个地方用到了 ic_launcher),如果没有被 shrink 说明上面的配置没有使得 shrink 生效,想办法让其生效即可。

通过 APK Analyze 打开新生成 APK 文件,发现新加入的

abc.webp
文件依然存在:

如果开启了 shrink resource,当 shrinkMode = safe 时,打包的时候会主动寻找那些可能被引用的资源,如通过 resources.getIdentifier() 方式获取资源,该资源不会被缩减,当 shrinkMode = strict 严格模式时该资源不会被缩减。

我在做实验的时候发现,如果一个资源被 shrink 了,它可能还在 APK 中,只不过该资源的体积变得非常小。

如果你将 shrinkMode 设置为 safe,那么可能没有被用到的也被保留了,因为检测可能没有那么精准。

你可以将 shrinkMode 设置为 strict,这个时候需要将通过 resources.getIdentifier(A)方式获取的资源 keep 起来。可以在 keep.xml 中配置要保留的文件:


更多关于混淆相关的知识,可以查看 AndroidAll

png 转成 webp

Android4.0 开始支持 webp,但是只有在 Android4.3 才支持透明度、无损 webp。所以如果你的 app 最低支持 4.3 的话,可以使用 webp 代替 png。

在 AndroidStudio 中支持一键转化,可以选择转码的质量比,还可以选择如果转成的 webp 反而比原来的 png 还要大,可以跳过。

操作 体积 减少
优化前 73437KB(71.7MB) -
Inspect Code 71054KB(69.3MB) 2383KB(2.3M)
Remove Launcher 71035KB(69.3MB) 19KB
ShrinkResources 67576KB(65.9MB) 3459KB(3.37M)
Png2webp 64505KB(62.9M) 3071KB(3M)
Enable R8

由于之前 R8 还不是很稳定,所以我们将其关闭了。现在都 AndroidStudio 3.6 了,我们将其打开:

android.enableR8=true

虽然官网上说 R8 支持现有 ProGuard 规则文件,但是在实际使用的时候还是会有些问题,解决一些混淆配置上的问题,重新打一个 release 包,发现减少了 0.9M:

操作 体积 减少
优化前 73437KB(71.7MB) -
Inspect Code 71054KB(69.3MB) 2383KB(2.3M)
Remove Launcher 71035KB(69.3MB) 19KB
ShrinkResources 67576KB(65.9MB) 3459KB(3.37M)
Png2webp 64505KB(62.9M) 3071KB(3M)
R8 63506KB(62M) 999KB(0.97M)

上面是 R8 的普通模式,R8 还有完全模式,还会做一些额外的优化操作,R8 开启完全模式,但是目前还是实验性质的:

android.enableR8.fullMode=true

重新打一个 release 包,发现减少了 0.16M:

操作 体积 减少
优化前 73437KB(71.7MB) -
Inspect Code 71054KB(69.3MB) 2383KB(2.3M)
Remove Launcher 71035KB(69.3MB) 19KB
ShrinkResources 67576KB(65.9MB) 3459KB(3.37M)
Png2webp 64505KB(62.9M) 3071KB(3M)
R8 63506KB(62M) 999KB(0.97M)
R8 FullMode 63333KB(61.8M) 173KB(0.16M)
通过自定义 View 来代替图标

我们还可以通过自定义 View 来代替一些状态图标,比如订单状态、退款状态等。如下所示:

在这里插入图片描述

类似这些图标都是可以使用自定义 View 来完成,可以减少大量的图片资源。如果状态很多,就会需要很多的状态图标,如果支持国际化的话,还需要为每个国家生成对应的状态图标。

经过自定义 View 替换状态图标后,包体积减少了 0.366M:

操作 体积 减少
优化前 73437KB(71.7MB) -
Inspect Code 71054KB(69.3MB) 2383KB(2.3M)
Remove Launcher 71035KB(69.3MB) 19KB
ShrinkResources 67576KB(65.9MB) 3459KB(3.37M)
Png2webp 64505KB(62.9M) 3071KB(3M)
R8 63506KB(62M) 999KB(0.97M)
R8 FullMode 63333KB(61.8M) 173KB(0.16M)
CustomView 62958KB(61.4M) 173KB(0.36M)
使用 AndResGuard

微信使用的 AndResGuard 可以对资源资源路径以及资源名字进行混淆,资源名字全部改成类似 abc 的样子。可以大大减少名字字符占用的空间大小。

特别是模块化后,为了防止资源重名,我们都会在资源的加上模块前缀,这样导致资源的名称就更长了。使用 AndResGuard 的时,程序中通过 getIdentifier 方式获取资源,一定要加入白名单,这个可以在程序中全局查找。

通过 AndResGuard 混淆后,包体积减少了 3.54M:

操作 体积 减少
优化前 73437KB(71.7MB) -
Inspect Code 71054KB(69.3MB) 2383KB(2.3M)
Remove Launcher 71035KB(69.3MB) 19KB
ShrinkResources 67576KB(65.9MB) 3459KB(3.37M)
Png2webp 64505KB(62.9M) 3071KB(3M)
R8 63506KB(62M) 999KB(0.97M)
R8 FullMode 63333KB(61.8M) 173KB(0.16M)
CustomView 62958KB(61.4M) 173KB(0.36M)
AndResGuard 59323KB(57.9M) 3635KB(3.54M)
so 文件

在主流的手机CPU架构都是 ARM,基本上只要支持这一种架构就可以了。更多关于这方面的知识可以查看 Android NDK ~ 基础入门指南

我们来看下市面上主流的 app 支付宝和微信的 CPU 架构:

alipay-arm

weixin-arm

armeabi-v7a 是向下兼容 armeabi,arm64-v8a 能兼容 armeabi-v7a 和 armeabi

我们项目中也是只支持一种 armeabi-v7a 架构,减少 so 文件体积大小

release {
    ndk {
        abiFilters 'armeabi-v7a'
    }
    //...
}
小结

到此,就介绍完了我这次包体积优化相关内容了,差不多了减少了 20% 的包体积大小。当然优化是无止尽的,除了上面的一些优化手段还有 app Bundles 的方式(需要结合 Google Play 一起);还可以考虑通过 BackgroundLibrary 替换程序中大量的 shape、selector 文件,减少包体积,但是该库对性能有一定的影响,所以我还没有使用,后面可以考虑是否还有更好的方案;还可以找出程序中重复的图片(图片内容一致,名字不同);当然还有插件化,插件也需要瘦身,减少下发消耗的流量。

另外本文涉及到的代码都在我的 AndroidAll GitHub 仓库中。该仓库除了

性能优化
,还有 Android 程序员需要掌握的技术栈,如:程序架构、设计模式、性能优化、数据结构算法、Kotlin、Flutter、NDK,以及常用开源框架 Router、RxJava、Glide、LeakCanary、Dagger2、Retrofit、OkHttp、ButterKnife、Router 的原理分析 等,持续更新,欢迎 star。


作者:Chiclaim


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android 性能优化 ~ 包体积优化实战

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android 性能优化 ~ 包体积优化实战

概述 用户通常都不愿意去下载一个比较大的程序,特别是不在 WIFI 的情况下。如果你的安装包很小,用户还是愿意下载安装体验下的。现在市面上满足某种需求的 App 通常都会有很多款,如何让用户愿意下载你的 App 来体验?安装包越小,在 WI
2022-06-06

SDK 体积与性能优化实践

字节各类业务拥有众多用户群,作为字节前端性能监控 SDK,自身若存在性能问题,则会影响到数以亿计的真实用户的体验,所以此类 SDK 自身的性能在设计之初,就必须达到一个非常极致的水准。
字节SDK监控2024-12-01

得物 Android 包体积资源优化实践

本文主要介绍了得物APP资源优化做了的一些动作,其中对资源优化插件的工作模式进行了重点介绍。当然,对于资源依旧有不少手段可以完善,比如提供高效简单的 9 图下发方案,包体积平台增加图片相似度检测能力、把一些次级的资源通过插件包下发都是之后可

特效侧用户体验优化实战 —— 包体积篇

抖音目前由多条业务线组成,每条业务线都类似中台的角色,特效中台是抖音其中一环;目前,特效由 effect 和 lab 聚合为EffectSDK,作为一条独立业务线结算包体积在抖音中的占比。

抖音 Android 包体积优化探索:基于 ReDex 的 DEX 优化落地实践

抖音是字节跳动规模最大、运行环境复杂度最高的应用之一。在 ReDex 落地初期,由于对复杂度估计不足,在独立灰度和全量灰度期间引起了一些问题,在解决问题的过程中,我们也逐步形成了一套迭代流程以保证优化的稳定性。

前端性能优化实战

相信前端的同学对于 Performance API 应该都不陌生,通常我们将浏览器提供的可以进行测算和采集的 API 统称为 Performance API,该类型的对象可以通过调用只读属性 window.performance 来获得。

Android性能优化之网络优化

在移动互联网的快速发展环境下,手机用户日益对网络的使用或体验有着更深度的诉求,因此应用中的网络体验已经显得由此重要。

Android性能优化(一)启动优化

以前做手机的时候,我非常重视app的性能优化。其实一直以来,在工作中我总会去强调性能优化的重要性。但是,很多时候,由于一些外界因素,我们对app的一些性能指标不会那么重视。但是,性能优化依然是做好一个产品的重中之重。试想一下,如果用户费了很
2022-06-06

Android对so体积优化的探索与实践

本文将先从 so 文件格式讲起,结合文件格式分析哪些内容可以优化,然后再具体讲解每项优化手段以及注意事项,最后介绍相关的工程实践经验。希望能对从事包体积优化的同学有所帮助或启发。

Vite打包优化之缩小打包体积实现详解

这篇文章主要为大家介绍了使用Vite缩小打包体积如何实现的示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-01-04

Android性能优化系列篇UI优化

这篇文章主要为大家介绍了Android性能优化系列篇UI优化示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

vivo 游戏中心包体积优化方案与实践

介绍 App 包体积优化的必要性,游戏中心 App 在实际优化过程中的有效措施,包括一些优化建议以及有优化思路。
游戏中心App2024-11-28

抖音 Android 性能优化系列:启动优化实践

本篇我们将按照主线程直接优化、后台线程间接优化、全局优化的逻辑,介绍团队在启动优化的实践中遇到的一些比较典型的案例,其间对于业界一些比较优秀的方案也会进行简要介绍。

APK体积优化(附Android Studio操作指引)

目录 1. 压缩APK体积的意义 2. APK的构成与打包 3. res资源的压缩和优化 3.1 删除没有被引用的资源 3.2 使用WebP压缩图片 3.3 使用TinyPNG压缩图片 4. 删除多余的代码(未被引用的类/方法/变量) 4.
2022-06-06

MySQL 高性能优化实战总结

如图 - MySQL 查询过程优化有风险,涉足需谨慎1、优化的哲学1.1、优化可能带来的问题优化不总是对一个单纯的环境进行,还很可能是一个复杂的已投产的系统。优化手段本来就有很大的风险,只不过你没能力意识到和预见到!任何的技术可以解决一个问题,但必然存在带来一
MySQL 高性能优化实战总结
2017-04-23

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录