背景引见
抖音为什么要继续优化图片才干
图片才干作为抖音最基础的才干之一,服务于抖音各个业务。随着抖音图文、电商、IM等多图业务体量的增长,图片加载量级越来越大,对应的图片带宽老本也在日益参与。为了降低图片老本、优化用户阅读图片体验,须要继续始终的探求和优化图片才干,在保证图片展现品质的前提下,优化图片加载速度,降低图片全体老本,成功图片的 "好快省" 。
BDFresco简介
BDFresco是火山引擎veImageX团队基于开源Fresco拓展优化的Android端通用基础的网络图片框架,关键提供图片网络加载、图像解码、图片基础处置与变换、图片服务品质监控上报、自研HEIF软解、内存缓存战略、云控性能下发等才干,目前已笼罩到字节简直一切App。
上方将从抖音视角登程,引见抖音基于BDFresco在图片方向做了哪些优化。
优化思绪
一张网络图片完整的加载流程如下:
客户端经过网络失掉业务数据,照应内容包括对应的图片数据,经过将图片Url数据交给BDFresco加载,正式开局图片的加载流程。BDFresco会判别图片能否在内存缓存及磁盘缓存,若存在则口头对应解码或渲染操作,若不存在则直接走veImageX-CDN下载,将图片资源下载到本地后再启动解码和渲染操作。
图片加载环节不只占用了客户端内存、存储和CPU等资源,也消耗了网络流量和服务端资源。
图片的加载流程实质上是一个多级缓存逻辑,可以将图片加载流程拆分红4大**阶段,内存缓存、图片解码、磁盘缓存、网络加载,结合目的监控体系,区分针对各阶段启动优化:
优化环节
目的树立
在启动图片优化之前,须要对图片全体品质成功一次性数据清点,目的树立是至关关键的一步。经过树立目的系统,能够协助咱们了解图片现状、确定优化方向和评价优化后的成果。
BDFresco提供日志上报才干,上报的图片日志经过veImageX云端数据荡涤,最终可以在veImageX云端控制台检查图片品质相关目的。从触发图片加载,到内存、解码、磁盘、网络各个阶段都树立了完备的数据监控体系,笼罩各阶段加载耗时、成功率、客户端和CDN缓存命中率、文件大小、内存占用、大图意外监控等几百名目的。
详细动作
1 内存缓存优化
1.1 内存查找优化
内存缓存原理
BDFresco是经过Producer/Consumer接口来成功图片加载的流程,例如网络数据失掉、缓存数据失掉、图片解码等多种上班,不同阶段由不同Producer成功类处置,一切的Producer都是一层嵌套一层,发生的结果由Consumer启动生产。一个简化后的图片内存缓存逻辑如下:
其中,读取内存或磁盘缓存是经过缓存key来启动婚配,缓存key是经过Uri做转换的,可以便捷了解成cacheKey==uri,抖音在之前上线过一个缓存key优化的试验:关于同个资源的不同域名,会剔除host和query参数,即cacheKey被简化为scheme://path/name
优化方案
业务在启动图片加载时,BDFresco允许传入Uri数组,Uri均是同一资源,指向的是不同veImageX-CDN地址,实践上外部会将该批Uri(A-B-C)识别成同一个缓存key。
如下图所示,ABC3个Uri并不齐全是依照【A全流程查找->B全流程查找->C全流程查找】的顺序口头,而是会先对ABC各启动一次性内存缓存查找,再按顺序启动ABC的全流程查找。
由于ABC为同一资源,只是域名不同,在端上生成的缓存key分歧,实践上的ABC各自的内存缓存查找为有效操作,由于该环节在UI线程口头,且抖音存在多图场景,一次性滑动会触发屡次图片加载逻辑,因此局部场景会造成卡顿丢帧等状况出现。
经过将多余的内存查找流程去除,对大盘帧率有清楚优化。
1.2 动态图缓存拆分
抖音图片的内存缓存大小,是依据 java 堆内存大小来启动性能,自动大小为1/8,即32M或许64M。由于Android8后,图片内存数据不再存储在java堆上,而是存在native堆,假设继续经常使用堆内存大小来启动图片内存缓存大小的性能是不正当的,因此经过将内存缓存大小*2,宿愿能缩小解码操作,优化OOM和ANR目的。
试验后的稳固性目的显示,OOM虽然缩小了,然而疑问转换成了native解体和ANR都清楚劣化,试验并不合乎预期。
图片的缓存命中率缓和存大小成正相关,缓存大小越大,命中率越高,但随着缓存大小的增大,命中率优化空间会越来越小。
结合试验结果来看,单纯增大缓存大小会造成内存水位回升,引发ANR和native解体疑问,方案并无法行。
目前动图和静图的内存缓存经常使用同一块缓存块,BDFresco的缓存控制是LRU的淘汰战略,假设播放动图帧数过多,很容易把静图缓存给交流掉,从新切换回来静图就须要从新解码,从新解码势必带来性能的损耗和用户体验的降低,抖音上存在较多此类场景,如IM、团体页动态图混搭场景。
同时,思索到直接增大内存缓存大小,命中率优化的空间不高,所以尝试 将动图和静图缓存做隔离,动态图各经常使用一块内存缓存,能够有效地优化命中率,缩小解码操作 。
最终试验收益:
2 图片解码优化
2.1 解码格局优化
的内存大小图片长度图片宽度
单位像素占用的字节数由色彩形式Bitmap.Config选择,即ARGB 色彩通道,关键有6种类型:
目前抖音关键经常使用ARGB_8888和RGB_565两种性能,ARGB_8888允许透明通道,且色彩品质更高,RGB_565不允许透明通道,但全体内存占用少了一半,抖音的优化思绪如下:
2.2 heif解码内存优化
优化原理:
BDFresco中heic图解码原逻辑是经过jni调用解码器的解码接口,前往解码后像素数据,前往到java层再转换成Bitmap对象展现。原逻辑中存在经常使用超大暂时对象疑问,会造成java内存开支以及GC,优化后缩小大对象创立,直接在native层成功Bitmap对象构建,预期缩小heif图片解码耗时,优化必定流利度。
将原有heif图片解码流程从:
优化为流程:
修复前 :每个heic图片解码时经常使用两个大数组:
修复后: 无java层大数组经常使用,只经常使用一个40K-700K的native层的DirectByteBuffer数组。缩小两个java层大数组创立,缩小GC出现概率以及由于大数组创立造成的OOM疑问,从而带来流利度以及ANR收益。
在抖音上开试验,性能相关目的均有清楚优化:java内存占用缩小,heic解码耗时缩小,Android ANR缩小,从而清楚优化图文的生产市场,带动了全体经常使用时长收益。
2.3 自顺应控件解码
在前面,咱们提到有超越15%的图片存在一倍尺寸的糜费,造成解码阶段须要开加少量的内存,最终展如今控件上并不须要这么大的bitmap,咱们经过将图片尺寸resize至控件大小后启动解码,最终解码出小分辨率的Bitmap,能够将解码内存放开极致化。
但思索到图片糜费关键是服务端下发过大的图片,单纯在解码阶段限度大小,无法处置网络阶段的大图片疑问,带宽糜费和网络加载耗时长疑问依然没有处置,因此咱们将该阶段做了前置迁徙,在网络加载阶段启动优化,详细方案可看4.2节按需缩放方案。
3 磁盘缓存优化
经过优化客户端的磁盘缓存性能来优化缓存命中率,缩小图片恳求量级,在优化图片加载速度的状况下,也能降低图片带宽老本。
磁盘缓存分为3种:主磁盘、small磁盘、独立磁盘;各磁盘空间存在下限,驳回LRU交流算法,目前抖音关键经常使用主磁盘和独立磁盘,全体流程如下:图片自动存储在主磁盘,图片被交流概率较高;若业务指定独立磁盘cacheName,则指定图片会独自经常使用一个磁盘,被交流概率低。
4 网络加载优化
4.1 图片格局优化
经常出现图片格局
heic格局推行
veImageX平台允许最好的是heic编码格局,但到22年终,抖音Android端笼罩率无余50%,直接经过优化业务的heic占比能够大幅缩小带宽老本,优化图片加载速度。
在做heif动图试验推行时,发现团体页UI帧率存在大幅劣化,在高下端设施均有6-8帧的帧率降低,试验无法上线,针对该疑问,咱们对heif动图的解码缓存逻辑启动一次性优化,提出了heif动图独立缓存优化方案。
heif动图独立缓存
动图原理
在图片文件下载成功解析成字节流,动图正式播放之前,BDFresco会启动预解码,当动图正式播放时,会依据动图调度器的播放顺序将Bitmap渲染到屏幕上,并且在播放环节中会主动预解码下一帧,如须要播放第5帧,会同步解码第6帧率。其中预解码操作均在子线程中启动。
不同调度器的**区别为:当子线程预解码速渡过慢,下一帧须要播放的Bitmap不存在时,是继续前往帧重复播放,期待子线程启动解码,还是前往下一帧,直接在主线程启动解码渲染。
独立缓存
heif动图掉帧疑问经过排查,发现heif动图驳回了一个新的播放调度逻辑FixedSlidingHeifFrameScheduler:动图无任何预解码逻辑,在须要播放对应帧时,直接在主线程启动解码,即播放一帧解码一帧,这也造成了Heif动图在播放环节中须要在主线程占用少量CPU资源启动解码。
为什么heif动图必定在主线程解码呢?
对比其余动图允许恣意帧解码,heif动图驳回了帧间紧缩的方式,引入了I帧P帧的概念,I帧为关键帧,蕴含了图像的完整消息,能够独立解码;P帧为差异帧,没有完整的画面数据,只要与前一帧的画面差异的数据,无法独立启动解码,解码须要依赖前一帧数据。
由于AndroidBDFresco的内存缓存为LRU交流,Bitmap随时有或许被回收,因此针对Heif动图的解码,必定严厉依照动图顺序启动解码,否则会造成Heif动图播放环节中出现花屏绿屏等疑问。
方案思索:
经过试验,最终采取了独立缓存方案,在取得带宽收益的同时,团体页帧率无清楚劣化。
4.2 按需缩放
背景
图片加载流程最终会将解码后的bitmap渲染在控件上,当bitmap大小大于控件时,实践对用户感官并无影响,图片最终展现的像素值不会超越控件占据的空间,当图片大小 >> 控件大小时:
处置方案
在图片展现时上报对应的bitmap和控件大小,从上报的数据来看,存在少量业务恳求的图片大小远大于控件。因此,须要驳回一种通用的方案,在满足图片品质的前提下,客户端提供一套控件规范,依据控件大小将图片收敛至固定大小,保证图片尺寸和展现控件基本分歧,同时缩小图片碎片化疑问。
团体页、同城、介绍等多个业务均存在双列封面场景,这里以双列封面为例子:
收益
5 意外复原
虽然前面咱们对图片的加载流程做了一系列优化,但由于抖音自身图片量级大,局部业务如电商、IM等对图片明晰度有较高的要求,且存在图片加大和长图展现等操作,业务会启动超大图加载,直接将图片直接加载进内存,单张图片内存甚至高达100M+,无论在磁盘IO阶段,还是内存解码或许Bitmap拷贝环节中均会开加少量内存,最终造成卡顿、ANR甚至OOM解体,因此须要一套兜底方案来处置图片OOM频发疑问,优化图片加载的牢靠性。
抖音在系统内存触顶时,会经过监禁图片内存来缓解压力:监听系统内存的告警回调,依据不同级别监禁不同大小的图片内存缓存,降低出现OOM和ANR的几率,但因大图存在,依然存在少量OOM。
OOM兜底
内存是一个全局目的,并不能直接经过OOM堆栈确定意外要素,由于OOM出现的时刻内存或许处于高水位形态,有或许放开了一个小对象就直接触发意外。但关注到解体中Top5的堆栈大局部和图片堆栈有相关,可以正当疑心是App内图片频繁开加大内存造成。
因此针对高频的图片解码和内存拷贝逻辑,参与兜底逻辑,当代码出现OOM,主动catch,并经过肃清图片占用的内存缓存来监禁局部内存,降低内存水位:
试验结果标明,虽然局部OOM转换成native解体,但全体影响用户大幅降低,试验合乎预期。
总结
总体来看,抖音在树立了图片的全链路监控后,依据数据剖析对图片加载流程做了不少优化。
从收益角度来看,大抵可以分为老本优化和客户端体验优化两方面。老本收益关键是图片带宽老本的降低,体验收益体如今天活和OOM目的上,并且随着各种优化方案推行到更多的业务线,收益也在继续参与。
本文简明引见了抖音基于BDFresco的图片优化最佳通常、阅历积淀、业务收益。由于篇幅所限,本文对探求历程、详细成功等细节内容有所省略,但仍宿愿能给业内同仁们一点启示或许参考自创。目前BDFresco已集成到火山引擎veImageX产品,对行业放开经常使用中,如需体验抖音同款图片优化才干,可以到火山引擎veImageX官方放开经常使用。