作者:京东零售 何骁

介绍

京喜APP早期开发首要是快速原生化迭代替代原有H5,进步用户体会,在这期间也积累了不少功用问题。之后咱们开始进行一些功用优化相关的作业,本文首要是介绍京喜图片库相关优化战略以及关于图片相关的一些相关常识

图片功用问题

作为电商APP,图片在各个事务场景被大量运用。咱们需求做到尽或许下降网络耗费/内存耗费/硬盘耗费,一同不下降图片质量,进步图片加载速度,给用户带来更好的运用体会。依据这些功用方针,咱们也经过开始功用评价梳理出了一些功用问题:

图片加载慢/流量耗费高

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

图片内存占用高

经过开始的APP内存运用评价,图片内存耗费占APP总内存耗费的比例最高,特别是大尺度图片会占用许多内存。一方面APP占用太高内存退到后台简单被体系杀死,导致下次翻开重新启动影响体会。另一方面APP大量运用内存,简单被体系杀死产生OOM。特别是咱们现在有大量的低端设备用户,设备内存相对比较低。

优化方向

依据上面分分出的一些功用问题,咱们对图片结构进行了全体重构优化。一方面是下降图片网络传输,进步图片加载速度。另一方面是削减图片内存耗费。

京喜APP - 图片库优化 | 京东云技术团队

最小化网络传输

京东图片服务器供给了多种处理功用,例如图片格局转化,图片降质,图片缩放,图片圆角等功用。这些功用经过在图片URL中添加特定参数完成,图片服务器会依据参数设置提早将图片处理完结并保存到CDN服务器。咱们能够经过添加图片处理参数,削减图片传输巨细。

尽管后台能够提早进行URL预处理,下发已添加过图片参数的图片URL。可是由于对接后台事务许多,每个事务图片参数设置差异很大无法一致,并且或许会形成功用影响,例如没有运用webP图片格局,下发太大的图片尺度。一同考虑到推进各事务后台修正成本也很高,并且前端机型多,不同机型需求运用不同的图片尺度。别的也不便利灰度降级功用,后续功用修正也不便利。所以在客户端进行图片URL预处理是更好的方法,能够一致操控,也便利之后功用更新。

图片URL预处理

京喜APP - 图片库优化 | 京东云技术团队

图片库在网络图片加载前,检测是否是京东域名的图片URL。假如域名匹配,图片结构先对图片URL进行预处理,预处理包含域名一致添加缩放参数添加webP参数添加降质参数的方法削减图片网络传输巨细。

提示:由于后台回来的图片URL或许会带有一部分图片处理参数,例如!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: ""),
                       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大屏幕设备和高端设备(内存比较高),恰当添加更多内存上限。

优化效果

京喜APP - 图片库优化 | 京东云技术团队

其他收益
  • 域名一致– 削减了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 - 图片库优化 | 京东云技术团队

优化后内存倾于平稳
京喜APP - 图片库优化 | 京东云技术团队

提示:保存每一帧也会导致更多的内存耗费。现在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