抖音 Android 端圖片優(yōu)化實踐
背景介紹
抖音為什么要持續(xù)優(yōu)化圖片能力
圖片能力作為抖音最基礎(chǔ)的能力之一,服務(wù)于抖音各個業(yè)務(wù)。隨著抖音圖文、電商、IM等多圖業(yè)務(wù)體量的增長,圖片加載量級越來越大,對應(yīng)的圖片帶寬成本也在日益增加。為了降低圖片成本、提升用戶瀏覽圖片體驗,需要持續(xù)不斷的探索和優(yōu)化圖片能力,在保證圖片展示質(zhì)量的前提下,提升圖片加載速度,降低圖片整體成本,實現(xiàn)圖片的 "好快省" 。
BDFresco簡介
BDFresco是火山引擎veImageX團隊基于開源Fresco拓展優(yōu)化的Android端通用基礎(chǔ)的網(wǎng)絡(luò)圖片框架,主要提供圖片網(wǎng)絡(luò)加載、圖像解碼、圖片基礎(chǔ)處理與變換、圖片服務(wù)質(zhì)量監(jiān)控上報、自研HEIF軟解、內(nèi)存緩存策略、云控配置下發(fā)等能力,目前已覆蓋到字節(jié)幾乎所有App。
下面將從抖音視角出發(fā),介紹抖音基于BDFresco在圖片方向做了哪些優(yōu)化。
優(yōu)化思路
一張網(wǎng)絡(luò)圖片完整的加載流程如下:
客戶端通過網(wǎng)絡(luò)獲取業(yè)務(wù)數(shù)據(jù),響應(yīng)內(nèi)容包括對應(yīng)的圖片數(shù)據(jù),通過將圖片Url數(shù)據(jù)交給BDFresco加載,正式開始圖片的加載流程。BDFresco會判斷當(dāng)前圖片是否在內(nèi)存緩存及磁盤緩存,若存在則執(zhí)行對應(yīng)解碼或渲染操作,若不存在則直接走veImageX-CDN下載,將圖片資源下載到本地后再進行解碼和渲染操作。
圖片加載過程不僅占用了客戶端內(nèi)存、存儲和CPU等資源,也消耗了網(wǎng)絡(luò)流量和服務(wù)端資源。
圖片的加載流程本質(zhì)上是一個多級緩存邏輯,可以將圖片加載流程拆分成4大核心階段,內(nèi)存緩存、圖片解碼、磁盤緩存、網(wǎng)絡(luò)加載,結(jié)合指標(biāo)監(jiān)控體系,分別針對各階段進行優(yōu)化:
- 內(nèi)存緩存優(yōu)化:當(dāng)前Android內(nèi)存緩存命中率高達50%,內(nèi)存緩存以占用App寶貴的內(nèi)存為代價,使得我們可以快速地訪問圖片;但內(nèi)存緩存的存在并不會直接導(dǎo)致App的OOM或者卡頓情況變嚴(yán)重,相反,根據(jù)特定場景配置合理的內(nèi)存緩存配置能夠減少圖片頻繁的解碼和內(nèi)存申請,甚至可以帶來OOM和ANR的優(yōu)化。
- 圖片解碼優(yōu)化:當(dāng)內(nèi)存緩存失敗后,圖片文件會進行解碼,最終以bitmap形式在內(nèi)存中存在,目前解碼后的bitmap平均大小為800KB,90分位為5MB,99分位更是高達夸張的11MB,解碼流程需要頻繁申請內(nèi)存,同時有超過15%的圖片存在一倍尺寸的浪費,對客戶端的性能影響非常大,因此如何減少解碼階段的內(nèi)存申請是我們需要重點解決的問題。
- 磁盤緩存優(yōu)化:盡管對比內(nèi)存緩存命中率,磁盤緩存命中率只有10%,但理論上內(nèi)存中的bitmap在磁盤中都有對應(yīng)的原始文件存在,因此想要整體緩存命中率,我們更關(guān)注磁盤緩存的優(yōu)化,需要通過合理的磁盤配置,讓存儲空間利用率更高。
- 網(wǎng)絡(luò)加載優(yōu)化:雖然網(wǎng)絡(luò)階段失敗率高達2.5%,但經(jīng)過數(shù)據(jù)排查和修復(fù),實際失敗率< 0.1%,優(yōu)化空間不多,考慮到網(wǎng)絡(luò)加載是整體流程耗時最長的,耗時占了近90%,其中主要影響為文件過大導(dǎo)致的加載耗時長,因此需要重點解決下發(fā)大文件問題,優(yōu)化網(wǎng)絡(luò)加載耗時。
優(yōu)化過程
指標(biāo)建設(shè)
在進行圖片優(yōu)化之前,需要對圖片整體質(zhì)量完成一次數(shù)據(jù)盤點,指標(biāo)建設(shè)是至關(guān)重要的一步。通過建立指標(biāo)系統(tǒng),能夠幫助我們了解圖片現(xiàn)狀、確定優(yōu)化方向和評估優(yōu)化后的效果。
BDFresco提供日志上報能力,上報的圖片日志經(jīng)過veImageX云端數(shù)據(jù)清洗,最終可以在veImageX云端控制臺查看圖片質(zhì)量相關(guān)指標(biāo)。從觸發(fā)圖片加載,到內(nèi)存、解碼、磁盤、網(wǎng)絡(luò)各個階段都建立了完備的數(shù)據(jù)監(jiān)控體系,覆蓋各階段加載耗時、成功率、客戶端和CDN緩存命中率、文件大小、內(nèi)存占用、大圖異常監(jiān)控等幾百項指標(biāo)。
具體舉措
1 內(nèi)存緩存優(yōu)化
1.1 內(nèi)存查找優(yōu)化
內(nèi)存緩存原理
BDFresco是通過Producer/Consumer接口來實現(xiàn)圖片加載的流程,例如網(wǎng)絡(luò)數(shù)據(jù)獲取、緩存數(shù)據(jù)獲取、圖片解碼等多種工作,不同階段由不同Producer實現(xiàn)類處理,所有的Producer都是一層嵌套一層,產(chǎn)生的結(jié)果由Consumer進行消費。一個簡化后的圖片內(nèi)存緩存邏輯如下:
其中,讀取內(nèi)存或磁盤緩存是通過緩存key來進行匹配,緩存key是通過Uri做轉(zhuǎn)換的,可以簡單理解成cacheKey==uri,抖音在之前上線過一個緩存key優(yōu)化的實驗:對于同個資源的不同域名,會剔除host和query參數(shù),即cacheKey被簡化為scheme://path/name
優(yōu)化方案
業(yè)務(wù)在進行圖片加載時,BDFresco支持傳入Uri數(shù)組,Uri均是同一資源,指向的是不同veImageX-CDN地址,實際上內(nèi)部會將該批Uri(A-B-C)識別成同一個緩存key。
如下圖所示,ABC3個Uri并不完全是按照【A全流程查找->B全流程查找->C全流程查找】的順序執(zhí)行,而是會先對ABC各進行一次內(nèi)存緩存查找,再按順序進行ABC的全流程查找。
由于ABC為同一資源,只是域名不同,在端上生成的緩存key一致,實際上的ABC各自的內(nèi)存緩存查找為無效操作,由于該環(huán)節(jié)在UI線程執(zhí)行,且抖音存在多圖場景,一次滑動會觸發(fā)多次圖片加載邏輯,因此部分場景會導(dǎo)致卡頓丟幀等情況發(fā)生。
通過將多余的內(nèi)存查找流程去除,對大盤幀率有明顯提升。
1.2 動靜圖緩存拆分
抖音圖片的內(nèi)存緩存大小,是根據(jù) java 堆內(nèi)存大小來進行配置,默認(rèn)大小為1/8,即32M或者64M。由于Android 8后,圖片內(nèi)存數(shù)據(jù)不再存儲在java堆上,而是存在native堆,如果繼續(xù)使用堆內(nèi)存大小來進行圖片內(nèi)存緩存大小的配置是不合理的,因此通過將內(nèi)存緩存大小*2,希望能減少解碼操作,優(yōu)化OOM和ANR指標(biāo)。
實驗后的穩(wěn)定性指標(biāo)顯示,OOM雖然減少了,但是問題轉(zhuǎn)換成了native崩潰和ANR都顯著劣化,實驗并不符合預(yù)期。
圖片的緩存命中率和緩存大小成正相關(guān),緩存大小越大,命中率越高,但隨著緩存大小的增大,命中率提升空間會越來越小。
結(jié)合實驗結(jié)果來看,單純增大緩存大小會導(dǎo)致內(nèi)存水位上升,引發(fā)ANR和native崩潰問題,方案并不可行。
目前動圖和靜圖的內(nèi)存緩存使用同一塊緩存塊,BDFresco的緩存管理是LRU的淘汰策略,如果播放動圖幀數(shù)過多,很容易把靜圖緩存給替換掉,重新切換回來靜圖就需要重新解碼,重新解碼勢必帶來性能的損耗和用戶體驗的降低,抖音上存在較多此類場景,如IM、個人頁動靜圖混搭場景。
同時,考慮到直接增大內(nèi)存緩存大小,命中率提升的空間不高,所以嘗試將動圖和靜圖緩存做隔離,動靜圖各使用一塊內(nèi)存緩存,能夠有效地提升命中率,減少解碼操作。
最終實驗收益:
- 抖音通過拆分動靜圖緩存,單塊緩存大小不變,整體緩存增大,日活顯著提升,OOM顯著降低,大盤幀率顯著正向。
- 抖極通過拆分動靜圖緩存,單塊緩存大小變?yōu)?/2,整體緩存不變,日活顯著提升,人均使用時長顯著正向,OOM顯著降低,大盤幀率顯著正向。
2 圖片解碼優(yōu)化
2.1 解碼格式優(yōu)化
的內(nèi)存大小圖片長度圖片寬度
單位像素點占用的字節(jié)數(shù)
單位像素占用的字節(jié)數(shù)由顏色模式Bitmap.Config決定,即ARGB 顏色通道,主要有6種類型:
- ALPHA_8:只有一個alpha通道,8bit,每個像素占1Byte;
- ARGB_4444:包含紅綠藍alpha4個通道,每個通道4bit,每個像素占2Byte;
- ARGB_8888:包含紅綠藍alpha4個通道,每個通道8bit,每個像素占4Byte;
- RGB_565:包含紅綠藍3個通道,其中紅色占5bit,綠色占6bit,藍色占5bit,每個像素占2Byte;
- RGBA_F16:包含紅綠藍alpha4個通道,每個通道8bit,每個像素占4Byte;
- HARDWARE:ARGB_8888的特殊配置,Bitmap會直接存儲在顯存中。
目前抖音主要使用ARGB_8888和RGB_565兩種配置,ARGB_8888支持透明通道,且顏色質(zhì)量更高,RGB_565不支持透明通道,但整體內(nèi)存占用少了一半,抖音的優(yōu)化思路如下:
- 低端機默認(rèn)使用RGB_565進行解碼,減少內(nèi)存占用。
- 抖音部分圖片不攜帶透明通道,如所有的heic圖,但業(yè)務(wù)指定為ARGB_8888,導(dǎo)致透明通道做無效占用,在內(nèi)存上造成浪費,因此可以在解碼階段將不攜帶透明通道的圖片強制降級為RGB_565,在犧牲一定程度的顏色質(zhì)量下減少近一半的內(nèi)存占用和解碼性能損耗。
- 由于部分bitmap的操作如圓角、高斯模糊等依賴透明通道的渲染,若強制將無透明通道圖片降級成565,可能會導(dǎo)致部分業(yè)務(wù)無法正常展示,因此需要針對這類業(yè)務(wù)進行加白處理。
2.2 heif解碼內(nèi)存優(yōu)化
優(yōu)化原理:
BDFresco中heic圖解碼原邏輯是通過jni調(diào)用解碼器的解碼接口,返回解碼后像素數(shù)據(jù),返回到j(luò)ava層再轉(zhuǎn)換成Bitmap對象展示。原邏輯中存在使用超大臨時對象問題,會導(dǎo)致java內(nèi)存開銷以及GC,優(yōu)化后減少大對象創(chuàng)建,直接在native層完成Bitmap對象構(gòu)建,預(yù)期減少heif圖片解碼耗時,提升一定流暢度。
將原有heif圖片解碼流程從:
優(yōu)化為流程:
修復(fù)前:每個heic圖片解碼時使用兩個大數(shù)組:
- 圖片原始數(shù)據(jù),大小為圖片文件大小,一般在40K-700K之間
- 圖片解碼后數(shù)據(jù):大小為圖片寬高4,一般在1-11M之間
修復(fù)后: 無java層大數(shù)組使用,只使用一個40K-700K的native層的DirectByteBuffer數(shù)組。減少兩個java層大數(shù)組創(chuàng)建,減少GC發(fā)生概率以及因為大數(shù)組創(chuàng)建導(dǎo)致的OOM問題,從而帶來流暢度以及ANR收益。
在抖音上開實驗,性能相關(guān)指標(biāo)均有顯著提升:java內(nèi)存占用減少,heic解碼耗時減少,Android ANR減少,從而顯著提升圖文的消費市場,帶動了整體使用時長收益。
2.3 自適應(yīng)控件解碼
在前面,我們提到有超過15%的圖片存在一倍尺寸的浪費,導(dǎo)致解碼階段需要申請大量的內(nèi)存,最終展示在控件上并不需要這么大的bitmap,我們通過將圖片尺寸resize至控件大小后進行解碼,最終解碼出小分辨率的Bitmap,能夠?qū)⒔獯a內(nèi)存申請極致化。
但考慮到圖片浪費主要是服務(wù)端下發(fā)過大的圖片,單純在解碼階段限制大小,無法解決網(wǎng)絡(luò)階段的大圖片問題,帶寬浪費和網(wǎng)絡(luò)加載耗時長問題仍然沒有解決,因此我們將該階段做了前置遷移,在網(wǎng)絡(luò)加載階段進行優(yōu)化,具體方案可看4.2節(jié)按需縮放方案。
3 磁盤緩存優(yōu)化
通過優(yōu)化客戶端的磁盤緩存配置來提升緩存命中率,減少圖片請求量級,在提升圖片加載速度的情況下,也能降低圖片帶寬成本。
磁盤緩存分為3種:主磁盤、small磁盤、獨立磁盤;各磁盤空間存在上限,采用LRU替換算法,目前抖音主要使用主磁盤和獨立磁盤,整體流程如下:圖片默認(rèn)存儲在主磁盤,圖片被替換概率較高;若業(yè)務(wù)指定獨立磁盤cacheName,則指定圖片會單獨使用一個磁盤,被替換概率低。
- 主磁盤存儲空間增大:抖音Android端存儲空間上限為40M,考慮到該值為fresco的默認(rèn)值,配置值主要參考當(dāng)年設(shè)備的存儲空間,因此可以針對存儲空間較多的設(shè)備,增加圖片存儲配置,提升磁盤緩存命中率。
- 實驗結(jié)果表明:隨著存儲空間的增大,磁盤緩存命中率顯著上漲,進一步帶來圖片量級的減少,當(dāng)圖片存儲上限提升至80M時,Android大盤量級-5%
- 獨立磁盤推廣:針對復(fù)用率高的圖片場景,推薦接入獨立磁盤緩存,可以減少被其他業(yè)務(wù)圖片LRU替換的幾率,提升圖片的磁盤緩存命中率。
- 以IM表情包為例,我們拉取IM業(yè)務(wù)的圖片緩存命中率數(shù)據(jù)分析,
表情包
命中率僅有7%,對比同樣使用獨立磁盤的IM
普通圖片的28%和個人頁主態(tài)
的31%,表情包磁盤命中率偏低。 - 將IM表情包接入獨立磁盤后,表情包請求量減少27%
4 網(wǎng)絡(luò)加載優(yōu)化
4.1 圖片格式優(yōu)化
常見圖片格式
- image:原圖,未經(jīng)過veImageX壓縮處理。
- JPEG:全稱為Joint Photographic Experts Group(聯(lián)合圖像專家組),于1992發(fā)布,是一種有損壓縮的光柵圖像文件格式,壓縮率越高圖片質(zhì)量越差,同時不支持透明通道。
- PNG:全稱為Portable Network Graphics(便攜式網(wǎng)絡(luò)圖形),在1997年3月作為知識性RFC 2083發(fā)布,于2004年作為ISO/IEC標(biāo)準(zhǔn)發(fā)布,PNG也是一種柵格圖形格式,但支持無損壓縮,同時也支持?jǐn)y帶透明通道信息。
- WebP:是一種由谷歌開發(fā)的圖片格式,于2010年發(fā)布,支持有損壓縮和無損壓縮圖片文件格式,提供更高的壓縮率和更快的加載速度。對比jpeg和png格式,在相同圖片質(zhì)量的情況下,文件體積能減少30%+,同時WebP 圖片格式還支持透明通道和動畫,目前抖音Android所有版本均支持Webp格式。
- HEIC(BVC1):基于火山引擎自研BVC算法進行封裝的圖片(17項第一,火山自研編碼器在MSU大賽多項奪冠[https://www.toutiao.com/article/6951287905268843011/?upstream_biz=doubao&source=m_redirect]),通常的文件后綴名為heic,對比Webp格式,在相同圖片質(zhì)量的情況下,文件體積能再減少30%+,帶寬收益更加明顯。但heic格式也存在缺點:由于高效編碼會導(dǎo)致解碼性能損耗略有增加,但體積較小也會帶來網(wǎng)絡(luò)耗時的降低,最終總的加載耗時基本打平或略有降低,目前抖音Android端已全量使用自研BVC軟解實現(xiàn)解碼。
- vvic:字節(jié)基于 BVC2算法自研的圖片格式,采用的是VVC的圖片編碼格式,又稱BVC2編碼格式,對比heic的BVC1壓縮率更高。
heic格式推廣
當(dāng)前veImageX平臺支持最好的是heic編碼格式,但到22年初,抖音Android端覆蓋率不足50%,直接通過提升業(yè)務(wù)的heic占比能夠大幅減少帶寬成本,提升圖片加載速度。
- JPEG->heic,大幅減少帶寬成本80%以上,加載速度提升30%+
- webp->heif,個人頁動圖平均文件大小-25.33%,加載速度提升30%+
在做heif動圖實驗推廣時,發(fā)現(xiàn)個人頁UI幀率存在大幅劣化,在高低端設(shè)備均有6-8幀的幀率下降,實驗無法上線,針對該問題,我們對heif動圖的解碼緩存邏輯進行一次優(yōu)化,提出了heif動圖獨立緩存優(yōu)化方案。
heif動圖獨立緩存
動圖原理
在圖片文件下載完成解析成字節(jié)流,動圖正式播放之前,BDFresco會進行預(yù)解碼,當(dāng)動圖正式播放時,會根據(jù)動圖調(diào)度器的播放順序?qū)itmap渲染到屏幕上,并且在播放過程中會主動預(yù)解碼下一幀,如當(dāng)前需要播放第5幀,會同步解碼第6幀率。其中預(yù)解碼操作均在子線程中進行。
不同調(diào)度器的核心區(qū)別為:當(dāng)子線程預(yù)解碼速度過慢,下一幀需要播放的Bitmap不存在時,是繼續(xù)返回當(dāng)前幀重復(fù)播放,等待子線程進行解碼,還是返回下一幀,直接在主線程進行解碼渲染。
- SmoothSlidingFrameScheduler:默認(rèn)調(diào)度器,在子線程預(yù)解碼速度跟不上播放速度時,會降低動圖的播放速度,如重復(fù)播放當(dāng)前幀,保證不在主線程進行解碼,會導(dǎo)致動圖播放不流程,但對頁面性能非常好,不會引起卡頓。
- DropFramesFrameScheduler:嚴(yán)格按照圖片的時間標(biāo)準(zhǔn)進行播放,若預(yù)解解碼速度太慢,則直接在主線程進行解碼,以保證對應(yīng)幀能夠在對應(yīng)時間內(nèi)進行解碼并且渲染到屏幕上,缺點是會在主線程進行解碼,可能會引起頁面的卡頓。
- 自定義調(diào)度器:業(yè)務(wù)自定義實現(xiàn)getFrameNumberToRender接口,支持倒序播放、跳幀播放等特殊邏輯。
獨立緩存
heif動圖掉幀問題經(jīng)過排查,發(fā)現(xiàn)heif動圖采用了一個新的播放調(diào)度邏輯FixedSlidingHeifFrameScheduler:動圖無任何預(yù)解碼邏輯,在需要播放對應(yīng)幀時,直接在主線程進行解碼,即播放一幀解碼一幀,這也導(dǎo)致了Heif動圖在播放過程中需要在主線程占用大量CPU資源進行解碼。
為什么heif動圖必須在主線程解碼呢?
對比其他動圖支持任意幀解碼,heif動圖采用了幀間壓縮的方式,引入了I幀P幀的概念,I幀為關(guān)鍵幀,包含了當(dāng)前圖像的完整信息,能夠獨立解碼;P幀為差別幀,沒有完整的畫面數(shù)據(jù),只有與前一幀的畫面差別的數(shù)據(jù),無法獨立進行解碼,解碼需要依賴前一幀數(shù)據(jù)。
由于AndroidBDFresco的內(nèi)存緩存為LRU替換,Bitmap隨時有可能被回收,因此針對Heif動圖的解碼,必須嚴(yán)格按照動圖順序進行解碼,否則會導(dǎo)致Heif動圖播放過程中出現(xiàn)花屏綠屏等問題。
方案思考:
- 從源頭解決,優(yōu)化heif動圖的編碼解碼邏輯,但目前Heif的幀結(jié)構(gòu)就決定了解碼器的解碼邏輯,如果需要支持指定幀解碼,就得改造Heif編碼格式,方案不可行。
- 不在主線程進行解碼,專門開一個子線程做heif動圖的解碼,主線程需要渲染某一幀的時候,就切到子線程去解碼,解碼完成通知主線程做渲染,但方案對BDFresco的解碼流程改造較大,且不支持內(nèi)存緩存,方案待定。
- 抖音Android&iOS雙端共用一個解碼器,但iOS實驗并無幀率劣化,原因在于iOS的圖片內(nèi)存緩存是可控的,不會有不符合預(yù)期的緩存釋放,因此Android端可以嘗試借鑒該思路
- 給heif動圖單獨開辟一個新的內(nèi)存緩存塊,且對解碼后的Bitmap進行強引用,即不會被動釋放內(nèi)容,也不會被其他圖片LRU替換。方案優(yōu)點在于能夠完美復(fù)用老的解碼邏輯,也支持子線程預(yù)解碼,只需要將Bitmap單獨緩存即可實現(xiàn)。
- 由于Bitmap是強引用,緩存塊也無上限,方案存在內(nèi)存無限增長的可能,因此需要有一個主動釋放時機,即能減少內(nèi)存占用,也能保證解碼順序不被影響。因此我們嘗試關(guān)聯(lián)view的detach方法,當(dāng)動圖控件在快速滑動時,會主動釋放不可見View上對應(yīng)的Bitmap。
經(jīng)過實驗,最終采取了獨立緩存方案,在取得帶寬收益的同時,個人頁幀率無明顯劣化。
4.2 按需縮放
背景
圖片加載流程最終會將解碼后的bitmap渲染在控件上,當(dāng)bitmap大小大于控件時,實際對用戶感官并無影響,圖片最終展示的像素值不會超過控件占據(jù)的空間,當(dāng)圖片大小 >> 控件大小時:
- 造成一定程度的帶寬浪費;
- 圖片過大,客戶端性能損耗嚴(yán)重;
- 不同業(yè)務(wù)對同一張圖片進行圖片裁剪,沒有考慮圖片尺寸碎片化問題,導(dǎo)致veImageX-CDN緩存命中率顯著下降,最終造成回源成本的暴漲。
解決方案
在圖片展示時上報對應(yīng)的bitmap和控件大小,從上報的數(shù)據(jù)來看,存在大量業(yè)務(wù)請求的圖片大小遠大于控件。因此,需要采用一種通用的方案,在滿足圖片質(zhì)量的前提下,客戶端提供一套控件規(guī)范,根據(jù)控件大小將圖片收斂至固定大小,保證圖片尺寸和展示控件基本一致,同時減少圖片碎片化問題。
個人頁、同城、推薦等多個業(yè)務(wù)均存在雙列封面場景,這里以雙列封面為例子:
收益
- 視覺搜索場景文件大小 -83.39%,內(nèi)存大小 -66.57%
- veImageX-CDN緩存命中率提升 + 6.99%,回源請求數(shù)減少 -23.79%
5 異常恢復(fù)
盡管前面我們對圖片的加載流程做了一系列優(yōu)化,但因為抖音本身圖片量級大,部分業(yè)務(wù)如電商、IM等對圖片清晰度有較高的要求,且存在圖片放大和長圖展示等操作,業(yè)務(wù)會進行超大圖加載,直接將圖片直接加載進內(nèi)存,單張圖片內(nèi)存甚至高達100M+,無論在磁盤IO階段,還是內(nèi)存解碼或者Bitmap拷貝過程中均會申請大量內(nèi)存,最終導(dǎo)致卡頓、ANR甚至OOM崩潰,因此需要一套兜底方案來解決圖片OOM頻發(fā)問題,提升圖片加載的可靠性。
抖音在系統(tǒng)內(nèi)存觸頂時,會通過釋放圖片內(nèi)存來緩解壓力:監(jiān)聽系統(tǒng)內(nèi)存的告警回調(diào),根據(jù)不同級別釋放不同大小的圖片內(nèi)存緩存,降低發(fā)生OOM和ANR的幾率,但因大圖存在,仍然存在大量OOM。
OOM兜底
內(nèi)存是一個全局指標(biāo),并不能直接通過OOM堆棧確定異常原因,因為OOM發(fā)生的時候內(nèi)存可能處于高水位狀態(tài),有可能申請了一個小對象就直接觸發(fā)異常。但關(guān)注到崩潰中Top5的堆棧大部分和圖片堆棧有關(guān)系,可以合理懷疑是App內(nèi)圖片頻繁申請大內(nèi)存導(dǎo)致。
因此針對高頻的圖片解碼和內(nèi)存拷貝邏輯,增加兜底邏輯,當(dāng)代碼發(fā)生OOM,主動catch,并通過清除圖片占用的內(nèi)存緩存來釋放部分內(nèi)存,降低內(nèi)存水位:
- 清除兩級內(nèi)存緩存,解碼內(nèi)存緩存+未解碼內(nèi)存緩存
- 清除接入層緩存的動圖預(yù)覽幀
實驗結(jié)果表明,盡管部分OOM轉(zhuǎn)換成native崩潰,但整體影響用戶大幅下降,實驗符合預(yù)期。
總結(jié)
總體來看,抖音在建設(shè)了圖片的全鏈路監(jiān)控后,根據(jù)數(shù)據(jù)分析對圖片加載流程做了不少優(yōu)化。
- 提升了圖片加載速度和性能
- 減少了圖片的總成本
從收益角度來看,大致可以分為成本優(yōu)化和客戶端體驗優(yōu)化兩方面。成本收益主要是圖片帶寬成本的降低,體驗收益體現(xiàn)在日活和OOM指標(biāo)上,并且隨著各種優(yōu)化方案推廣到更多的業(yè)務(wù)線,收益也在持續(xù)增加。
本文簡要介紹了抖音基于BDFresco的圖片優(yōu)化最佳實踐、經(jīng)驗沉淀、業(yè)務(wù)收益。由于篇幅所限,本文對探索歷程、具體實現(xiàn)等細(xì)節(jié)內(nèi)容有所省略,但仍希望能給業(yè)內(nèi)同仁們一點啟發(fā)或者參考借鑒。目前BDFresco已集成到火山引擎veImageX產(chǎn)品,對行業(yè)開放使用中,如需體驗抖音同款圖片優(yōu)化能力,可以到火山引擎veImageX官網(wǎng)申請使用。
參考:火山引擎veImageX提供端到端一站式的整體圖片解決方案,包含圖片及素材托管、圖像處理與壓縮、分發(fā)、客戶端編解碼及圖片加載SDK全鏈路能力,官網(wǎng)地址:https://www.volcengine.com/product/imagex