隨着Android生態的多年發展,如今4GB 內存的手機都變成了主流,2008 年的手機只有可憐的 140MB 內存,而今年的華爲Mate 20 Pro 手機的內存已經達到了 8GB,在之前低內存設備更容易出現內存不足引發的異常和卡頓,咱們也能夠經過查看應用中用戶的手機內存在 2GB 如下所佔的比例來評估,因此在優化前要先定好本身的目標,這一點很是關鍵。好比針對2GB 以上的設備,徹底是兩種不一樣的優化思路。
內存優化誤區:
一、內存佔用越少越好。應用是否佔用了過多的內存,跟設備、系統和當時狀況有關,而不是 300MB、400MB 這樣一個絕對的數值。當系統內存充足的時候,咱們能夠多用一些得到更好的性能。當系統內存不足的時候,但願能夠作到「用時分配,及時釋放」,當系統內存出現壓力時,可以迅速釋放各類緩存來減小系統壓力。
git
- 在 Android 3.0 以前,Bitmap 對象放在Java 堆,而像素數據是放在 Native 內存中。若是不手動調用 recycle,Bitmap Native 內存的回收徹底依賴finalize 函數回調,熟悉 Java 的同窗應該知道,這個時機不太可控。
- Android 3.0~Android 7.0 將 Bitmap對象和像素數據統一放到 Java 堆中,這樣就算咱們不調用 recycle,Bitmap 內存也會隨着對象一塊兒被回收。不過 Bitmap 是內存消耗的大戶,把它的內存放到 Java 堆中彷佛不是那麼美妙。即便是最新的華爲 Mate 20,最大的 Java 堆限制也纔到 512MB,可能個人物理內存還有 5GB,可是應用仍是會由於 Java 堆內存不足致使 OOM。Bitmap 放到 Java 堆的另一個問題會引發大量的GC,對系統內存也沒有徹底利用起來。
- 有沒有一種實現,能夠將 Bitmap 內存放到 Native 中,也能夠作到和對象一塊兒快速釋放,同時 GC 的時候也能考慮這些內存防止被濫用?NativeAllocationRegistry 能夠一次知足你這三個要求,Android 8.0 正是使用這個輔助回收 Native內存的機制,來實現像素數據放到 Native 內存中。Android 8.0 還新增了硬件位圖 Hardware Bitmap,它能夠減小圖片內存並提高繪製效率。
二、Native 內存不用管。雖然 Android 8.0 從新將 Bitmap 內存放回到Native中,那麼咱們是否是就能夠爲所欲爲地使用圖片呢?答案固然是否認的。正如前面所說當系統物理內存不足時,從後臺、桌面、服務、前臺,直到手機重啓。lmk 開始殺進程,系統構想的場景就像下面這張圖描述的同樣,你們有條不絮的按照優先級排隊等着被 kill。low memory killer 的設計,是假定咱們都遵照 Android 規範,但並無考慮到中國國情。國內不少應用就像是打不死的小強,殺死一個拉起五個。頻繁的殺死、拉起進程,又會致使 system server 卡死。固然在 Android 8.0 之後應用保活變得困難不少,但依然有一些方法能夠突破。只是流程複雜些。
內存形成問題:
以前的崩潰優化中提到「內存優化」是崩潰優化工做中很是重要的一部分,相似 OOM,不少的「異常退出」其實都是由內存問題引發。內存主要會引起兩方面問題
程序員
一、異常。異常包括 OOM、內存分配失敗這些崩潰,也包括由於總體內存不足致使應用被殺死、設備重啓等問題。
github
二、卡頓。Java 內存不足會致使頻繁 GC,這個問題在 Dalvik虛擬機會更加明顯。而 ART 虛擬機在內存管理跟回收策略上都作大量優化,內存分配和 GC 效率相比提高了 5~10 倍。若是想具體測試 GC 的性能,例如暫停掛起時間、例如暫停掛起時間、總耗時、GC 吞吐量,咱們能夠經過發送SIGQUIT 信號得到 ANR 日誌。算法
adb shell kill -S QUIT PID
adb pull /data/anr/traces.txt複製代碼
它包含一些 ANR 轉儲信息以及 GC 的詳細性能信息,另外咱們還可使用 systrace 來觀察 GC 的性能耗。
sticky concurrent mark sweep paused: Sum: 5.491ms 99% C.I. 1.464ms-2.133ms Avg: 1.830ms Max: 2.133ms // GC 暫停時間
Total time spent in GC: 502.251ms // GC 總耗時
Mean GC size throughput: 92MB/s // GC 吞吐量
Mean GC object throughput: 1.54702e+06 objects/s 複製代碼
着手內存優化:
一、設備分級。
- 相信你確定遇到過,同一個應用在 4GB 內存的手機運行得很是流暢,但在 1GB 內存的手機就不必定能夠作到,並且在系統空閒和繁忙的時候表現也不太同樣。因此內存優化首先須要根據設備環境來綜合考慮,Facebook 有一個叫device-year-class的開源庫,它會用年份來區分設備的性能。使用相似 device-year-class 的策略對設備分級,對於低端機用戶能夠關閉複雜的動畫,或者是某些功能;使用 565 格式的圖片,使用更小的緩存內存等。在現實環境下,不是每一個用戶的設備都跟咱們的測試機同樣高端,在開發過程咱們要學會思考功能要不要對低端機開啓、在系統資源吃緊的時候能不能作降級。
- 緩存管理。咱們須要有一套統一的緩存管理機制,能夠適當地使用內存;當「系統有難」時,也要責無旁貸地歸還。咱們可使用 OnTrimMemory 回調,根據不一樣的狀態決定釋放多少內存。對於大項目來講,可能存在幾十上百個模塊,統一緩存管理能夠更好地監控每一個模塊的緩存大小。
- 安裝包大小。安裝包中的代碼、資源、圖片以及 so 庫的體積,跟它們佔用的內存有很大的關係。一個 80MB 的應用很難在 512MB 內存的手機上流暢運行,這種狀況咱們須要考慮針對低端機用戶推出 4MB 的輕量版本,例如 Facebook Lite、今日頭條極速版都是這個思路。
二、
Bitmap優化。Bitmap 內存通常佔應用總內存很大一部分,因此作內存優化永遠沒法避開圖片內存這個「永恆主題」。
- 統一圖片庫。圖片內存優化的前提是收攏圖片的調用,這樣咱們能夠作總體的控制策略。例如低端機使用 565 格式、更加嚴格的縮放算法,可使用 Glide、Fresco 或者採起自研均可以。並且須要進一步將全部 Bitmap.createBitmap、BitmapFactory 相關的接口也一併收攏。
- 統一監控。第一是大圖片監控:咱們須要注意某張圖片內存佔用是否過大,例如長寬遠遠大於 View 甚至是屏幕的長寬。在開發過程當中,若是檢測到不合規的圖片使用,應該當即彈出對話框提示圖片所在的 Activity 和堆棧,讓開發同窗更快發現並解決問題。在灰度和線上環境下能夠將異常信息上報到後臺,咱們能夠計算有多少比例的圖片會超過屏幕的大小,也就是圖片的「超寬率」。第二是重複圖片監控:重複圖片指的是 Bitmap 的像素數據徹底一致,可是有多個不一樣的對象存在。這個監控不須要太多的樣本量,通常只在內部使用。第三是圖片總內存:經過收攏圖片使用,咱們還能夠統計應用全部圖片佔用的內存,這樣在線上就能夠按不一樣的系統、屏幕分辨率等維度去分析圖片內存的佔用狀況。在 OOM 崩潰的時候,也能夠把圖片佔用的總內存、Top N 圖片的內存都寫到崩潰日誌中,幫助咱們排查問題。
三、
內存泄漏。內存泄漏簡單來講就是沒有回收再也不使用的內存,排查和解決內存泄漏也是內存優化沒法避開的工做之一。內存泄漏主要分兩種狀況,一種是同一個對象泄漏,還有一種狀況更加糟糕,就是每次都會泄漏新的對象,可能會出現幾百上千個無用的對象。不少內存泄漏都是框架設計不合理所致使,各類各樣的單例滿天飛,MVC 中 Controller 的生命週期遠遠大於 View。優秀的框架設計能夠減小甚至避免程序員犯錯,固然這不是一件容易的事情,因此咱們還須要對內存泄漏創建持續的監控。
- Java內存泄漏。創建相似 LeakCanary 自動化檢測方案,至少作到 Activity 和 Fragment 的泄漏檢測。在開發過程,咱們但願出現泄漏時能夠彈出對話框,讓開發者更加容易去發現和解決問題。內存泄漏監控放到線上並不容易,咱們能夠對生成的 Hprof 內存快照文件作一些優化裁剪大部分圖片對應的 byte 數組減小文件大小。好比一個 100MB 的文件裁剪後通常只剩下 30MB 左右。使用 7zip 壓縮最後小於 10MB,增長了文件上傳的成功率。
- OOM監控。美團有一個 Android 內存泄露自動化鏈路分析組件Probe,它在發生 OOM 的時候生成 Hprof 內存快照,而後經過單獨進程對這個文件作進一步的分析。不過在線上使用這個工具風險仍是比較大,在崩潰的時候生成內存快照有可能會致使二次崩潰,並且部分手機生成 Hprof 快照可能會耗時幾分鐘,對用戶形成的體驗影響會比較大。另外,部分 OOM 是由於虛擬內存不足致使,這塊須要具體問題具體分析。
- Native內存泄漏監控。Malloc 調試(Malloc Debug)和 Malloc鉤子(Malloc Hook)彷佛還不是那麼穩定。在 WeMobileDev 最近的一篇文章《微信 Android 終端內存優化實踐》中,微信也作了一些其餘方案上面的嘗試。
- 針對沒法重編so的狀況。使用 PLT Hook 攔截庫的內存分配函數,其中 PLT Hook 是 Native Hook 的一種方案,而後重定向到咱們本身的實現後記錄分配的內存地址、大小、來源 so 庫路徑等信息,按期掃描分配與釋放是否配對,對於不配對的分配輸出咱們記錄的信息。
- 針對可重編的so狀況。經過 GCC 的「-finstrument-functions"參數給全部函數插樁,樁中模擬調用棧入棧出棧操做;經過 ld 的「–wrap」參數攔截內存分配和釋放函數,重定向到咱們本身的實現後記錄分配的內存地址、大小、來源 so 以及插樁記錄的調用棧此刻的內容,按期掃描分配與釋放是否配對,對於不配對的分配輸出咱們記錄的信息。
坦白地說,除了 Java 泄漏檢測方案,目前 OOM 監控和 Native 內存泄漏監控都只能作到實驗室自動化測試的水平。微信的 Native 監控方案也遇到一些兼容性的問題,若是想達到灰度和線上部署,須要考慮的細節會很是多。Native 內存泄漏檢測在 iOS 會簡單一些,不過 Google 也在一直優化 Native 內存泄漏檢測的性能和易用性,相信在將來的 Android 版本將會有很大改善。