Android Note - 記憶體優化

NianyiYang發表於2019-05-01

記憶體優化是Android效能優化的重點內容,一般來說,談及效能優化,肯定避不開記憶體優化。雖然現在手機記憶體都很大,但並不意味著我們的App在使用記憶體時能“為所欲為”。這篇就簡單地總結一下關於記憶體優化要注意的一些事項。


程式級別的記憶體策略

我們知道,Android 系統是一個基於 Linux 的開源系統,使用的是 Dalvik / Android Runtime 作為對應的虛擬機器來執行程式碼。底層記憶體分配機制在這裡就不詳述了,只看 Application Framework 這一層。

首先,Android 中的記憶體分配由 ActivityManagerService 統一管理。這是一個很重要的類,除了記憶體管理之外,它還管理了 Activity 生命週期,啟動行為,訊息派發等功能。當使用者點選應用的啟動圖示時, ActivityManagerService 會請求系統建立一個程式,當程式建立完成之後,繫結給 ActivityManagerService,這時才會開始 App 本身的生命週期。

而關於記憶體回收這塊,Application Framework 給建立的程式規定了五個的回收型別優先順序,即從最先被回收到最後被回收,分別是:

  • Empty process 空程式
  • Background process 後臺程式
  • Service process 服務程式
  • Visible process 可見程式
  • Foreground process 前臺程式

然後在 ActivityManagerService 中,會對所有程式進行評分,並將評分同步到 Linux 核心中,最終由核心來執行記憶體回收


物件級別的記憶體策略

關於物件級別的記憶體管理策略,由於是應用程式自動管理記憶體分配以及記憶體回收(Java GC),能操作的有限,但是基本的概念還是要了解的,因為當你知道物件/變數在記憶體中是如何分配,以及分配到堆/棧/方法區的時候,在寫程式碼的時候就會有意規避一些問題,達到記憶體優化的效果。


Android常見記憶體問題

記憶體洩漏

記憶體洩漏 Memory Leak,指的是記憶體申請並使用完畢以後,系統因為某些原因無法回收該記憶體塊的情況。其實質也是長生命週期物件持有短生命週期的物件,導致短生命週期物件需要被回收時,由於長生命週期的物件持有無法被回收的現象。

在Android中,以下幾個場景容易出現記憶體洩漏。

單例物件使用了非全域性的 Context

這個是初學者容易犯的錯誤,在構造一個單例物件時,往往需要使用 Context 物件來初始化建構函式,並持有一個 Context物件,這時如果傳入一個非全域性的 Context ,會導致該 Context物件在 GC 時無法回收,造成記憶體洩漏。

解決方案:單例使用 ApplicationContext ,這個物件的生命週期是整個應用的生命週期,不會導致洩漏

匿名內部類 / 非靜態內部類 / 非同步執行緒 / Handler 持有外部類的引用

這也是常見的記憶體洩漏易於出現的場景。舉例來說,我們現在經常使用 Rxjava + Retrofit + OkHttp 來構建網路請求。有時候會碰到網速過慢導致網路請求返回慢或者超時的情況。這時如果我們關閉了當前頁面,網路請求結果仍然會被觀察者接受並重新整理UI。這時本來要被回收的UI物件由於被觀察者持有,無法回收,就導致了記憶體洩漏。

解決方案:

  1. 使用靜態內部類,並在需要的時候引入外部類,而不是直接在建構函式中引用
  2. 使用弱引用 WeakReference 來引用外部類例項(掌握Java的四種引用方式)

資源未關閉導致無法回收

這也是經常被提到的點,註冊並使用後,忘記在生命週期結束時解綁,導致無法回收。

解決方案:BroadcastReceiver、ContentObserver、File、Bitmap、Timer、EventBus 等都是需要解綁或者清空的,要養成直覺

WebView不要在佈局中定義

這個是網路上一篇文章裡看到的,我想現在應該沒有多少人會在佈局裡定義 WebView 吧(還有 Fragment )

解決方案:在程式碼中構造WebView物件,建立時上下文使用 ApplicationContext


記憶體溢位

記憶體溢位 Out Of Memory ,是指應用的記憶體申請超出了當前所能申請的最大記憶體容量,導致應用出現一系列問題甚至被系統殺掉程式。在 Android 中出現記憶體溢位,主要是因為以下原因引起。

使用 Bitmap 並且未優化

Bitmap 是產生記憶體溢位的大戶,如果沒有經過任何優化,直接載入一個 Bitmap 的話,會導致該物件吃掉大量記憶體。

解決方案:

  1. 載入圖片之前先計算出合適的縮放比例,按比例縮放。
  2. 選擇合適的解碼格式。不同的格式,記憶體佔用在很大差異。
  3. Bitmap 不用時要及時回收,呼叫 recycle 方法。

短時間建立大量物件

這個常見於列表元件的載入。載入列表時如果不優化,同一時間內建立了過多物件,就會造成記憶體溢位。

解決方案:

  1. 使用按需載入的方式載入內容(上拉載入更多)
  2. 常用物件做到儘量複用,並快取常用物件。

其他記憶體溢位的場景和解決方案

  1. 捕獲 OOM 異常,避免因為記憶體溢位而導致崩潰
  2. 使用優化的資料容器,合適的記憶體容器可以大幅度增加記憶體效能
  3. 使用 zipalign 對齊可以一定量優化記憶體效能(Google 官方推薦)
  4. 使用成熟的第三方框架,例如使用 Glide 處理圖片

相關文章