得物App白屏優(yōu)化系列|歸因篇
一、前言
本系列前面兩篇文章已經(jīng)分別在圖片庫和網(wǎng)絡(luò)庫的角度介紹了諸多白屏問題的定位和解決方案,但都是相對獨(dú)立的問題,并且像OSCP,CDN節(jié)點(diǎn)異常之類的第三方問題無法徹底根治,因此為了長治白屏并發(fā)掘更多問題,就需要一套相對完善的白屏檢測+問題歸因體系。
本文將介紹從用戶視角出發(fā)的白屏檢測方案以及線上白屏問題的大致歸因思路。
二、白屏歸因平臺概覽
圖片
三、客戶端
檢測思路
直接將白屏檢測寫到圖片庫里似乎是比較合適的方案,但是基礎(chǔ)庫的改動也可能出bug導(dǎo)致圖片加載失敗,例如圖片請求的url被某個bug置空,這樣展示的效果就是接口正常但是圖片全都展示占位圖,如果寫在圖片庫里將無法發(fā)現(xiàn)該問題。
因此為了規(guī)避類似的事件發(fā)生,我們需要一個完全使用系統(tǒng)API來實(shí)現(xiàn)的白屏檢測方案,站在用戶視角來判斷當(dāng)前是否發(fā)生了白屏,而圖片庫和網(wǎng)絡(luò)庫提供的信息僅作為前置判斷和現(xiàn)場日志。
整體流程圖
圖片
屏上圖片獲取
既然是站在用戶視角,那么我們就只需要檢測屏上的ImageView即可,根據(jù)線上用戶反饋的信息,白屏問題主要集中在動態(tài)流和商品流這類存在大量圖片加載的recyclerView中,那么根據(jù)recyclerView的特性,我們最終是采用了View類的onAttachedToWindow和onDetachedFromWindow回調(diào)來作為對單張圖片檢測的開始和結(jié)束。
這樣可以做到和用戶視角完全一致,即在圖片可見時開始檢測,完全不可見時停止檢測。此外圖片庫的上屏請求發(fā)起時機(jī)也是同樣的方法,這樣后續(xù)獲取現(xiàn)場信息也無需再做時間對齊。
圖片
像素抽樣檢測
為了檢測ImageView的展示內(nèi)容是否正常,我們需要獲取到其真實(shí)展示的Bitmap。為了減少對內(nèi)存的占用,我們通過draw方法將屏上的ImageView繪制到指定的50*50的Bitmap中,這里注意必須要使用draw來獲取Bitmap,不可以從圖片庫直接獲取,因?yàn)橹骶€程卡頓也有可能造成白屏,如果從圖片庫獲取就會掩蓋這一問題。
由于此處是異步執(zhí)行ImageView的draw方法,并且我們持有的其實(shí)是ImageView的弱引用,因此需要在draw之前判斷下其內(nèi)部的bitmap是否已經(jīng)被回收,如果是圓角圖還需要判斷下path對象是否已經(jīng)被回收,防止出現(xiàn)訪問已經(jīng)回收的native對象的崩潰。
圖片
圖片
圖片
之后從該Bitmap中居中均勻的取出N*N個像素點(diǎn)(這里以3*3為例),按其色值進(jìn)行統(tǒng)計,找出占比最高的色值的比例,如果其占比超過一定閾值,說明他是白圖(不一定是白色,因?yàn)檎嘉粓D背景是淡灰色)。
性能優(yōu)化
白屏問題作為僅次于crash和ANR的穩(wěn)定性問題,線上將會全量開啟功能,因此對檢測性能要求比較嚴(yán)格。
盡管像素抽樣檢測能夠在一定程度上降低內(nèi)存使用,但是在異步現(xiàn)場頻繁調(diào)用view的draw方法還是會有性能損耗,如果恰好檢測的同時主線程在繪制某一幀,對幀繪制較慢的低端機(jī)而言勢必會影響體驗(yàn),因此需要盡可能降低像素抽樣檢測頻次。
離屏檢測
onAttach和onDetach僅適用于recyclerview,對于其他的布局在圖片上屏/離屏?xí)r未必會觸發(fā)這兩個回調(diào),因此需要做些適配。
頁面可見檢測
目前大多數(shù)App首頁的設(shè)計都是底部導(dǎo)航欄+多Fragment的組合,而在tab之間切換時并不會觸發(fā)View的attach和detach,但是切換后前一個頁面中view已經(jīng)不在屏上。
因此需要額外注冊Activity和Fragment的生命周期的監(jiān)聽,并記錄所有ImageView歸屬的頁面,在onResume時,將當(dāng)前Activity/Fragment標(biāo)記為可檢測狀態(tài),同時在onPause(fragment而言是onPaused)標(biāo)記為不可檢測狀態(tài),這樣即可解決Fragment切換這類場景的檢測,同時還兼顧了App切后臺之后的暫停檢測處理。
view可見性檢測
與此相似的還有View的Visibility問題,例如viewPager實(shí)現(xiàn)的輪播圖,在圖片輪播切換時,只是把圖片設(shè)置為INVISIBLE,并不會執(zhí)行onDetach,且ImageView可能是被包含在自定義的布局中,因此在檢測之前需要從當(dāng)前ImageView向上遍歷其父View直到View樹根節(jié)點(diǎn),如果途中有INVISIBLE或者GONE狀態(tài)的View則無需檢測。
圖片庫&網(wǎng)絡(luò)庫預(yù)檢
圖片白屏最常見就是弱網(wǎng)或者IO阻塞這類網(wǎng)絡(luò)/圖片庫問題,因此在做像素抽檢之前需要通過圖片庫,網(wǎng)絡(luò)庫查詢到該圖片對應(yīng)的請求進(jìn)度,如果加載異常或者耗時異常則無需檢測直接判定為白圖,同時獲取這些基礎(chǔ)庫中關(guān)鍵的現(xiàn)場快照信息跟隨白屏日志上傳即可。如果二者均表示加載正常則再做像素抽樣檢測。
單張圖片檢測流程示意圖:
圖片
頻次控制
用戶正常使用過程中,屏上圖片的變更較為頻繁,因此需要將檢測周期限制為3s一次,并且經(jīng)檢測確認(rèn)正常或白屏的圖片不再參與檢測。
現(xiàn)場日志
白屏檢測的方案只是發(fā)現(xiàn)問題,重點(diǎn)在于如何獲取充足的現(xiàn)場信息提供給歸因平臺。
圖片網(wǎng)絡(luò)請求信息
網(wǎng)絡(luò)請求階段信息通常是重寫okhttp的eventListener抽象類來獲取到各個階段的執(zhí)行回調(diào),但是常規(guī)的方案一般只關(guān)注各個階段的耗時和基礎(chǔ)信息,但是針對白屏問題,我們需要額外關(guān)注connectFailed,requestFailed和responseFailed這三個特殊的回調(diào),因?yàn)樗麄儗?yīng)的TCP建連,request構(gòu)建,response傳輸階段都是會因?yàn)槭《卦嚨模虼诵枰涗浵旅恳淮沃卦嚨脑敿?xì)信息。
例如下圖中記錄的就是TCP建連階段對不同ip的多次嘗試,如果不單獨(dú)記錄的話將只有一條建連記錄:
圖片
流量監(jiān)控
方案有兩套,分別是系統(tǒng)API TrafficStats.getUidRxBytes來獲取和通過NetworkStatsManager.querySummary獲取,兩者各有優(yōu)劣:
- 前者能確保在弱網(wǎng)環(huán)境下哪怕非常小的流量消耗都能記錄,但是它會包含本地socket通信的流量,如果App中使用PCDN之類的SDK則會對數(shù)據(jù)造成干擾。
- 后者不會包含localSocket的通信流量,但是系統(tǒng)為了優(yōu)化性能對記錄流量的bucket文件寫入頻率做了限制,在流量消耗非常低的情況下可能獲取不到最新的數(shù)據(jù)。
我們需要將兩套方案結(jié)合起來看,但是實(shí)際歸因時為了確保準(zhǔn)確性還采用后者來計算App網(wǎng)速,這樣會漏掉一小部分弱網(wǎng)日志,但是不會誤判。
圖片庫階段信息
圖片庫采用的是Facebook的Fresco開源庫,具體的圖片庫階段已經(jīng)在系列一圖片庫中有過介紹。核心日志主要以ProducerContext的hashCode作為唯一鍵值,串聯(lián)起圖片庫的Producer信息、Request信息、Submit信息。
- Producer信息中記錄圖片經(jīng)歷所有任務(wù)處理階段: 線程切換、內(nèi)存緩存、磁盤緩存、網(wǎng)絡(luò)請求、編解碼等。
- Request信息中記錄了圖片開始請求、取消、失敗的核心時間節(jié)點(diǎn)。
- Submit信息中記錄了圖片上屏、完成加載、離開屏幕等核心時間節(jié)點(diǎn)。
針對每個Producer我們也補(bǔ)充了自定義的字段屬性,如:
- 網(wǎng)絡(luò)隊(duì)列、解碼隊(duì)列信息。
- 圖片元信息、業(yè)務(wù)調(diào)用標(biāo)簽。
- 動圖幀耗時、幀數(shù)、單幀大小等。
基礎(chǔ)庫隊(duì)列信息
圖片庫,網(wǎng)絡(luò)庫的各個關(guān)鍵隊(duì)列的狀態(tài),包含:
- 業(yè)務(wù)接口請求中隊(duì)列
- 業(yè)務(wù)接口等待隊(duì)列
- 圖片庫高優(yōu)隊(duì)列(即屏上圖片請求隊(duì)列)
- 圖片庫低優(yōu)隊(duì)列(即離屏/預(yù)加載請求隊(duì)列)
- 圖片網(wǎng)絡(luò)請求中隊(duì)列
- 圖片網(wǎng)絡(luò)請求等待隊(duì)列
結(jié)合隊(duì)列狀況可以分析出一些隊(duì)列阻塞問題,例如本系列在圖片庫篇提到過的圖片庫請求隊(duì)列被某個異常的CDN請求打滿導(dǎo)致另一個CDN的請求無法發(fā)起的問題。
最近N分鐘的CDN異常記錄
針對圖片請求使用的幾個CDN域名,以及App主站業(yè)務(wù)接口的域名,分別對成功,失敗,慢請求的數(shù)量和異常信息單獨(dú)記錄,考慮到內(nèi)存占用可以改成只記錄最近1分鐘的請求信息。
火焰圖
主線程卡頓/慢消息同樣會導(dǎo)致白屏問題,多發(fā)生在冷啟動的首頁首幀繪制時,此類問題與圖片庫/網(wǎng)絡(luò)庫無關(guān),因此針對圖片庫網(wǎng)絡(luò)庫均正常的情況下,如果像素抽樣檢測結(jié)果顯示為白屏,則需要在日志中額外添加最近20s的火焰圖日志。
現(xiàn)場快照
除去基礎(chǔ)庫的現(xiàn)場快照信息之外,還需要一個直截了當(dāng)?shù)淖C據(jù)表明確實(shí)發(fā)生了白屏,因此在判斷出白屏并準(zhǔn)備上報日志時需要獲取當(dāng)前App頁面內(nèi)容(僅限首頁),這樣可以直觀的看出是否有白屏發(fā)生。我們采用的是系統(tǒng)提供的PixelCopy類,可以獲取當(dāng)前頁面最近一幀的Bitmap,系統(tǒng)在native層做了異步處理,最終會通過入?yún)⒌膆andler返回獲取結(jié)果,因此無需考慮多線程問題。
圖片
圖片
其底層實(shí)現(xiàn)是獲取當(dāng)前window最近一幀的繪制緩存,可以縮放到入?yún)⒅兄付ǖ腂itmap,因此無需擔(dān)心內(nèi)存占用和性能損耗問題,但是會存在一定幾率獲取失敗,做好防護(hù)即可。
并且其內(nèi)部實(shí)現(xiàn)了超時機(jī)制,超過500ms后會回調(diào)異常碼,但是由于在子線程執(zhí)行,時間片調(diào)度未必及時,實(shí)測經(jīng)常會超過500ms,因此需要自行計算耗時,如果超過一定閾值(例如2s),說明設(shè)備此時性能不佳,應(yīng)當(dāng)關(guān)閉白屏檢測或者暫停一段時間,避免進(jìn)一步影響用戶體驗(yàn)。
圖片
圖片
由于首頁頁面大小和設(shè)備相近,因此需要采取一定比例進(jìn)行壓縮,能夠勉強(qiáng)看清文案即可,這里建議使用270*480,符合大部分移動設(shè)備的屏幕比例:
圖片
四、平臺
歸因思路
白屏這種綜合性問題絕大部分都是環(huán)境異常導(dǎo)致,因此在歸因優(yōu)先級上更傾向于環(huán)境類問題,但是像弱網(wǎng)這類環(huán)境問題想要精準(zhǔn)歸因勢必要劃分一個相對嚴(yán)格的閾值,這就會存在有些和閾值非常相近的弱網(wǎng)問題沒有被歸為弱網(wǎng),那么這類問題就只能按照耗時異常的階段來歸因,例如DNS長耗時,TCP建連長耗時等。
如果一個日志不符合任意一種環(huán)境問題,那么就需要對白屏中的所有圖片單獨(dú)做歸因,最后再取占比最高的問題類型作為整體的白屏歸因。
歸因策略
特殊異常問題
OCSP問題(網(wǎng)絡(luò)篇有介紹),解碼異常,證書校驗(yàn)異常
此類問題都伴有特殊的基礎(chǔ)庫異常,可以直接歸因,不像CDN節(jié)點(diǎn)異常和弱網(wǎng)之間存在著重疊部分,還需要現(xiàn)場信息佐證。
案例:
下圖中圖片庫拋出了OCSP問題特有的異常信息:javax.net.ssl.SSLHandshakeException: Unacceptable certificate,則可直接被判斷為OCSP問題。
圖片
環(huán)境問題
弱網(wǎng)/無網(wǎng)-流量監(jiān)控
由于白屏問題的滯后性,導(dǎo)致白屏的故障往往是發(fā)生在十幾秒之前(例如進(jìn)電梯弱網(wǎng)),因此網(wǎng)絡(luò)診斷這類檢測到白屏之后的后續(xù)檢測的結(jié)果僅能作為參考信息,不能作為弱網(wǎng)的直接證據(jù)。
因此我們通過流量的消耗來計算App網(wǎng)速,通過在內(nèi)存中維護(hù)一個記錄最近3s,6s,9s的流量消耗的隊(duì)列,可以算出最近App的網(wǎng)絡(luò)下行速率。同時判定弱網(wǎng)的最低閾值,可以線下用charles限速來找出能夠滿足App加載頁面的最小帶寬。
采用三個3s階段中平均速率的最大值40KB/s作為下行速率。
圖片
我們區(qū)分弱網(wǎng)和無網(wǎng)并不是依據(jù)是否有網(wǎng)速,而是最近3分鐘的業(yè)務(wù)接口是否有成功記錄,如果無一成功則判定無網(wǎng),否則判定弱網(wǎng)。
案例:
該用戶的下行速率低于我們設(shè)置的最低閾值30KB/s,并且業(yè)務(wù)接口能正常請求,圖片CDN請求卻都失敗,因此判定為弱網(wǎng)導(dǎo)致白屏。
圖片
CDN節(jié)點(diǎn)不通 -CDN異常記錄
CDN單節(jié)點(diǎn)不通出現(xiàn)概率較低,其具體表現(xiàn)為TCP建連超時,往往難以和常見的弱網(wǎng)/無網(wǎng)等問題區(qū)分開來,因此我們需要讓多個CDN廠商的請求橫向進(jìn)行對比。
通過客戶端提供的最近1分鐘內(nèi)CDN的異常記錄,橫向?qū)Ρ雀鱾€域名的狀態(tài),如果某個CDN域名全部是失敗或者慢請求,而其他域名均正常,則足以證明該CDN節(jié)點(diǎn)異常。
案例:
僅CDN A的45個請求均失敗,其他CDN和業(yè)務(wù)接口均正常,則可判定為CDN節(jié)點(diǎn)異常。
主線程慢消息 - 火焰圖
在白屏檢測上線后我們發(fā)現(xiàn)了一些特殊的白屏問題,即在圖片庫已經(jīng)調(diào)用了ImageView.setBitmap之后過了數(shù)秒之后App仍舊處于白屏狀態(tài),因此推測是主線程卡頓導(dǎo)致的幀繪制延遲,補(bǔ)充火焰圖之后再進(jìn)行排查,定位并治理了數(shù)個主線程耗時消息導(dǎo)致的白屏。
案例:
主線程連續(xù)讀取磁盤緩存導(dǎo)致的卡頓。
圖片
圖片解碼線程池阻塞 - 圖片庫階段信息
由于圖片庫解碼存在一些性能問題,部分圖片解碼較慢,最終會導(dǎo)致圖片庫的解碼線程池阻塞。此類問題記錄下解碼線程池等待隊(duì)列中任務(wù)數(shù)量即可,如果好過一定閾值并且解碼總耗時超時,則可判定為線程池阻塞問題。
案例:
解碼任務(wù)等待超過10s未開始執(zhí)行,并且線程池等待隊(duì)列中請求超過30個,足以證明解碼線程池已經(jīng)因之前某些解碼慢的任務(wù)堵死。
共性問題
- 圖片庫磁盤緩存讀寫慢
- 圖片庫磁盤緩存鎖耗時
以上都是針對單張圖片白圖診斷出來的問題類型,白屏問題都包含多張白圖,因此可以采用在這些白圖中占比最高的問題類型作為白屏的歸因。
清洗臟數(shù)據(jù)
前文提到的像素抽樣檢測方案,我們線上使用的是10*10的采樣,到這個數(shù)量已經(jīng)可以準(zhǔn)確的識別出占位圖和正常圖,但是部分細(xì)長商品的主圖空白部分較多,很容易被誤判為占位圖,具體表現(xiàn)為圖片請求正常,現(xiàn)場快照也正常,但是上報白屏。
因此需要在服務(wù)端做二次check,即下載這張圖的原圖并按100%采樣做同樣的統(tǒng)計,如果得到的比例數(shù)據(jù)和客戶端檢測結(jié)果相近則標(biāo)記為臟數(shù)據(jù)。
左圖為占位圖,右圖為刀劍模型商品圖。
圖片
圖片
案例:
臟數(shù)據(jù)這個問題分類的日志量有段時間上漲許多,經(jīng)排查發(fā)現(xiàn)多為首飾類的商品,其商品圖同樣有這大部分空白,確實(shí)屬于臟數(shù)據(jù)。
而當(dāng)時恰逢有首飾相關(guān)推廣,App內(nèi)項(xiàng)鏈?zhǔn)罪椷@個類目的tab提到了靠前的位置,這一類的圖片曝光都提升了很多從而引起指標(biāo)上漲,且隨著推廣到期結(jié)束之后該問題日志量又回落到正常水平。這也證明了該策略對臟數(shù)據(jù)歸因的準(zhǔn)確性。
圖片
歸因優(yōu)先級
我們目前問題歸因的優(yōu)先級從高到低如下,主要按歸因證據(jù)的可信度來排序。
問題治理
以下是可以優(yōu)先推進(jìn)治理的問題類型:
- CDN單點(diǎn)問題
可批量導(dǎo)出異常節(jié)點(diǎn)的IP地址后聯(lián)系CDN廠商排查。
- 主線程慢消息導(dǎo)致白屏
大部分都是主線程任務(wù)阻塞導(dǎo)致幀繪制的消息沒有及時執(zhí)行,和卡頓檢測的日志比較重合,可以借助火焰圖和主線程消息隊(duì)列日志來分析排查問題。
問題分析工具
分析單個白屏日志的工具:
- 網(wǎng)絡(luò)庫和圖片庫的現(xiàn)場信息
便于定位圖片加載耗時集中在哪些階段
圖片
圖片
- 白屏現(xiàn)場頁面內(nèi)容
圖片
問題比例
下圖是目前得物App線上白屏問題的分布,問題分配的比例在99.7%以上。且大頭還是在網(wǎng)絡(luò)和磁盤緩存阻塞等設(shè)備問題上,CDN問題僅占1.24%,整體處于相對穩(wěn)定的狀態(tài),后續(xù)網(wǎng)絡(luò)資源調(diào)度和圖片庫磁盤鎖優(yōu)化落地之后將得到明顯改善。
圖片
五、總結(jié)
白屏作為長鏈路綜合性問題,任意環(huán)節(jié)出問題都會引發(fā)最終的白屏,尤其是客觀因素較多的網(wǎng)絡(luò)側(cè)。通過白屏歸因平臺可以對線上問題分而治之,對弱網(wǎng),CDN節(jié)點(diǎn)異常這類無法根治的問題可以通過配置告警來持續(xù)關(guān)注防劣化,對圖片解碼超時,主線程卡頓這類可以專項(xiàng)進(jìn)行治理優(yōu)化,對線上反饋的單用戶白屏則可以通過診斷工具快速定位到根因。