关于groovy:有道词典Android客户端包体积优化之路

1次阅读

共计 6135 个字符,预计需要花费 16 分钟才能阅读完成。

1 背景

有道词典从挪动互联网之初就凭借玲珑疾速、功能强大的印象让用户爱上翻译查词,爱上学习。随着业务一直地迭代以及性能不断完善,有道词典不再是单纯的查词软件,而是变成了用户的综合学习平台。咱们摸索过社区、问答、直播、信息流等业务,目前也承载着音频、视频、课程、背单词、写作批改等等的性能。词典曾经倒退成为一个综合性的学习平台,玲珑疾速的初心依然指引着咱们一直进行启动速度以及包体积优化。

通过了一直的性能优化,目前咱们的冷启动工夫曾经能维持在业界规范程度 3s 以内。咱们近一个季度次要的性能优化工作集中在安装包体积优化下面。通过一系列的致力,咱们包体积缩小了 23.7%,安装包体积从 177MB 缩小到 135MB,整体少了 42MB。

以下具体介绍咱们的剖析以及实现细节。

2 剖析

介绍下包体积蕴含的内容以及优化办法概述

个别的 APK 安装包蕴含了以下一些目录和资源:

META-INF/ 签名文件

assets/ 程序应用的辅助资源文件

res/ 没有编译进入 resources.arsc 资源文件,个别是图片

lib/ 依赖的不同 native 平台的库文件

resource.arsc 编译之后的文案、色值、大小、主题等资源索引

classes.dex 编译后的代码

AndroidMenifest.xml 利用的名称、版本、拜访权限和援用的库文件信息


能够看出占比拟大的局部次要是别离是 assets/、lib/、res/、classes.dex 以及 resources.arsc,大略对应的就是资源、库文件、代码以及资源索引。咱们次要的优化思路如下(其中蓝色框局部为目前曾经解决局部):

3 技术实现细节

3.1 图片压缩

在 APK 打包的过程中,aapt 工具会默认对图片进行无损压缩,不过默认的压缩并不能达到一个很好的压缩成果,通过了比照 webp 以及 tinypng 的压缩成果,咱们最终抉择了应用 tinypng 对图片进行压缩。并且咱们编写了编译工具,对图片进行自动化压缩。

有损 webp > tinypng > 无损 webp

比方这张启动图,原大小 724KB,压到 75% 左右的品质只有 23.7KB。成果上有一点点差别,但能够承受。那么咱们是否能够把全副 png 图压成有损 webp 呢?答案是否定的,能够看看上面的例子:

压缩前:

压缩后:

能够看到,雷同的压缩品质下 (75%),这个图就变得非常含糊,哪怕抉择到了 99% 的压缩品质,突变区域仍然会呈现一些没有天然过渡的条纹。


对于上述的状况,用 tinypng 计划更好

原图:643KB,

tinypng: 152KB,

webp:339KB

综上,对于有损 webp,无奈找到一个固定的压缩品质来适配所有场景。有损 webp 有些时候甚至比 tinypng 还大,但显示品质更差。

咱们最后应用的抖音的 McImage 插件对图片进行解决,不过这个计划存在一些显著的问题:

  1. 计划采纳有损 webp,有损 webp 无奈定一个通用的压缩品质适应所有场景。
  2. 每次打包都要对所有图进行压缩,重大影响迭代效率。打包机要 40 分钟,且常常 OOM。
  3. 没有对 assets 目录的图片进行解决。

针对以上问题,咱们本人开发了一套应用 tinypng 的自动化图片压缩工具,做出以下调整:

1. 对于大图 png,用手工压成有损 webp。收益大,且危险可控。
2. 对于非大图,开发了一个 image-optimization 插件进行压缩。该插件计划为:

· png 转 tinypng。尽管是有损的,但从抽样来看,肉眼齐全看不到显著变动。

· 对 assets 进行解决。assets 内有前端 png 图,转 tinypng 不转 webp 的益处是不须要独自改 html、js 等文件,且对低版本零碎兼容性更敌对。flutter 相干我的项目的 flutter_assets 图片比拟大且没留神压缩。插件对立解决能够不须要关上 flutter 工程独自优化、从新打包。

· 对于已压缩的图片,做缓存解决,不须要从新压缩,打包的时候动静替换。压缩缓存追随词典工程提交到 gitlab 对立治理。

以下是咱们图片自动化压缩插件解决的流程图:

这里压缩图是否可用判断,次要是大小判断,如果压出来比原图大,那么将舍弃。比方 crunchPng 压缩就存在这种状况

附加 1:因为曾经用了 tinypng 对立压缩,那么 google 官网自带的 crunchPng 倡议敞开,否则打包速度变慢,而且优化好的图片也可能又变大,退出这行即可:

buildTypes.all {\
  isCrunchPngs = false\
}

附加 2:无损 webp 和 tinypng 比照

如图所示,全量换 tinypng 比全量换 webp(蕴含 assets)少 7.7MB。如果思考到 assets 内的 14.7MB 其实是不能简略换 webp 的,差距会更大。

附加 3:tinypng 曾经是最好的计划吗?

参考另一个 ImageOptim 工具,它联合 OptiPNG, PNGCrush, AdvanceComp, PNGOUT, Jpegoptim + Jpegtran, 和 Gifsicle 等几个工具提供最好的优化成果,而且是简直无损的。对于小局部图片 ImageOptim 压出来小,看起来没有差异。不过压缩速度十分慢。

所以,如果做到极致的话,能够进行多种压缩计划,选最佳的图作为替换。且咱们的 image-optimization 插件从一开始设计的时候就预留了这种可扩展性。

附加 4:AndResGuard 优化比照

试了一下成果不显著,且呈现局部资源失落而解体的状况。成果不显著的起因,猜想是目前 R8 对资源名也有混同压缩(以前 proguard 没有),所以 AndResGuard 当初的作用比拟强劲。至于 7zip 的压缩没有开,实践上会导致启动速度变慢,感觉得失相当(另外会导致 Google Pay 的 Patch 优化算法生效)。

3.2 resources.arsc 优化

  • 语言包优化

关上 resources.arsc 的 string,咱们能够看到如下表格,会发现大量空的中央(如上图)。这些空白的中央,其实是用 FF FF FF…字符进行占位的,占用了很多空间(如下图)。因为有道词典没有进行国际化翻译(有一个国际化版本叫 U -Dictionary,欢送反对),因而删掉不必要的语言版本有助于缩小体积。

android {\
    defaultConfig {\
        resConfigs "zh"\
    }\
}
  • 如上所示,减少一行,保留中文即可。播种比设想中大,间接缩小了 3MB。
  • dimens 优化查看了最近几个版本的 arsc 体积,发现有一个版本减少了 5MB。
  • 在这个版本咱们做了平板适配性能,因为咱们采纳的是 SmallestWith 限定符适配计划(能够先理解下这个屏幕适配计划),因而产生大量的尺寸资源。

一共是有 3000 多个资源,每一个资源有“values-sw300dp”到 ”values-sw1200dp” 共 90 个版本,这块存在较大的优化空间。

sqb_px_xx”这一项是用于字体适配的,但词典用到最大的字体是“sqb_px_144”,所以优化了生成规定,缩小了这一类资源。

优化后,资源数量由 3012 变成 1662,缩小了近一半。间接缩小了 2.5MB。

3.3 业务代码删除

因为 Proguard 以及 lint 等工具是从代码援用的角度进行剖析和代码裁剪,如果一些废除的代码不先进行删除会影响后续工作的成果。对于一些曾经废除没有入口的业务,不进行解决的话那么代码、资源会只增不减。业务删减应该是所有包体积流程的第一步,否则前面的去掉无用资源、图片压缩、混同等等成果都要打一个折扣。如果工夫无限的话,那么删最近的需要会比删远古时代的需要收益会大点,起因是越凑近当初的我的项目,图片资源、字体资源,以及用到 so 库都会比拟大(尤其是音视频)。

这部分工作次要是对业务性能的整顿以及沟通局部古老业务是否能够进行删除,除此之外就是须要粗疏的援用剖析将废除业务相干代码剥离进去进行删除。

一个良好的我的项目架构对于日后业务代码的剥离有很大益处。目前新开发的性能咱们采纳的是分层分模块的组织架构,功能模块之间不存在相互依赖,因而当前对于业务的抽离或者删除会更加不便。

3.4 无用资源删除

对于无用资源删除咱们次要应用了两个办法,一个是通过 lint 工具找到利用中可能没有应用的资源并逐个进行判断确认没有应用后进行删除,第二个是在 build.gradle 文件中退出 shrinkResources 在编译阶段应用 R8 工具进行删除

buildTypes {\
        release {\
            // Zipalign 优化 \
            zipAlignEnabled true\
            // 移除无用的 resource 文件 \
            shrinkResources true\
            // 移除没用的代码 \
            minifyEnabled true\
        }\
}

应用 lint 工具须要留神对以下一些场景进行再次判断确认

  1. 对于反射性援用资源,可能会被辨认成无用资源,比方 push 用到的告诉栏 icon
  2. DataBinding 用到的 layout 资源会被辨认成无用资源

3.5 压缩混同

应用 R8 工具在编译阶段对代码进行压缩混同,从而达到压缩安装包体积的成果。次要分为以下 4 个步骤:

  1. 压缩(shrink)移除未应用的类、办法、字段等;
  2. 优化(optimize)优化字节码、简化代码等操作;
  3. 混同(obfuscate)应用简短的、无意义的名称重命名类名、办法名、字段等;
  4. 预校验(preverify)为 class 增加预校验信息。

咱们在两年前就引入了 Proguard,不过思考到混同带来的问题应用了 -dontobfuscate 配置勾销混同。咱们发现之前的规定中从依赖库中继承了 -dontoptimize 的配置导致优化也没有失效。这次优化中,咱们全面解决了混同带来的泛滥问题,全面开启了优化以及混同。

因为咱们之前曾经开启过了压缩,因而须要应用到的类曾经在 proguard 中进行了保留。开启混同后还须要解决以下一些问题:

  • getIdentifier 通过名称获取资源问题。如果是一般模式,则会主动不去掉相干资源:

  • 查看 Resources.getValue 相干逻辑
  • 查看 AssetManager.open 相干逻辑
  • 反射,全局搜一下反射包,批改相干地位 java.lang.reflect
  • 解决 Retrofit 报错问题(https://github.com/square/ret…),目前应用降级 Gradle 插件版本进行解决
Caused by: java.lang.IllegalArgumentException: Method return type must not include a type variable or wildcard: ho8<su3<?>>\
    for method CheckInApi.popupConfig\
    at retrofit2.Utils.methodError(SourceFile:5)\
    at retrofit2.Utils.methodError(SourceFile:1)\
    at retrofit2.ServiceMethod.parseAnnotations(SourceFile:7)\
    at retrofit2.Retrofit.loadServiceMethod(SourceFile:4)\
    at retrofit2.Retrofit$1.invoke(SourceFile:6)\
    at java.lang.reflect.Proxy.invoke(Proxy.java:1006)\
    at $Proxy23.popupConfig(Unknown Source)\
    at com.youdao.dict.checkin.CheckInPopupManager.requestPopupConfig(SourceFile:3)\
    at java.lang.reflect.Method.invoke(Native Method)

Proguard 的规定会很大水平上影响 R8 对代码压缩和混同带来的成果,因而对压缩规定的回顾以及整顿能够帮忙进一步的体积压缩。

3.6 字体优化

字体优化这部分是在之前的版本曾经实现过的,获得的成果也挺显著,这里补充阐明一下。

– 字体裁剪

个别的字体库大小会有十几二十兆。但实际上用到的字符只有很少一部分,因而针对实际的应用场景对字体库进行适当的裁剪,收益十分大。

常用字列表:https://github.com/DavidSheh/…

字体压缩工具:https://github.com/forJrking/…

– 字体合并

一般来说,咱们开发都会模块化,不同的团队采纳在开发不同性能的时候,有可能用到雷同的字体。如果稍不留神就会复制成两份、三份,文件大大增加。词典这边的计划是把共有的字体下沉到底层 core 根底库,供各个模块援用。

4 瞻望

通过了上述的工作,目前词典的安装包体积优化了 23.7%,整体缩小了 42MB。在接下来的 Q2,咱们将筹备做两方面的事件。

4.1 包体积监控

在包体积优化的过程中,咱们在千辛万苦地砍掉一点体积之后,转过头来发现别的同学又随随便便扔进去几 MB 的大图。因而,如何坚守胜利的果实,让包体积放弃最佳状态成了重中之重。

打包工作减少了是否查看包大小限度(默认都要查看)的选项;merge request 之后,词典的打包工作会触发主动构建;

打包工作实现之后,如果须要查看包大小,那就开始触发 apkcheck 步骤;具体如下:

  1. 打包工作实现之后减少脚本操作,把本次构建的数据(如 apk 文件地址,mapping 文件地址,R 文本地址等)写入临时文件;
  2. 打包工作构建后操作减少 Trigger parameterized build on other projects,触发 apk 大小查看工作;
  3. 开始查看流程,查看流程依据参数对 apk 进行查看工作,并且把工作后果生成 html;

4.2 动静散发

– 整体业务散发

能够应用插件化以及动静加载等技术,不过这些可能不是最难的,最难的是如何把一些祖传的、低频的、而又相互依赖的代码抽离进去,造成独立模块去做散发、动静加载。

– 业务子性能散发(预计可优化 39.6MB)

  1. 数据库 (单词锁屏 8MB)

单词锁屏能够保留几百 kb 数据在本地让用户备用,同时再下载残缺的词库。

  1. OCR 引擎数据(22.5MB)

用户应该能够按需下载训练模型,而不是间接内置;当没有训练模型的时候,能够间接网络申请。

  1. 字体(9.1MB)

除了查词等高频业务,低频业务的字体能够动静散发,有则显示,无则应用零碎的即可。但 emoji 的兼容库比拟非凡,次要用在首页信息流的帖子、UGC 发帖等。如果没有兼容库,用户在遇到特地的 emoji 中可能会显示“豆腐块”,这个时候如果 emoji 字体库还没下载实现,须要进行替换兜底解决。另外哪怕用户零碎曾经有这个 emoji 内置字体,也有可能显示成果各个手机不太一样,须要跟 UI 确认一下是要替换掉,还是临时这样显示。

放弃住词典玲珑疾速、功能强大的初心是咱们不停进行性能优化的能源,在接下来的工作中,咱们会对启动速度、安装包体积以及内存占用等多方面进行继续优化和改良,欢送大家持续关注和反对!

5 参考

  • Reduce your app size
  • Shrink, obfuscate, and optimize your app
  • 抖音图片压缩插件 McImage
  • 腾讯包体积监控 ApkChecker
正文完
 0