设为首页 加入收藏

TOP

京喜APP - 图片库优化(二)
2023-07-23 13:25:24 】 浏览:89
Tags:京喜 APP
//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缓存- 优先淘汰最久未使用的图片内存。对于很多二级界面的场景,用户打开界面后并不会再次打开。但是因为这些图片缓存是最后使用,所以清除内存时也会最后移除,但是在这种场景下就不太合适。
  • 界面栈管理- 当界面关闭时将该界面的所有的图片内存移除,但是对于经常会打开的界面会导致频繁图片编解码也不太合适。

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

  • 对于购物车/我的订单这类界面,用户每次加载的图片基本固定,所以更适合在内存中常驻,当内存消耗过高时再回收。
  • 对于商详/搜索商品列表
首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇IOS使用AutoLayout让UIScrollView.. 下一篇uniapp ios app离线打包

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目