乐趣区

关于图片处理:京喜APP-图片库优化-京东云技术团队

作者:京东批发 何骁

介绍

京喜 APP 晚期开发次要是疾速 原生化 迭代代替原有 H5,进步用户体验,在这期间也积攒了不少性能问题。之后咱们开始进行一些性能优化相干的工作,本文次要是介绍 京喜图片库 相干优化策略以及对于图片相干的一些关联常识。

图片性能问题

作为电商 APP,图片在各个业务场景被大量应用。咱们须要做到尽可能升高 网络耗费 / 内存耗费 / 硬盘耗费 ,同时不升高 图片品质 ,进步图片 加载速度,给用户带来更好的应用体验。基于这些性能指标,咱们也通过初步性能评估梳理出了一些性能问题:

图片加载慢 / 流量耗费高

图片链接次要由后盾接口下发,下发图片 格局 尺寸 由每个业务后盾指定。局部业务没有应用更小的图片格式比方 WebP,或图片 尺寸 过大,都会使图片过大导致网络耗费高。特地是网络状况不佳的场景,图片加载过慢给用户带来不好的体验。同时也会导致更多的 I/ O 读写解码 耗时,造成更多的电量耗费。

图片内存占用高

通过初步的 APP 内存应用评估,图片内存耗费占 APP 总内存耗费的比例 最高,特地是大尺寸图片会占用很多内存。一方面 APP 占用太高内存退到后盾容易被零碎杀死,导致下次关上重新启动影响体验。另一方面 APP 大量应用内存,容易被零碎杀死产生OOM。特地是咱们目前有大量的低端设施用户,设施内存绝对比拟低。

优化方向

基于下面剖析出的一些性能问题,咱们对图片框架进行了整体重构优化。一方面是 升高 图片网络传输,进步图片加载速度。另一方面是 缩小 图片内存耗费。

最小化网络传输

京东 图片服务器 提供了多种解决性能,例如图片 格局转换,图片降质,图片缩放,图片圆角 等性能。这些性能通过在图片 URL 中增加特定参数实现,图片服务器会依据参数设置提前将图片解决实现并保留到 CDN 服务器。咱们能够通过增加图片解决参数,缩小图片传输大小。

尽管后盾能够提前进行 URL 预处理,下发已增加过图片参数的 图片 URL。然而因为对接后盾业务很多,每个业务图片参数设置差别很大无奈对立,而且可能会造成性能影响,例如没有应用 webP 图片格式,下发太大的 图片尺寸 。同时思考到推动各业务后盾批改老本也很高,并且前端机型多,不同机型须要应用不同的图片尺寸。另外也不不便灰度降级性能,后续性能批改也不不便。所以在 客户端 进行图片 URL 预处理 是更好的形式,能够对立管制,也不便之后性能更新。

图片 URL 预处理

图片库在网络图片加载前,检测是否是 京东 域名的图片 URL。如果 域名 匹配,图片框架先对图片 URL 进行预处理,预处理包含 域名对立 增加缩放参数 增加 webP 参数 增加降质参数 的形式缩小图片网络传输大小。

提醒:因为后盾返回的图片 URL 可能会带有一部分图片解决参数,例如https://img11.360buyimg.com/img/pingou-head/25.jpg!webp,间接追加图片参数可能会导致图片解决参数不失效,或格局谬误导致加载失败。所以转换时会先将所有图片参数提前计算出来,之后一起解决,防止增加反复参数。

域名对立

目前图片服务器提供了多个图片域名可应用,例如 m.360buyimg.comimg10.360buyimg.com 等多个域名。m.360buyimg.com次要提供给 挪动端 应用。然而因为对接了各种业务后盾,导致接口会下发不同的域名图片。图片应用 不同域名 可能会导致以下问题:

  • 不利于缓存复用 – 图片框架通常默认以URL 字符串生成图片 缓存 key,不同 域名 导致生成不同的 缓存 key硬盘缓存 无奈复用会导致图片反复下载,内存缓存 无奈复用导致同样的图片占用多份内存。
  • 不利于 HTTP/ 2 连贯复用 – 大部分界面图片比拟多,很多场景都会同时加载多张图片,特地是 首屏 通常会加载几十张图片。当加载多个图片时,每个域名都须要从新建设 HTTPS 连贯,经验 DNS 解析 /TCP 连贯 /TLS 握手 过程(目前一次 HTTPS 申请创立过程大略耗时 50-150ms)。如果利用HTTP/2 链接复用就只须要创立一次 HTTPS 申请,之后的图片申请能够缩小这部分的耗时。

所以在预处理时,如果是 京东 域名的图片,将图片 URL域名 对立替换为m.360buyimg.com

追加图片参数

图片缩放

很多业务后盾返回的原始 图片 URLsize 都比客户端理论显示的 size 要大。一方面导致应用更多的网络流量造成节约。另一方面会导致占用更多内存。同时因为图片 size 和理论显示 size 不统一导致 像素不对齐 GPU 须要做额定的插值解决,也会肯定的影响渲染性能。所以咱们通过增加缩放参数的形式,指定图片服务器下发更小和更匹配理论显示 size 的图片尺寸。

动静 scale 计算尺寸

因为 iOS 设施次要应用 2x/3x 的分辨率,所以业务方应用 API 时须要传入对应的 ptsize大小,图片库外部依据设施的 scale 进行动静计算出实在的像素宽高。

提醒:android设施因为屏幕差别比拟大,更适宜应用固定的 scale。太多的图片尺寸不利于CDN 缓存,无缓存的时候须要对图片进行相干参数解决,图片解决自身是耗时操作。

Scale 降级

  • 低端机降级 – 对于局部3xscale 的低端设施,因为机器自身内存比拟低,应用3x 分辨率计算出来的图片 像素 宽高比较大,会造成更多的内存耗费以及解码 / 渲染更多的性能耗费。所以对于宽高超过肯定要求的图片,降级到应用 2x 分辨率来计算 像素 宽高,缩小设施性能耗费。
  • iPad 降级 – 因为目前 APP 并没有针对iPad 做特定优化,所以 iPad 设施下默认是放大显示。这会导致在 iPad 下图片尺寸计算出来特地大。所以也是针对 iPad 图片尺寸做了特定限度,避免下发图片尺寸过大。
  • 大图片降级 – 失常状况下图片 宽 / 高 不应该超过屏幕 宽 / 高 。为了避免局部业务应用过大的图片size,所以增加了一个限度,最终生成的图片 像素 尺寸不能超过屏幕 宽 / 高

降质

图片服务器反对 0-100 的图片品质参数设置,通过升高图片品质能够缩小图片大小,然而品质升高太多也会影响图片的观看体验。咱们将图片品质参数设置为 q70,指定图片服务器下发70% 品质的图片。对于大部分业务,一方面能够大幅缩小图片下载大小,同时也能够保障观看体验。通过增加图片降质参数至多能够缩小 30-40% 的图片大小。

应用 WebP

依照 Google 官网的数据,与 PNG 相比,WebP无损图像的字节数要少 26%WebP 有损图像比同类 JPG 图像字节数少 25-34%。图片服务器反对转换webP 格局,能够缩小图片大小。针对 png/jpg 图片格式,增加 webP 参数,指定图片服务器下发 webp 格局。尽管 webP 相比 png/jpg 图片解码须要更长时间,但绝对网络传输速度晋升还是很大。

提醒:因为目前图片服务器并不反对 GIFwebP,GIF 并没有做解决。

URL 预处理缓存

增加轻量缓存,进步 URL 转换性能。因为 URL 转换自身有肯定的耗时,而且单个图片 URL 可能会屡次加载 / 屡次转换。转换后的 URL 会间接保留到缓存中,下次应用能够间接返回。缓存 keyURL+ 相干图片 转换参数 拼接组成。

图片 API 设计

图片解决参数通过 options 设置,默认应用 q70 图片品质以及 webP 格局。业务方在调用加载图片办法时传入,上面是 iOS 端的 API:

imageView6.jx.setImage(url: URL(string: "https://img11.360buyimg.com/img/pingou-head/25.jpg"), 
                       placeholder: nil, options: [.imageSize(CGSize(width: 40, height: 40))])

磁盘缓存优化

图片缓存查找优化

设置图片不同的 size 参数会导致更多的图片下载和磁盘缓存,例如同样一张图片 100px200px300px 尺寸因为 URL 不同会下载 3 次,同时缓存也无奈不同。因为图片库通常默认应用 URL 作为图片缓存 key,所以咱们须要针对图片缓存key 查找图片进行优化革新。简略来讲,雷同的图片小 size 的图片能够间接复用更大 size 的缓存,这样当存在更大尺寸图片时,能够防止图片间接下载并且复用磁盘缓存。

升高图片内存耗费

png/jpg等图片格式在显示之前都须要通过 解码 生成一张位图,之后依据位图创立 纹理 传给 GPU 做渲染。一张位图的内存耗费大略是 像素宽 x 像素高 x 位深 。通常图片应用的是RGBA,位深为 32 位。一张500px_500px 的大略 1MB 内存。对于 GIF 图片因为自身有多帧,所以最终的内存耗费为 单帧内存 x 帧数

咱们的优化方向一方面是通过图片缩放的形式,缩小图片位图的内存耗费。另一方面限度图片缓存下限防止缓存应用过高。

图片缩放

通过下面 URL 预处理过程让图片服务器下发更小的图片格式,曾经升高了一部分内存。然而 URL 预处理只解决了 jd 域名的 jpg/png 图片,对于 GIF京东 域名外的图片没有解决,包含一部分 URL 转换后加载失败的图片。所以对于这部分图片,咱们会在端侧做图片缩放的解决,升高内存耗费。例如一张 300px_300px 蕴含 100 帧 的 GIF 图片,理论显示区域只有 50px_50px,优化后总内存耗费可从30MB+ 内存升高到3MB

GIF 动静帧率播放

之前依据线上监控数据发现,局部页面场景偶然会配置 尺寸大 / 帧数多 GIF图片,导致内存占用极高。例如一张 500x400px 播放 200 帧 的 GIF 图片会占用 100MB+ 内存耗费。所以针对这种场景,咱们针对 GIF 做了减帧播放革新。当 GIF 图片总内存耗费大于一定量级时(例如图片内存缓存上线的 20%),将 GIF 播放的帧数适当缩小,每一帧的播放工夫减少,这样能够将内存管制在肯定范畴之内。

提醒:这里也能够通过 GIF 图片缓存 Buffer 管制内存总量,然而会导致更频繁的解码造成更多的 CPU 耗费。

图片内存缓存下限

图片缓存的设计目标是缩小 图片解码 耗费。图片第一次应用的时候,将图片进行 解码 后的位图保留在内存中,这样能够防止下次应用时防止 反复解码 。尽管图片内存高能够尽量避免图片反复解码,然而占用太高内存也会导致 APP 后盾被零碎杀掉或产生OOM 等问题。所以咱们应该将内存缓存管制在肯定范畴内。

例如 iOS 的第三方图片库 SDWebImage/Kingfisher 默认都应用零碎库 NSCache 来实现内存缓存。尽管 NSCache 会在设施内存缓和时回收内存,然而默认并不限度可保留内存最大字节数,所以在设施内存可用的状况下内存能够始终减少。所以通过设置图片缓存下限,避免图片缓存占用太高内存。图片缓存定义了一个默认的初始值下限,之后对于 3x 大屏幕设施和 高端设施(内存比拟高),适当减少更多内存下限。

优化成绩

其余收益

  • 域名对立 – 缩小了10%+ 的反复图片下载和内存耗费。同时缩小之前 多域名 图片加载时反复创立 HTTPS 申请的过程,缩小图片加载工夫。

其余策略

加载异样解决

因为大量图片通过 URL 预处理转换后,可能会存在图片不存在的异样场景导致 加载失败 。所以当产生图片加载失败时,咱们还是须要加载原始图片 URL。然而这里须要屏蔽一些非凡的加载谬误,防止非必要的加载,例如 无网络 / 网络超时 / 被动勾销加载 等谬误。之后会将谬误图片 URL 上报到后盾,不便之后调整 URL 转换策略,也能够发现一部分谬误的图片 URL 推动业务批改。同时将这部分连贯退出到 谬误连贯 缓存中,防止下次反复执行预处理和反复上报。

线上配置

目前存在的一些性能,例如 URL 预处理/ 对立域名 /WebP 应用等性能,都增加了线上配置,不便灰度 / 降级。一在呈现问题时能够降级某些性能,新性能上线时也能够进行灰度测试。

大图检测

须要有一个机制及时发现图片不符合规范的问题。一方面咱们通过线上灰度检测的形式,当发现大图片时会进行上报,后续推动业务方进行优化。另一方面咱们在日常测试阶段,会开启 Debug 检测工具,当检测到大图片时,通过 图片翻转 / 高亮背景色彩 的形式揭示业务开发同学进行优化。

Flutter 图片库优化

目前京喜 APP 有 10+ 个二级界面是基于 Flutter 开发,所以咱们也针对 Flutter 图片加载做了一些优化。

对接原生图片库

因为 Flutter 框架自带图片库只提供内存图片缓存,并不反对硬盘缓存,所以会导致图片反复下载。所以咱们通过重写 ImageProvider,当加载网络图片时,通过Channel 调用原生图片库,原生图片库下载图片到本地磁盘后,返回图片文件目录。之后 Flutter 通过文件目录加载解码图片显示。这样一方面能够利用原生图片库相干优化能力,同时也能够 复用 图片硬盘缓存防止反复下载。

缩小内存耗费

应用 Image 组件时,通过设置 cacheHeight/cacheWidth,将图片解码为置顶 像素 宽高的位图尺寸,缩小内存耗费。同时因为 Flutter 内存耗费绝对 原生 更高,所以在 Flutter 界面敞开时,通过调用 imageCache 办法革除图片内存耗费升高内存耗费。

GIF 优化

  • 动画优化 – 因为通常应用Flutter 都是混合栈的机制,原生 Flutter界面在页面导航中互相跳转。所以当 Flutter 界面存在 GIF 图片时,跳转到原生当前 GIF 动画还会始终执行。所以咱们通过在 Image 组件内监听 Flutter engine 发送的生命周期告诉,当 Flutter 界面不在栈顶时,进行 GIF 动画执行,缩小内存和 CPU 耗费。
  • 缩小解码次数 – Flutter 框架外部对GIF 渲染的解决形式,在屏幕每一帧判断以后须要显示的 GIF 帧,之后对该 GIF 帧进行解码之后渲染。因为并不会把解码过的帧保留,所以会导致频繁解码导致内存稳定大。通过优化,对曾经解码过的帧进行保留,防止反复解码的耗费,同时防止内存的稳定。

优化前内存稳定很显著

优化后内存倾于安稳

提醒:保留每一帧也会导致更多的内存耗费。目前 APP 中通常是小尺寸的 GIF 所以整体可控。能够思考设置缓冲区下限来管制缓存的图片帧数防止内存过高。

后续优化方向

更优的缓存算法

  • 优先移除最大内存 – iOS 零碎NSCache 实现。通过设置最大内存数,当内存不足时优先移除最大的值。
  • LRU 缓存 – 优先淘汰最久未应用的图片内存。对于很多 二级界面 的场景,用户关上界面后并不会再次关上。然而因为这些图片缓存是最初应用,所以革除内存时也会最初移除,然而在这种场景下就不太适合。
  • 界面栈治理 – 当界面 敞开 时将该界面的所有的图片内存移除,然而对于常常会关上的界面会导致频繁图片 编解码 也不太适合。

所以针对不同的业务场景应用不同的回收形式可能更加适合:

  • 对于 购物车 / 我的订单 这类界面,用户每次加载的图片根本固定,所以更适宜在内存中常驻,当内存耗费过高时再回收。
  • 对于 商详 / 搜寻商品列表 这类界面,通常商品列表展现的图片不一样并且用户也不会频繁进某一个特定的商详,所以更适宜 优先 移除这部分的内存。
  • 对于局部弹窗性能,图片显示后并不会再次应用,能够思考不增加到内存中。

应用更好的图片格式

应用更好的图片格式通常能够带来更小的图片字节大小。同时因为压缩率的进步,能够在缩小大小的同时进步图片品质。

提醒:应用零碎反对硬解码的图片格式更有劣势。硬解码就是应用 GPU 进行解码,相比应用 CPU 软解码性能更好更省电。

  • APNG/ 动画 WebP 代替 GIF– 依照 Google 官网的说法,GIF转换为 有损 WebP的字节数放大了 64%,而 无损 WebP字节数放大了 19%。所以应用 动画 WebP能够缩小更多的网络流量传输。APNGMozilla 推出的基于 PNG 的动图格局并且齐全反对 RGBA,相比GIF 能够缩小 20%+ 的图片大小。而且 GIF 自身只反对 256 色索引色彩以及 1 位 alpha(加上透明度后,边缘会呈现显著的锯齿),应用 APNG/WebP 也能够带来相比 GIF 更好的显示成果。

提醒:相比 GIFWebP 的解码比 GIF 占用更多的 CPU 资源。有损 WebP的解码工夫是 GIF 的 2.2 倍,而 无损 WebP的解码工夫是 GIF 的 1.5 倍。

  • HEICHEIC是基于 H.265 视频编码格局推出的图片格式。HEIC相比 WebP 能够缩小 20%+ 的图片大小,并且编解码性能更好。在零碎兼容性上,Android 9.0以上的零碎反对 HEIC。苹果在iOS14 以上零碎才提供了 WebP 硬解码,之前的零碎只能应用软解码,而 HEICiOS11之后的机器上都曾经反对硬解码,不过并不反对 浏览器
  • AVIFAVIF是基于 AV1 编码格局推出的图片格式。AVIF相比 WebP 能够缩小 30%+ 的图片大小。不过目前只有 Android 12 以上的版本反对。

提醒:这里次要是以 VP8 编码格局的 WebPVP9 编码格局的 WebP 整体性能和 HEIC 差别不大。
不过这些图片格式须要图片服务器反对之后能力应用。

Flutter

尽管咱们对 Flutter 图片库做了一些优化,但总体上还有很多优化空间。包含业界有在应用的基于 纹理 的图片计划。在原生侧将图片解码后,通过 Flutter 引擎创立 纹理 。之后讲图片纹理id 传递给 Flutter 进行渲染。这样能够对立在原生侧治理图片内存缓存,优化之前 Flutter原生 都别离有一份内存缓存的形式。而且针对于混合栈的导航栈形式,也能够更好的进行图片内存回收。另外针对Flutter,须要提供更灵便的图片内存回收策略,防止内存耗费过高。

提醒:纹理能够复用内存中的 位图 缓存,所以并不会导致更多的内存占用。纹理形式大略能缩小 30% 的内存耗费相比 Flutter 引擎图片库,次要是一些其余对象应用导致。

优化 H5 图片加载

咱们能够通过拦挡 WebView 图片加载的形式,让原生图片库来下载图片之后传递图片 二进制 数据给 WebView 显示。

缩小流量耗费

通过这种形式,咱们能够将原生图片库 URL 预处理 相干性能反对到 H5 图片,缩小 H5 加载过程中图片流量耗费,进步图片加载速度。同时因为 APP原生 WebView图片缓存机制是互相独立的,所以通过对立在原生侧治理图片缓存,能够缩小雷同图片的反复下载。

反对更多图片格式

例如在 iOS 零碎上,WKWebView目前只反对 PNG/JPG/GIF 图片格式。所以咱们能够通过在原生端实现下载 WebP/HEIC 图片,之后对图片进行 解码 再传给WebView,这样就能够反对其余图片格式的显示。

提醒:因为 WebView 不反对间接传递 位图 二进制数据显示,所以须要提前转换为 PNG/JPG 二进制数据传递。所以对于其余图片格式减少一次 PNG/JPG 编码过程会造成更多的性能耗费。不过对于 Android 零碎应该能够在 web 内核层优化缩小这块耗费。

总结

本文并没有讲底层图片加载库的具体实现,目前图片库不论是间接用第三方库还是自研图片库实现形式通常差别不大。咱们更多是关注本身业务以及如何利用图片服务器能力最大化改善网络图片加载性能。所以局部策略可能不肯定针对所有 APP 都适合,应该针对本身业务场景认真评估优化计划。

扩大链接

  • WebP
  • 手淘图片库 HEIC 应用
  • 动画 WebP 和 GIF 比拟
  • WebP 反对
  • APNG 反对
  • AVIF
退出移动版