作者:京东零售 何骁
介绍
京喜APP早期开发首要是快速原生化
迭代替代原有H5
,进步用户体会,在这期间也积累了不少功用问题。之后咱们开始进行一些功用优化相关的作业,本文首要是介绍京喜图片库
相关优化战略以及关于图片相关的一些相关常识。
图片功用问题
作为电商APP,图片在各个事务场景被大量运用。咱们需求做到尽或许下降网络耗费
/内存耗费
/硬盘耗费
,一同不下降图片质量
,进步图片加载速度
,给用户带来更好的运用体会。依据这些功用方针,咱们也经过开始功用评价梳理出了一些功用问题:
图片加载慢/流量耗费高
图片链接首要由后台接口下发,下发图片格局
和尺度
由每个事务后台指定。部分事务没有运用更小的图片格局比方WebP
,或图片尺度
过大,都会使图片过大导致网络耗费高。特别是网络状况不佳的场景,图片加载过慢给用户带来不好的体会。一同也会导致更多的I/O读写
和解码
耗时,形成更多的电量耗费。
图片内存占用高
经过开始的APP内存运用评价,图片内存耗费占APP总内存耗费的比例最高
,特别是大尺度图片会占用许多内存。一方面APP占用太高内存退到后台简单被体系杀死,导致下次翻开重新启动影响体会。另一方面APP大量运用内存,简单被体系杀死产生OOM
。特别是咱们现在有大量的低端设备用户,设备内存相对比较低。
优化方向
依据上面分分出的一些功用问题,咱们对图片结构进行了全体重构优化。一方面是下降
图片网络传输,进步图片加载速度。另一方面是削减
图片内存耗费。
最小化网络传输
京东图片服务器
供给了多种处理功用,例如图片格局转化,图片降质,图片缩放,图片圆角
等功用。这些功用经过在图片URL
中添加特定参数完成,图片服务器会依据参数设置提早将图片处理完结并保存到CDN
服务器。咱们能够经过添加图片处理参数,削减图片传输巨细。
尽管后台能够提早进行URL预处理
,下发已添加过图片参数的图片URL
。可是由于对接后台事务许多,每个事务图片参数设置差异很大无法一致,并且或许会形成功用影响,例如没有运用webP
图片格局,下发太大的图片尺度
。一同考虑到推进各事务后台修正成本也很高,并且前端机型多,不同机型需求运用不同的图片尺度。别的也不便利灰度降级功用,后续功用修正也不便利。所以在客户端
进行图片URL预处理
是更好的方法,能够一致操控,也便利之后功用更新。
图片URL预处理
图片库在网络图片加载前,检测是否是京东
域名的图片URL
。假如域名
匹配,图片结构先对图片URL
进行预处理,预处理包含域名一致
,添加缩放参数
,添加webP参数
,添加降质参数
的方法削减图片网络传输巨细。
提示:由于后台回来的图片
URL
或许会带有一部分图片处理参数,例如!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: ""),
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