作者:京东批发 何骁
介绍
京喜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