動態

詳情 返回 返回

得物App白屏優化系列|歸因篇 - 動態 詳情

一、前言

本系列前面兩篇文章已經分別在圖片庫和網絡庫的角度介紹了諸多白屏問題的定位和解決方案,但都是相對獨立的問題,並且像OSCP,CDN節點異常之類的第三方問題無法徹底根治,因此為了長治白屏併發掘更多問題,就需要一套相對完善的白屏檢測+問題歸因體系。

本文將介紹從用户視角出發的白屏檢測方案以及線上白屏問題的大致歸因思路。

二、白屏歸因平台概覽

01.jpg

三、客户端

檢測思路

直接將白屏檢測寫到圖片庫裏似乎是比較合適的方案,但是基礎庫的改動也可能出bug導致圖片加載失敗,例如圖片請求的url被某個bug置空,這樣展示的效果就是接口正常但是圖片全都展示佔位圖,如果寫在圖片庫裏將無法發現該問題。

因此為了規避類似的事件發生,我們需要一個完全使用系統API來實現的白屏檢測方案,站在用户視角來判斷當前是否發生了白屏,而圖片庫和網絡庫提供的信息僅作為前置判斷和現場日誌。

整體流程圖

02.jpg

屏上圖片獲取

既然是站在用户視角,那麼我們就只需要檢測屏上的ImageView即可,根據線上用户反饋的信息,白屏問題主要集中在動態流和商品流這類存在大量圖片加載的recyclerView中,那麼根據recyclerView的特性,我們最終是採用了View類的onAttachedToWindow和onDetachedFromWindow回調來作為對單張圖片檢測的開始和結束。

這樣可以做到和用户視角完全一致,即在圖片可見時開始檢測,完全不可見時停止檢測。此外圖片庫的上屏請求發起時機也是同樣的方法,這樣後續獲取現場信息也無需再做時間對齊。

03.jpg

像素抽樣檢測

為了檢測ImageView的展示內容是否正常,我們需要獲取到其真實展示的Bitmap。為了減少對內存的佔用,我們通過draw方法將屏上的ImageView繪製到指定的50*50的Bitmap中,這裏注意必須要使用draw來獲取Bitmap,不可以從圖片庫直接獲取,因為主線程卡頓也有可能造成白屏,如果從圖片庫獲取就會掩蓋這一問題。

由於此處是異步執行ImageView的draw方法,並且我們持有的其實是ImageView的弱引用,因此需要在draw之前判斷下其內部的bitmap是否已經被回收,如果是圓角圖還需要判斷下path對象是否已經被回收,防止出現訪問已經回收的native對象的崩潰。

04.jpg

05.jpg

06.jpg

之後從該Bitmap中居中均勻的取出NN個像素點(這裏以33為例),按其色值進行統計,找出佔比最高的色值的比例,如果其佔比超過一定閾值,説明他是白圖(不一定是白色,因為佔位圖背景是淡灰色)。

性能優化

白屏問題作為僅次於crash和ANR的穩定性問題,線上將會全量開啓功能,因此對檢測性能要求比較嚴格。

儘管像素抽樣檢測能夠在一定程度上降低內存使用,但是在異步現場頻繁調用view的draw方法還是會有性能損耗,如果恰好檢測的同時主線程在繪製某一幀,對幀繪製較慢的低端機而言勢必會影響體驗,因此需要儘可能降低像素抽樣檢測頻次。

離屏檢測

onAttach和onDetach僅適用於recyclerview,對於其他的佈局在圖片上屏/離屏時未必會觸發這兩個回調,因此需要做些適配。

頁面可見檢測

目前大多數App首頁的設計都是底部導航欄+多Fragment的組合,而在tab之間切換時並不會觸發View的attach和detach,但是切換後前一個頁面中view已經不在屏上。

因此需要額外註冊Activity和Fragment的生命週期的監聽,並記錄所有ImageView歸屬的頁面,在onResume時,將當前Activity/Fragment標記為可檢測狀態,同時在onPause(fragment而言是onPaused)標記為不可檢測狀態,這樣即可解決Fragment切換這類場景的檢測,同時還兼顧了App切後台之後的暫停檢測處理。

view可見性檢測

與此相似的還有View的Visibility問題,例如viewPager實現的輪播圖,在圖片輪播切換時,只是把圖片設置為INVISIBLE,並不會執行onDetach,且ImageView可能是被包含在自定義的佈局中,因此在檢測之前需要從當前ImageView向上遍歷其父View直到View樹根節點,如果途中有INVISIBLE或者GONE狀態的View則無需檢測。

圖片庫&網絡庫預檢

圖片白屏最常見就是弱網或者IO阻塞這類網絡/圖片庫問題,因此在做像素抽檢之前需要通過圖片庫,網絡庫查詢到該圖片對應的請求進度,如果加載異常或者耗時異常則無需檢測直接判定為白圖,同時獲取這些基礎庫中關鍵的現場快照信息跟隨白屏日誌上傳即可。如果二者均表示加載正常則再做像素抽樣檢測。

單張圖片檢測流程示意圖:

07.jpg

頻次控制

用户正常使用過程中,屏上圖片的變更較為頻繁,因此需要將檢測週期限制為3s一次,並且經檢測確認正常或白屏的圖片不再參與檢測。

現場日誌

白屏檢測的方案只是發現問題,重點在於如何獲取充足的現場信息提供給歸因平台。

圖片網絡請求信息

網絡請求階段信息通常是重寫okhttp的eventListener抽象類來獲取到各個階段的執行回調,但是常規的方案一般只關注各個階段的耗時和基礎信息,但是針對白屏問題,我們需要額外關注connectFailed,requestFailed和responseFailed這三個特殊的回調,因為他們對應的TCP建連,request構建,response傳輸階段都是會因為失敗而重試的,因此需要記錄下每一次重試的詳細信息。

例如下圖中記錄的就是TCP建連階段對不同ip的多次嘗試,如果不單獨記錄的話將只有一條建連記錄:

08.jpg

流量監控

方案有兩套,分別是系統API TrafficStats.getUidRxBytes來獲取和通過NetworkStatsManager.querySummary獲取,兩者各有優劣:

  • 前者能確保在弱網環境下哪怕非常小的流量消耗都能記錄,但是它會包含本地socket通信的流量,如果App中使用PCDN之類的SDK則會對數據造成干擾。
  • 後者不會包含localSocket的通信流量,但是系統為了優化性能對記錄流量的bucket文件寫入頻率做了限制,在流量消耗非常低的情況下可能獲取不到最新的數據。

我們需要將兩套方案結合起來看,但是實際歸因時為了確保準確性還採用後者來計算App網速,這樣會漏掉一小部分弱網日誌,但是不會誤判。

圖片庫階段信息

圖片庫採用的是Facebook的Fresco開源庫,具體的圖片庫階段已經在系列一圖片庫中有過介紹。核心日誌主要以ProducerContext的hashCode作為唯一鍵值,串聯起圖片庫的Producer信息、Request信息、Submit信息。

  • Producer信息中記錄圖片經歷所有任務處理階段: 線程切換、內存緩存、磁盤緩存、網絡請求、編解碼等。
  • Request信息中記錄了圖片開始請求、取消、失敗的核心時間節點。
  • Submit信息中記錄了圖片上屏、完成加載、離開屏幕等核心時間節點。

針對每個Producer我們也補充了自定義的字段屬性,如:

  • 網絡隊列、解碼隊列信息。
  • 圖片元信息、業務調用標籤。
  • 動圖幀耗時、幀數、單幀大小等。

基礎庫隊列信息

圖片庫,網絡庫的各個關鍵隊列的狀態,包含:

  • 業務接口請求中隊列
  • 業務接口等待隊列
  • 圖片庫高優隊列(即屏上圖片請求隊列)
  • 圖片庫低優隊列(即離屏/預加載請求隊列)
  • 圖片網絡請求中隊列
  • 圖片網絡請求等待隊列

結合隊列狀況可以分析出一些隊列阻塞問題,例如本系列在圖片庫篇提到過的圖片庫請求隊列被某個異常的CDN請求打滿導致另一個CDN的請求無法發起的問題。

最近N分鐘的CDN異常記錄

針對圖片請求使用的幾個CDN域名,以及App主站業務接口的域名,分別對成功,失敗,慢請求的數量和異常信息單獨記錄,考慮到內存佔用可以改成只記錄最近1分鐘的請求信息。

火焰圖

主線程卡頓/慢消息同樣會導致白屏問題,多發生在冷啓動的首頁首幀繪製時,此類問題與圖片庫/網絡庫無關,因此針對圖片庫網絡庫均正常的情況下,如果像素抽樣檢測結果顯示為白屏,則需要在日誌中額外添加最近20s的火焰圖日誌。

現場快照

除去基礎庫的現場快照信息之外,還需要一個直截了當的證據表明確實發生了白屏,因此在判斷出白屏並準備上報日誌時需要獲取當前App頁面內容(僅限首頁),這樣可以直觀的看出是否有白屏發生。我們採用的是系統提供的PixelCopy類,可以獲取當前頁面最近一幀的Bitmap,系統在native層做了異步處理,最終會通過入參的handler返回獲取結果,因此無需考慮多線程問題。

09.jpg

10.jpg

其底層實現是獲取當前window最近一幀的繪製緩存,可以縮放到入參中指定的Bitmap,因此無需擔心內存佔用和性能損耗問題,但是會存在一定機率獲取失敗,做好防護即可。

並且其內部實現了超時機制,超過500ms後會回調異常碼,但是由於在子線程執行,時間片調度未必及時,實測經常會超過500ms,因此需要自行計算耗時,如果超過一定閾值(例如2s),説明設備此時性能不佳,應當關閉白屏檢測或者暫停一段時間,避免進一步影響用户體驗。

11.jpg

12.jpg

由於首頁頁面大小和設備相近,因此需要採取一定比例進行壓縮,能夠勉強看清文案即可,這裏建議使用270*480,符合大部分移動設備的屏幕比例:

13.jpg

四、平台

歸因思路

白屏這種綜合性問題絕大部分都是環境異常導致,因此在歸因優先級上更傾向於環境類問題,但是像弱網這類環境問題想要精準歸因勢必要劃分一個相對嚴格的閾值,這就會存在有些和閾值非常相近的弱網問題沒有被歸為弱網,那麼這類問題就只能按照耗時異常的階段來歸因,例如DNS長耗時,TCP建連長耗時等。

如果一個日誌不符合任意一種環境問題,那麼就需要對白屏中的所有圖片單獨做歸因,最後再取佔比最高的問題類型作為整體的白屏歸因。

歸因策略

特殊異常問題

++OCSP問題(網絡篇有介紹),解碼異常,證書校驗異常++

此類問題都伴有特殊的基礎庫異常,可以直接歸因,不像CDN節點異常和弱網之間存在着重疊部分,還需要現場信息佐證。

案例

下圖中圖片庫拋出了OCSP問題特有的異常信息:javax.net.ssl.SSLHandshakeException: Unacceptable certificate,則可直接被判斷為OCSP問題。

14.jpg

環境問題

++弱網/無網-流量監控++

由於白屏問題的滯後性,導致白屏的故障往往是發生在十幾秒之前(例如進電梯弱網),因此網絡診斷這類檢測到白屏之後的後續檢測的結果僅能作為參考信息,不能作為弱網的直接證據。

因此我們通過流量的消耗來計算App網速,通過在內存中維護一個記錄最近3s,6s,9s的流量消耗的隊列,可以算出最近App的網絡下行速率。同時判定弱網的最低閾值,可以線下用charles限速來找出能夠滿足App加載頁面的最小帶寬。

採用三個3s階段中平均速率的最大值40KB/s作為下行速率。

15.jpg

我們區分弱網和無網並不是依據是否有網速,而是最近3分鐘的業務接口是否有成功記錄,如果無一成功則判定無網,否則判定弱網。

案例

該用户的下行速率低於我們設置的最低閾值30KB/s,並且業務接口能正常請求,圖片CDN請求卻都失敗,因此判定為弱網導致白屏。

16.jpg

++CDN節點不通 -CDN異常記錄++

CDN單節點不通出現概率較低,其具體表現為TCP建連超時,往往難以和常見的弱網/無網等問題區分開來,因此我們需要讓多個CDN廠商的請求橫向進行對比。

通過客户端提供的最近1分鐘內CDN的異常記錄,橫向對比各個域名的狀態,如果某個CDN域名全部是失敗或者慢請求,而其他域名均正常,則足以證明該CDN節點異常。

案例

僅CDN A的45個請求均失敗,其他CDN和業務接口均正常,則可判定為CDN節點異常。

17.jpg

++主線程慢消息 - 火焰圖++

在白屏檢測上線後我們發現了一些特殊的白屏問題,即在圖片庫已經調用了ImageView.setBitmap之後過了數秒之後App仍舊處於白屏狀態,因此推測是主線程卡頓導致的幀繪製延遲,補充火焰圖之後再進行排查,定位並治理了數個主線程耗時消息導致的白屏。

案例

主線程連續讀取磁盤緩存導致的卡頓。

18.jpg

++圖片解碼線程池阻塞 - 圖片庫階段信息++

由於圖片庫解碼存在一些性能問題,部分圖片解碼較慢,最終會導致圖片庫的解碼線程池阻塞。此類問題記錄下解碼線程池等待隊列中任務數量即可,如果好過一定閾值並且解碼總耗時超時,則可判定為線程池阻塞問題。

案例

解碼任務等待超過10s未開始執行,並且線程池等待隊列中請求超過30個,足以證明解碼線程池已經因之前某些解碼慢的任務堵死。

19.jpg

共性問題

  • 圖片庫磁盤緩存讀寫慢
  • 圖片庫磁盤緩存鎖耗時

以上都是針對單張圖片白圖診斷出來的問題類型,白屏問題都包含多張白圖,因此可以採用在這些白圖中佔比最高的問題類型作為白屏的歸因。

清洗髒數據

前文提到的像素抽樣檢測方案,我們線上使用的是10*10的採樣,到這個數量已經可以準確的識別出佔位圖和正常圖,但是部分細長商品的主圖空白部分較多,很容易被誤判為佔位圖,具體表現為圖片請求正常,現場快照也正常,但是上報白屏。

因此需要在服務端做二次check,即下載這張圖的原圖並按100%採樣做同樣的統計,如果得到的比例數據和客户端檢測結果相近則標記為髒數據。

上方圖為佔位圖,下方圖為刀劍模型商品圖。

20.jpg

21.jpg

案例

髒數據這個問題分類的日誌量有段時間上漲許多,經排查發現多為首飾類的商品,其商品圖同樣有這大部分空白,確實屬於髒數據。

而當時恰逢有首飾相關推廣,App內項鍊首飾這個類目的tab提到了靠前的位置,這一類的圖片曝光都提升了很多從而引起指標上漲,且隨着推廣到期結束之後該問題日誌量又回落到正常水平。這也證明了該策略對髒數據歸因的準確性。

22.jpg

歸因優先級

我們目前問題歸因的優先級從高到低如下,主要按歸因證據的可信度來排序。

23.jpg

問題治理

以下是可以優先推進治理的問題類型:

  • CDN單點問題

    • 可批量導出異常節點的IP地址後聯繫CDN廠商排查。
  • 主線程慢消息導致白屏

    • 大部分都是主線程任務阻塞導致幀繪製的消息沒有及時執行,和卡頓檢測的日誌比較重合,可以藉助火焰圖和主線程消息隊列日誌來分析排查問題。

問題分析工具

分析單個白屏日誌的工具:

  • 網絡庫和圖片庫的現場信息

    • 便於定位圖片加載耗時集中在哪些階段

24.jpg

25.jpg

  • 白屏現場頁面內容

26.jpg

問題比例

下圖是目前得物App線上白屏問題的分佈,問題分配的比例在99.7%以上。且大頭還是在網絡和磁盤緩存阻塞等設備問題上,CDN問題僅佔1.24%,整體處於相對穩定的狀態,後續網絡資源調度和圖片庫磁盤鎖優化落地之後將得到明顯改善。

27.jpg

五、總結

白屏作為長鏈路綜合性問題,任意環節出問題都會引發最終的白屏,尤其是客觀因素較多的網絡側。通過白屏歸因平台可以對線上問題分而治之,對弱網,CDN節點異常這類無法根治的問題可以通過配置告警來持續關注防劣化,對圖片解碼超時,主線程卡頓這類可以專項進行治理優化,對線上反饋的單用户白屏則可以通過診斷工具快速定位到根因。

*文 / Jordas

本文屬得物技術原創,更多精彩文章請看:得物技術

未經得物技術許可嚴禁轉載,否則依法追究法律責任!

user avatar chongdianqishi 頭像 AmbitionGarden 頭像 febobo 頭像 eolink 頭像 lizhiqianduan 頭像 yangrd 頭像 shumin_5bd11c2a4b889 頭像 yourena_c 頭像 smartbidashuju 頭像 qaz666 頭像 object_684147fd5fae2 頭像 zhao_59106344e870e 頭像
點贊 13 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.