Android 端图像加载、显示、处理、监控集一体的一站式解决方案。
背景
Phenix 图像加载体系是立足手淘面向阿里巴巴全集团打造的一款 Android 端集图像加载、显示、处理、监控于一体的一站式解决方案。基于高可用、高性能、可高度定制、数据服务、省流量五大核心优势而被集团各个业务接入使用,经过长期的迭代与维护进入了相对成熟和稳定的模式。
在如今更加追求体验的背景下,对图片库提出的要求也越来越高。而图片加载长尾问题,特别是在中低端机和弱网条件下体现的更加明显。因此我们以手淘为出发点,基于用户舆情反馈以及图片库自身埋点数据,对影响图片正常展示的长尾问题进行了较为完整的梳理。
据统计,淘宝 Android 端每月图片类舆情在几百条,基本都表现出白屏、图片加载失败的现象。其中明确图片加载慢的 case 下,基本有以下几个因素:
- 用户网络慢,图片下载耗时久
- 磁盘缓存读取耗时,存在读取阻塞未返回的情况
- 图片尺寸大,原图访问 RT 耗时久
问题发现
结合舆情日志,通过图片库埋点数据统计发现:
- 图片加载磁盘读取慢(超过 N 秒) 次数每日百万次以上
- 图片库内部线程调度慢 (超过 N 秒) 次数每日千万次以上
- 图片网络错误类中,超时和连接错误每日次数亿次以上
- 首页信息流图片秒开率低,主要受制于原图访问问题
深入剖析场景根因
▐ 磁盘读取阻塞
磁盘缓存读取作为图片加载流程中非常关键的一个环节,发生阻塞时后续联网下载图片资源、图片解码、图片上屏等流程会被动等待,在用户视角即为图片未加载或页面白屏。
▐ 线程调度频繁
图片库将图片的请求分割到多个阶段 1. 内存缓存查找;2. 本地图片处理;3. 磁盘缓存查找;4. 网络请求;5. 图片解码;6. 图片回调上屏。
每个阶段都会进行线程切换,这些任务都共享一个大的线程池,频繁的线程切换会增加性能损耗。
▐ 网络任务队列阻塞
图片库内部针对网络任务(包括网络连接和网络数据下载)制定了特殊的调度器,该调度器限制了在强弱网环境下允许被传递到线程池中的最大任务数。
而弱网条件下图片数据返回慢,后续图片请求任务被阻塞在网络调度器的内置队列中,而不会进入线程池等待被执行,图片加载流程会因为排队而阻塞在队列中,上屏时间被延后,影响用户体验。
▐ 网络下载等待
图片网络数据下载受限于网络速度,弱网条件下数据返回慢。在图片库共享线程池时,网络下载任务占用着线程不释放,会造成两个比较明显的问题:
- 当前图片加载流程被阻塞
- 线程池中线程被占用,后续图片请求需要排队等待
▐ 图片尺寸大&原图访问
选取 JPG/PNG/WEBP/HEIC 几种图片在最近一个月的整体加载时长,从图中我们可以看出与其他图片格式相比,HEIC格式图片的整体耗时是最短的。这对于用户体验,特别是在中低端机上的体验是有明显的差距的。
非 HEIC 图片在 CDN 命中率低,绝大多数情况下会访问原图。同时图片尺寸大,再加上图片库内部的调度策略受网络资源下载时长的影响,会加剧单张图片加载总耗时。
此外,Android 12 引入了对使用 AV1 图片文件格式 (AVIF) 图片的支持,这种格式可以显著提升相同文件大小下的图片质量,而图片库目前还不支持。
分场景优化
基于以上提出的几类比较明显的限制根因,我们为图片上屏的场景分别制定了优化方案,并通过解决图片加载长尾问题来提升用户体验。
▐ 统一存储空窗优化
针对磁盘缓存读取阻塞问题,图片库增加存储读取异常检测与异常状态恢复检测方案。
- 磁盘读取耗时检测:
预设定磁盘读取阻塞的条件(可以通过云端配置平台动态调整)
在连续多次检测到磁盘缓存读取任务被阻塞时主动跳过二级缓存,后续图片请求全部走网络请求 - 磁盘缓存读取跳过状态恢复检测:
预设定恢复二级缓存读取的条件(可以通过云端配置平台动态调整)
在跳过磁盘读取一段时间后,同时发起多个磁盘缓存读取任务,并且在规定时间内正常返回时恢复二级缓存读取,后续的图片加载流程与初始策略一致
▐ 线程调度方案改造
- 优化点1: 减少线程切换
重新梳理各任务之间的关联情况以及各任务在执行时的耗时瓶颈点,最大可能的降低线程切换的频率。
- 仅保留两次线程调度
主线程切换至工作线程
网络库线程切换至图片库线程
- 优化点2: 去掉网络调度内部阻塞
网络调度器的存在主要是为了限制在强弱网环境下允许被传递到线程池中的最大任务数,避免出现网络任务占满线程池,而后续可以从缓存中获取的图片也无法上屏显示的情况。
基于 优化点3 中网络数据下载变更为分片回调,不再占用图片库线程。此时图片库内部的网络调度器就不再被使用,该问题也会被规避。
- 优化点3:网络数据分块接收
以一种空间换时间的方式,解决弱网条件下网络数据返回慢所导致的线程占用问题。
- 网络库首次回调创建指定大小的容器,之后分段接受图片数据,直到所有数据全部返回后切换到图片库线程执行后续任务
- 弱网条件下返回数据慢也不影响图片库中线程的使用,通过提高线程使用率,降低等待时间来缩短图片加载时长
▐ 尺寸更小的图片格式
Android 12 引入了对使用 AV1 图片文件格式 (AVIF) 图片的支持。AVIF 是一种使用 AV1 编码的图片和图片序列的容器格式。利用了视频压缩的帧内编码内容。与以前的图片格式(例如 JPEG)相比,这种格式可显著提升相同文件大小下的图片质量。
采用新一代 AV1 图片格式,图片的压缩率相比 HEIC 能提高约 25%,能够在降低图片尺寸的同时缩短图片下载时长,提升用户体验。
通过与图片空间合作,在图片库内部增加了 AVIF 图片格式的支持和适配。目前已经历了 3 次完整的独立灰度,后期会先在手淘中落地并逐渐放量。
总结
磁盘缓存阻塞问题逐步放量至全量上线后优化效果明显,每周舆情数相比优化初期降低超过50%,磁盘读取耗时超过 N 秒样本数降低90%;
线程调度优化方案已经独立灰度完成,从灰度数据看线程调度慢 (超过 N 秒) 占比小于 0.01%;预计上线后与优化前相比每日能减少 90% 的样本数;
AVIF 图片格式独立灰度后图片尺寸与HEIC相比优化 15%,解码耗时基本保持一致,由于目前CDN 命中率较低,RT 耗时较长,需要等放量以后整体优化效果才能体现。同时后续压缩率与HEIC 相比预期达到 25%,会根据实际的埋点数据来决定后期的放量节奏。
我们还能做什么?
▐ 图片格式收敛至HEIC(后续可统一升级到AVIF)
目前 Native 页面中 HEIC 图片占比约 60%,而 H5 页面中只有部分资源适配了 HEIC。
- 在 Native 页面,使用图片库提供的组件或者调用图片库提供的 CDN 适配策略时,图片库会对传入的 URL 做处理,按照图片空间的规则拼接上 HEIC 后缀,这样当 URL 到达 CDN 时,如果有HEIC 图片缓存直接返回,如果没有会请求图片空间的服务将原始图片转码成 HEIC 图片
- H5 页面中由于 UC 内核提供了外接三方解码器的能力,同时手淘支持远程 SO 的下载,在这两个前提下 H5 页面的图片使用图片库提供的 CDN 策略,以及图片库提供的外置解码器来加载 HEIC 图片
针对 Native 页面可以推动业务方使用图片库提供的组件,或者主动调用图片库提供的 CDN 策略来接入对 HEIC 图片的支持。
针对 H5 页面正在和业务方做沟通以及制定更加完善的替换方案,争取早日完成图片格式的收敛。
写在最后
对于图片上屏体验而言,Phenix 图片库任重而道远。在一轮又一轮的技术迭代下,手淘的图片上屏体验在不断的优化和提升。希望在未来,图片库在优化自身的同时能够结合网络库、图片空间等模块进一步挖掘隐藏在各个环节的性能瓶颈,帮助提升用户体验。