作者:京东批发 何骁
介绍
京喜 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.com
,img10.360buyimg.com
等多个域名。m.360buyimg.com
次要提供给 挪动端
应用。然而因为对接了各种业务后盾,导致接口会下发不同的域名图片。图片应用 不同域名
可能会导致以下问题:
不利于缓存复用
– 图片框架通常默认以URL
字符串生成图片缓存 key
,不同域名
导致生成不同的缓存 key
。硬盘缓存
无奈复用会导致图片反复下载,内存缓存
无奈复用导致同样的图片占用多份内存。不利于 HTTP/ 2 连贯复用
– 大部分界面图片比拟多,很多场景都会同时加载多张图片,特地是首屏
通常会加载几十张图片。当加载多个图片时,每个域名都须要从新建设HTTPS
连贯,经验DNS 解析 /TCP 连贯 /TLS 握手
过程(目前一次 HTTPS 申请创立过程大略耗时50-150ms
)。如果利用HTTP/2
链接复用就只须要创立一次HTTPS
申请,之后的图片申请能够缩小这部分的耗时。
所以在预处理时,如果是 京东
域名的图片,将图片 URL域名
对立替换为m.360buyimg.com
。
追加图片参数
图片缩放
很多业务后盾返回的原始 图片 URL
的 size
都比客户端理论显示的 size
要大。一方面导致应用更多的网络流量造成节约。另一方面会导致占用更多内存。同时因为图片 size
和理论显示 size
不统一导致 像素不对齐
,GPU
须要做额定的插值解决,也会肯定的影响渲染性能。所以咱们通过增加缩放参数的形式,指定图片服务器下发更小和更匹配理论显示 size
的图片尺寸。
动静 scale 计算尺寸
因为 iOS
设施次要应用 2x/3x
的分辨率,所以业务方应用 API 时须要传入对应的 ptsize
大小,图片库外部依据设施的 scale
进行动静计算出实在的像素宽高。
提醒:
android
设施因为屏幕差别比拟大,更适宜应用固定的scale
。太多的图片尺寸不利于CDN
缓存,无缓存的时候须要对图片进行相干参数解决,图片解决自身是耗时操作。
Scale 降级
低端机降级
– 对于局部3x
scale 的低端设施,因为机器自身内存比拟低,应用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
图片解码须要更长时间,但绝对网络传输速度晋升还是很大。
提醒:因为目前图片服务器并不反对
GIF
转webP
,GIF 并没有做解决。
URL 预处理缓存
增加轻量缓存,进步 URL
转换性能。因为 URL
转换自身有肯定的耗时,而且单个图片 URL
可能会屡次加载 / 屡次转换。转换后的 URL
会间接保留到缓存中,下次应用能够间接返回。缓存 key
由URL
+ 相干图片 转换参数
拼接组成。
图片 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
参数会导致更多的图片下载和磁盘缓存,例如同样一张图片 100px
、200px
、300px
尺寸因为 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
能够缩小更多的网络流量传输。APNG
是Mozilla
推出的基于PNG
的动图格局并且齐全反对RGBA
,相比GIF
能够缩小20%+
的图片大小。而且GIF
自身只反对 256 色索引色彩以及 1 位 alpha(加上透明度后,边缘会呈现显著的锯齿),应用APNG
/WebP
也能够带来相比GIF
更好的显示成果。
提醒:相比
GIF
,WebP
的解码比GIF
占用更多的 CPU 资源。有损 WebP
的解码工夫是GIF
的 2.2 倍,而无损 WebP
的解码工夫是GIF
的 1.5 倍。
HEIC
–HEIC
是基于H.265
视频编码格局推出的图片格式。HEIC
相比WebP
能够缩小 20%+ 的图片大小,并且编解码性能更好。在零碎兼容性上,Android 9.0
以上的零碎反对HEIC
。苹果在iOS14
以上零碎才提供了WebP
硬解码,之前的零碎只能应用软解码,而HEIC
在iOS11
之后的机器上都曾经反对硬解码,不过并不反对浏览器
。AVIF
–AVIF
是基于AV1
编码格局推出的图片格式。AVIF
相比WebP
能够缩小 30%+ 的图片大小。不过目前只有Android 12
以上的版本反对。
提醒:这里次要是以
VP8
编码格局的WebP
,VP9
编码格局的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