從OnTrimMemory角度談Android程式碼記憶體優化

androidperformance發表於2015-07-21

OnTrimMemory 回撥是 Android 4.0 之後提供的一個API,這個 API 是提供給開發者的,它的主要作用是提示開發者在系統記憶體不足的時候,通過處理部分資源來釋放記憶體,從而避免被 Android 系統殺死。這樣應用在下一次啟動的時候,速度就會比較快。
本文通過問答的方式,從各個方面來講解 OnTrimMemory 回撥的使用過程和效果。想要開發高效能且使用者體驗良好的 Android 應用,那麼這篇文章你不應該錯過。

相關文章:從Android資源角度談Android程式碼記憶體優化

0. OnTrimMemory回撥的作用?

OnTrimMemory是Android在4.0之後加入的一個回撥,任何實現了ComponentCallbacks2介面的類都可以重寫實現這個回撥方法.OnTrimMemory的主要作用就是 指導應用程式在不同的情況下進行自身的記憶體釋放,以避免被系統直接殺掉,提高應用程式的使用者體驗.

Android系統會根據不同等級的記憶體使用情況,呼叫這個函式,並傳入對應的等級:

  • TRIM_MEMORY_UI_HIDDEN 表示應用程式的 所有UI介面被隱藏了,即使用者點選了Home鍵或者Back鍵導致應用的UI介面不可見.這時候應該釋放一些資源.TRIM_MEMORY_UI_HIDDEN這個等級比較常用,和下面六個的關係不是很強,所以單獨說.

下面三個等級是當我們的應用程式真正執行時的回撥:

  • TRIM_MEMORY_RUNNING_MODERATE 表示應用程式正常執行,並且不會被殺掉。但是目前手機的記憶體已經有點低了,系統可能會開始根據LRU快取規則來去殺死程式了。
  • TRIM_MEMORY_RUNNING_LOW 表示應用程式正常執行,並且不會被殺掉。但是目前手機的記憶體已經非常低了,我們應該去釋放掉一些不必要的資源以提升系統的效能,同時這也會直接影響到我們應用程式的效能。
  • TRIM_MEMORY_RUNNING_CRITICAL 表示應用程式仍然正常執行,但是系統已經根據LRU快取規則殺掉了大部分快取的程式了。這個時候我們應當儘可能地去釋放任何不必要的資源,不然的話系統可能會繼續殺掉所有快取中的程式,並且開始殺掉一些本來應當保持執行的程式,比如說後臺執行的服務。

當應用程式是快取的,則會收到以下幾種型別的回撥:

  • TRIM_MEMORY_BACKGROUND 表示手機目前記憶體已經很低了,系統準備開始根據LRU快取來清理程式。這個時候我們的程式在LRU快取列表的最近位置,是不太可能被清理掉的,但這時去釋放掉一些比較容易恢復的資源能夠讓手機的記憶體變得比較充足,從而讓我們的程式更長時間地保留在快取當中,這樣當使用者返回我們的程式時會感覺非常順暢,而不是經歷了一次重新啟動的過程。
  • TRIM_MEMORY_MODERATE 表示手機目前記憶體已經很低了,並且我們的程式處於LRU快取列表的中間位置,如果手機記憶體還得不到進一步釋放的話,那麼我們的程式就有被系統殺掉的風險了。
  • TRIM_MEMORY_COMPLETE 表示手機目前記憶體已經很低了,並且我們的程式處於LRU快取列表的最邊緣位置,系統會最優先考慮殺掉我們的應用程式,在這個時候應當儘可能地把一切可以釋放的東西都進行釋放。

1. 哪些元件可以實現OnTrimMemory回撥?

  • Application.onTrimMemory()
  • Activity.onTrimMemory()
  • Fragement.OnTrimMemory()
  • Service.onTrimMemory()
  • ContentProvider.OnTrimMemory()

2. OnTrimMemory回撥中可以釋放哪些資源?

通常在架構階段就要考慮清楚,我們有哪些東西是要常駐記憶體的,有哪些是伴隨介面存在的.一般情況下,有下面幾種資源需要進行釋放:

  • 快取 快取包括一些檔案快取,圖片快取等,在使用者正常使用的時候這些快取很有作用,但當你的應用程式UI不可見的時候,這些快取就可以被清除以減少記憶體的使用.比如第三方圖片庫的快取.
  • 一些動態生成動態新增的View. 這些動態生成和新增的View且少數情況下才使用到的View,這時候可以被釋放,下次使用的時候再進行動態生成即可.比如原生桌面中,會在 OnTrimMemory的TRIM_MEMORY_MODERATE等級中,釋放所有AppsCustomizePagedView的資源,來保證在低記憶體的時候,桌面不會輕易被殺掉.

2.1 例子:釋放不常用到的View.

程式碼出處:Launcher

Launcher.java:

@Override
public void onTrimMemory(int level) {
    super.onTrimMemory(level);
    if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
        mAppsCustomizeTabHost.onTrimMemory();
    }
}

AppsCustomizeTabHost.java:

public void onTrimMemory() {
    mContent.setVisibility(GONE);
    // Clear the widget pages of all their subviews - this will trigger the widget previews
    // to delete their bitmaps
    mPagedView.clearAllWidgetPages();
}

AppsCustomizePagedView.java:

public void clearAllWidgetPages() {
    cancelAllTasks();
    int count = getChildCount();
    for (int i = 0; i < count; i++) {
        View v = getPageAt(i);
        if (v instanceof PagedViewGridLayout) {
            ((PagedViewGridLayout) v).removeAllViewsOnPage();
            mDirtyPageContent.set(i, true);
        }
    }
}

PagedViewGridLayout.java

@Override
public void removeAllViewsOnPage() {
    removeAllViews();
    mOnLayoutListener = null;
    setLayerType(LAYER_TYPE_NONE, null);
}

2.2 例子: 清除快取

程式碼出處:Contact

@Override
public void onTrimMemory(int level) {
    if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
        // Clear the caches.  Note all pending requests will be removed too.
        clear();
    }
}

public void clear() {
    mPendingRequests.clear();
    mBitmapHolderCache.evictAll();
    mBitmapCache.evictAll();
}

3. OnTrimMemory和onStop的關係?

onTrimMemory()方法中的TRIM_MEMORY_UI_HIDDEN回撥只有當我們程式中的所有UI元件全部不可見的時候才會觸發,這和onStop()方法還是有很大區別的,因為onStop()方法只是當一個Activity完全不可見的時候就會呼叫,比如說使用者開啟了我們程式中的另一個Activity。

因此,我們可以在onStop()方法中去釋放一些Activity相關的資源,比如說取消網路連線或者登出廣播接收器等,但是像UI相關的資源應該一直要等到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)這個回撥之後才去釋放,這樣可以保證如果使用者只是從我們程式的一個Activity回到了另外一個Activity,介面相關的資源都不需要重新載入,從而提升響應速度。

需要注意的是,onTrimMemory的TRIM_MEMORY_UI_HIDDEN 等級是在onStop方法之前呼叫的.

4. OnTrimMemory和OnLowMemory的關係?

在引入OnTrimMemory之前都是使用OnLowMemory回撥,需要知道的是,OnLowMemory大概和 OnTrimMemory中的TRIM_MEMORY_COMPLETE級別相同,如果你想相容api<14的機器,那麼可以用 OnLowMemory來實現,否則你可以忽略OnLowMemory,直接使用OnTrimMemory即可.

5. 為什麼要呼叫OnTrimMemory?

儘管系統在記憶體不足的時候殺程式的順序是按照LRU Cache中從低到高來的,但是它同時也會考慮殺掉那些佔用記憶體較高的應用來讓系統更快地獲得更多的記憶體。

所以如果你的應用佔用記憶體較小,就可以增加不被殺掉的機率,從而快速地恢復(如果不被殺掉,啟動的時候就是熱啟動,否則就是冷啟動,其速度差在2~3倍)。

所以說在幾個不同的OnTrimMemory回撥中釋放自己的UI資源,可以有效地提高使用者體驗。

6. 有哪些典型的使用場景?

6.1 常駐記憶體的應用

一些常駐記憶體的應用,比如Launcher、安全中心、電話等,在使用者使用過要退出的時候,需要呼叫OnTrimMemory來及時釋放使用者使用的時候所產生的多餘的記憶體資源:比如動態生成的View、圖片快取、Fragment等。

6.2 有後臺Service執行的應用

這些應用不是常駐記憶體的,意味著可以被工作管理員殺掉,但是在某些場景下使用者不會去殺。這類應用包括:音樂、下載等。使用者退出UI介面後,音樂還在繼續播放,下載程式還在執行。這時候音樂應該釋放部分UI資源和Cache。

相關文章