客服群裡叫喊著:這個使用者圖片不顯示了,那個使用者圖片也不顯示了。我拿著手上一切正常的測試機,what the hell……
默默地開啟bugly。
滿園春色關不住,遍地記憶體溢位來!是的,又闖禍了!
記憶體問題永遠是既陌生又熟悉的話題,而且大多數都發生在一個叫作使用者家的手機上。安卓系統本身不斷的在優化,三方框架也逐漸成熟,外加手機廠商的大記憶體加持,似乎記憶體問題變得少見,但還是不能忽視。
藉著這次修復記憶體問題的記錄,分享一些“自以為”的解決思路,僅供參考。ok,let’s go!
修復問題的三部曲,先復現,再定位,最後修復。
復現
估計有的人會說,異常現象都在那,有啥好復現的,衝進程式碼直接開幹。
修復bug永遠是個驚心動魄的事,稍微一不小心就有可能天崩地裂。不是修復不完全,就是引入新問題。從起因開始瞭解整個緣由,一方面能加深對問題的理解,同時確保最終能驗證問題是否得到修復。
記憶體的問題經常發生在一些比較特殊的環境下,而且很多時候不一定是必現,往往體現在一些中低端機型上。所以從機型上入手可能會是一個不錯的選擇。
最終,通過bugly查到了對應的問題機型及系統版本,上各類雲測平臺找到了臺雲測試機。按照進入問題頁面的幾個固定流程,反覆執行,最終鎖定了復現流程。
定位
知道問題如何復現,接下來就是定位問題到底出在哪。通常記憶體的問題,會碰到兩種情況:
- 記憶體堆積:由於特殊情況造成的頁面關閉但資源還遺漏在記憶體中。
- 記憶體高佔用:由於業務需要或者使用不當導致記憶體佔用量過高。
我們先來看看這次的問題屬於哪種情況。
在Android Studio2.3及之前版本上自帶的Android monitor中,可以直觀的反應出當前應用的整體記憶體使用水平。[如何使用工具的分享估計大家都看膩了,這次就不再重複了。]
142MB!!!!進入事故現場之前就已經被佔用了這麼多記憶體。難怪之後會記憶體異常。看來這次要先解決記憶體高佔用的問題,我們先要詳細的瞭解記憶體的具體情況,才知道從哪下手去解決,無論是避免無意義的使用或者優化必要的佔用。
先強制gc一下,然後dump java heap,看一下整體記憶體裡的情況,按照shallow size排序。
首當其衝的byte陣列映入眼簾,大家都明白的,bitmap一直都是大客戶。我們接著分析下byte[]中的各個物件。
從資料上看,有很多大小相同的記憶體使用,從理論上看應該是有很多尺寸相同的圖片。可為什麼會有這麼多呢?是相同的圖片重複了?or other?
所謂耳聽為虛眼見為實,如果能看到這些圖片長什麼樣,是否就容易做出對應的判斷了?來,開始行動:
來自Gracker的Android記憶體優化之三:開啟MAT中的Bitmap原圖 | Performance。
感謝Gracker的分享,Get到一個新技能。具體流程參見傳送門。主體思路就是通過MAT將對應的byte陣列另存為圖片原始檔案,再用對應的工具開啟預覽即可。不過我記得以前Android Studio是可以直接看的,可現在不知道跑哪了。
步驟一:
因為Android Studio dump出來的檔案mat是無法直接開啟的,所以需要做一次轉換。在Captures中找到剛剛dump出來的prof檔案。右鍵 -> Export to standar .hprof 即可。
步驟二:
通過MAT Eclipse Memory Analyzer Open Source Project 開啟。
步驟三:
右鍵想要檢視的物件 -> Copy -> Save Value To File。儲存為xxx.data。他推薦使用Gracker分享中的gimp。Photoshop不確定是不是我使用方式有問題,在驗證的時候一直無法正常顯示。
步驟四:
檢視對應圖片的相關屬性,主體是要寬高,因為上一步中儲存的是圖片的原始格式檔案,其中不包含對應的引數資訊,所以在匯入gimp中需要指定對應的引數。
步驟五:
開啟gimp GIMP - Downloads. 然後開啟剛剛匯出的問題。影象型別根據實際的來,一般都是8888或者565,選擇RGB Alpha或者RGB565。然後寬度與高度填寫剛剛查詢到的引數。最後點選open就能看到實際的圖片。
通過這個方式,可以直觀的檢視到記憶體中圖片的實際情況。然後我們就可以進一步分析產生問題的實際原因。
通過以上方式,定位到了3個問題:
- 有大量圖片資源佔用,首頁確實有好多圖。
- 有暫未使用到的圖片資源佔用(gone狀態)。
- 有大量蒙版圖片佔用,因為設計師要求的效果。
解決 - 大量圖片佔用
對於大量圖片佔用的問題,其實從以下幾個個方向來看思考問題。
- 從效果設計的角度來避免,儘可能的少使用滿屏圖片的方式來處理需求。但這方面我個人主張尊重設計師,專業的事情交給專業的人去處理。
- 圖片資源本身,在滿足效果的前提下,儘可能的選用RGB565,也許少量圖片不明顯,但在量大的情況下,節省的記憶體資源還是很客觀。
- 圖片資源在不使用的時候及時釋放。
結合以上方向來看下我們遇到的問題。設計角度目前無法調整,緣由都是淚,這裡就不多說了。資源本身已經是RGB565。圖片的釋放應該是fresco的強項,可從現象上看似乎並沒有。看來問題可能出在這,回ui頁面上瞄一眼,明白了。
viewpager + fragment + recyclerview,相當於大量圖片都屬於使用狀態,所以fresco不會去釋放對應的資源。
臨時解決方案:
為了確保核心邏輯的順利,通過RxBus的方式,在進入和退出核心頁面時傳送Event事件,然後在大量使用圖片的頁面註冊接收此係列事件,遍歷所有SimpleDraweeView,呼叫其Controller的onDetach或onAttach來,從而實現圖片資源引用的臨時釋放和載入恢復。
為什麼是臨時解決方案,因為我總覺得是一種取巧的方式,理論上看。是不應該直接呼叫方法來插手fresco的管理流程。所以此處留坑,之後再次深入瞭解fresco的原理後再回填,也希望大家提些建議或者意見。
解決 - 暫未使用到的圖片資源佔用
每個頁面中,都有處理網路異常及相關資料載入異常的提示。原先的處理方式是通過include統一匯入後隱藏,在遇到異常的時候才顯示出來。問題就出在這,這些異常提示本身是小概率觸發,但通過include標籤匯入的話,會直接例項化完成,佔用記憶體資源。
臨時解決方案:
改用ViewStub標籤,實現按需載入。
為什麼又是臨時解決方案呢,因為有些機型在黑屏狀態下是切斷wifi的,當重新進入應用的時候都會經過一個聯網的過程,所以會先觸發聯網異常,ViewStub只能載入一次,載入完後就佔用記憶體了。
解決 - 蒙版圖片
之前為了在圖片上顯示文字但又不想被圖案所影響,所以在上面加一層陰影蒙版來保證字型的顯示效果。習慣用fresco:overlayImage的方法來實現。但這種實現方式會造成蒙版本身是一個獨立的記憶體資源。
解決方法:
嘗試通過Processor的方式,預先把蒙版與要顯示的圖片合成,使得在記憶體中只保留一份資源。
結果
通過以上優化方式,同樣的機型再次檢測,記憶體佔用下來了....
總結
這次從記憶體高佔用入手,解決了由於記憶體使用量過高導致的記憶體溢位。等之後遇到記憶體遺留問題時,再來補下文。
記憶體問題的排查與解決算是一個老生常談的話題,因為適配等等情況往往又是一個比較棘手的問題。開發的時候很難發現,所以建議一個需求完成後都例行的檢查下記憶體狀況,看下是否有問題後者需要調整的部分。