写在前面

  文章的前面部分触及很多证明与探究相对单调(但干货满满),若只重视最佳实践可跳至最后一节阅读。本文从项目中运用的图片资源分类开端,通过Bitmap内存巨细剖析、比照Mipmap和Drawable差异、列举图片紧缩的方法和东西,最后得出最佳实战主张。

Andorid 客户端项目中不可避免地运用各种图片资源,按来源分:

  • 本地原始资源:assets、内/外部存储中的图片(如 sdcard
  • 本地编译资源:drawablemipmap
  • 网络图片资源:一般是运用图片结构(如 FrescoGlidePicasso 等)将其下载到本地再运用

按图片类型分:

  • 矢量图(VectorDrawable):从 PSD、SVG 转化而来根节点为 <vector /> 的 XML 文件
  • 位图(BitmapDrawable):*.png/*.9.png*.webp*.gif*.jpg/*.jpeg

本次探讨的主题则是 APK 打包过程中需求编译处理的位图资源

明显,这个议题是极具探讨价值的:

  1. 编译的图片资源会打进 APK 包中,而 APK 包的巨细直接影响 APP 投放本钱(这也是在头部 APP 中插件化技能经久不衰的原因之一)。
  2. 图片资源最终会被体系解析成 Bitmap,而不管 Android 将 Bitmap 保存方位是放在 ART 的 Heap 还是 Native,移动端的内存始终是及其有限的,那 OOM 和 Bitmap 就总会拉扯不清。

并且,Android 最原始的方式加载编译后的图片资源,有一套自己的缩放处理规则(包括 ImageView 的 ScaleType),若处理不当则会有各种模糊与锯齿的不良体现。

  • 2.3及以下的 Android 版本,Bitmap 在 Native上,但生命周期办理没做好
  • 之后一直到8.0之前,Bitmap 保存在虚拟机堆中,自此 Bitmap 和 OOM 结缘
  • 8.0上优化了 Bitmap 内存办理重新将其移回 Native 内存

图片内存核算

  咱们在硬盘中看到的某个图片文件的巨细是该图片的像素信息保存在某种格式容器(如 PNG )紧缩后的巨细,而 Android 中的图片要上屏渲染,还需将图片容器中的像素信息解析成 Bitmap,Bitmap 所占用的内存巨细和图片容器在硬盘上的巨细并没有肯定的线性相关联系。

  和 Bitmap 占用内存巨细有直接联系的要素主要有两点:

  1. 图片分辨率,即宽/高方向上的像素数
  2. RAM 中保存每个像素点的需求用到的二进制位数

  在 Android 的编译后的 Drawable 资源(即drawable-Xdpi),体系在履行BitmapFactory#decodeResourceStream()时,若发现图片地点目录dpi高于当时设备dpi,则会依据设备像素密度(density)和资源地点的密度文件夹方位做一次换算,从而得到转化后的分辨率。其公式为:

width=originWidth∗(devicePpi/folderDpi)width = originWidth * (devicePpi / folderDpi) height=originHeight∗(devicePpi/folderDpi)height = originHeight * (devicePpi / folderDpi)

  举个栗子:如果有一张 72*72 的 PNG 图放在 drawble-xxhdpi 文件夹,在像素密度为 320 的设备上加载时,则其对应的 Bitmap 占用内存为:

with = 72 * (320 / (160 * 3)) = 48
height = 72 * (320 / (160 * 3)) = 48
# Android默许会运用Bitmap.Config.ARGB_8888解析,即一个像素点占4B。
size = 48 * 48 * 4B = 73728(bit)

  1. Android 在查找资源时会运用“最近”匹配原则: 优先运用和设备 dpi 最近的文件资源(如果存在的话,这儿疏忽any-dpi)。
  2. ImageView#setImageResource()android:src=@drawable/xxx都会走到Context#getDrawable()BitmapFactory#decodeResourceStream()
  3. Bitmap.Config.ARGB_8888 支持透明通道,若图片非透明可显式指定 Bitmap.Config.RGB_565解析
  4. 可运用 adb shell wm density 检查与设置设备像素密度(一些手机支持智能分辨率,也可在设置中手动切换)
  5. 4.4+ 设备可运用 bitmap.getAllocationByteCount(); Dump Bitmap所占内存
  6. 特别的,以上公式在运用图片加载结构时不生效,因为 Fresco 不管来源都不转化分辨率,Glide 会依据 ImageView 组件尺度调整分辨率
  7. 依据上述公式,可推导得出结论:
    1. 若将低分辨率图,放入高像素密度文件夹,该图若被解析会因位图拉伸导致图片模糊(特别是在低像素密度设备上)
    2. 若将高分辨率图,放入低像素密度文件夹,该图若被解析会需求较大内存[注1]

Android开发合理使用图片的实践指南

  • [注1]:这个明显是个边界值BUG,经查询其在 AOSP 项目里 2017-04-22 04:25 的提交中被处理( Commit Id:50954d2b4ea938d787ef5021d75f6bc02826607a),Commit Message 为:Propagate density through AdaptiveIconDrawable and BitmapDrawable

Mipmap VS Drawable

  高版本的 Android Studio 中,默许的 App 运用图标 ic_launcher.webp 是放在mipmap 文件夹下,且放了多份。

那是不是意味着 运用开发时应在 APP 中运用多套图,并将其放在 mipmap 文件夹下呢?

要探讨这个问题,首要需求弄清楚 mipmap 和 drawable 差异(参阅YouTube)

Android开发合理使用图片的实践指南

提炼下结论:在 Google AppStore 中打 App Bundle 下发时,mipmap 资源会被完整下发,而drawable 只会挑选对应设备和兜底资源下发。

Google 的开发者文档中也有仅将图标置于 mipmap 的主张。

另外Google在Android4.3文档中关于 Mipmap 的描绘:

Android开发合理使用图片的实践指南

  这儿几句尽管含糊隐晦的描绘,并不容易 GET 到点,而以笔者粗浅的图形学常识理解起来:体系会对 mipmap 中的图片做纹路映射和抗锯齿优化,适用于有缩放动画场景运用(一起这也意味着会有额定开支)。

图片紧缩

  依据上述讨论,内存上的问题根本剖析透了,而包巨细的问题则更易于处理 —— 没错,正是图片紧缩。

  Android Studio 中供给的 Webp 转化东西,可将 *.png 图片以一种较大的紧缩率转为 *.webp 格式。但除此之外,业内大名鼎鼎的TinyPNG供给了体现更为优异的图片紧缩东西。TinyPNG 运用了多种紧缩算法混合紧缩,其中出力较大的明显是下降色彩深度,在其平台上紧缩的图片色彩深度只有8bit。

  然而这样需求将切图上传到 PNG 站点,内部资料外传,这无疑是不安全的,并且在一些团队可能还是高危操作触及违规。

  还好,Google Chrome 实验室根据 Node 开源了一套图片紧缩东西:Squoosh,也部署了官方体验站点点我跳转。能够将其部署在本地或内网的机器中安全运用。

最佳实践

  1. 对于简略的几何图形,可优先考虑运用Vector Asset制作矢量图片,如将 SVG 转成 XML
  2. 必须运用位图场景,可考虑运用网络加载云端图片,图片 URL 走 APP 配置下发
  3. 不得不放在包体中的场景:
    1. 可从 UI 处获取 *.webp 原始切图后运用 Squoosh 或 TinyPNG 东西紧缩处理
    2. 只保留一套图,优先推荐 drawable-xxhdpi 途径下

特别的:关于第3步,一般 UI 会供给多套切图,怎么准确挑选你所需求的切图呢?

一、首要确认组件的逻辑像素值。   即在正确的规划稿中找到图片显现组件的逻辑尺度。什么是正确的规划稿?当时职业 UI 画稿一般是根据 iPhoneX 的屏幕尺度,其逻辑宽度为 375。若开发中 ImageView 的巨细为24dp*24dp,切图资源巨细最大为48px*48px,若将 48*48 的图,放到 drawable-xhdpi 下会怎样?在设备 ppi=320 的情况下,依据分辨率转化公式:

width∣height=48∗320/320=48width | height = 48 * 320 / 320 = 48

这意味着体系无需将图片缩放处理也能体现杰出(即老外说的 As you See 的效果),无疑这是咱们所希望的。

简言之:这一步是 找到切图尺度是组件尺度2倍或3倍的切图,下载下来

二、切图下载下来后,运用 Squoosh 或 TinyPNG 等东西紧缩,主张将图片色彩深度调整为8位(一般 PNG 是 32 位),再将紧缩产品放入对应的文件夹

  • 2倍图放入drawble-xhdpi
  • 3倍图放入drawble-xxhdpi

:据不完全统计,如今市场上像素密度480以上的设备已经占比较大了,为此咱们可优先考虑仅保留drawble-xxhdpi文件夹,即优先运用 3 倍切图。

以上。

2023-07-19.长沙(最后更新于2023-08-02)