1. 程式人生 > >Android 效能優化——管理應用的記憶體

Android 效能優化——管理應用的記憶體

請保持淡定,分析程式碼,記住:效能很重要。

隨機存取儲存器(RAM)在任何軟體開發環境中都是一個很寶貴的資源。這一點在實體記憶體通常很有限的移動作業系統上,顯得尤為突出。儘管 Android Runtime(ART)和 Dalvik 虛擬機器都扮演了常規的垃圾回收的角色,但這並不意味著你可以忽略應用記憶體分配和釋放的時間和位置。你仍然需要避免引入記憶體洩漏(通常是由於靜態成員變數中持有物件引用導致),並在適當的生命週期的回撥中釋放所有的引用物件。

本文章介紹如何在應用中主動減少記憶體的使用。關於 Java 資源管理機制的更多資訊,請參閱有關管理引用資源的其他書籍或線上文件。如果你正在尋找如何分析應用中的記憶體使用情況的文章,請參閱:

Tools for analyzing RAM usage。 關於 Android Runtime 和 Dalvik 虛擬機器如何管理記憶體的更多詳細資訊,請參閱:Overview of Android Memory Management

監視可用記憶體和記憶體使用情況

Android Framework 和 Android Studio 可以幫助你分析和調整應用的記憶體使用。Android Framework 公開了幾個 API,允許你的應用在執行時動態地減少其記憶體使用。Android Studio 包含幾個工具,可讓你調查應用如何使用記憶體。

分析 RAM 使用的工具

在你修復應用記憶體使用問題之前,你首先需要找到它們。Android Studio 和 Android SDK 包含了幾個用於分析應用記憶體使用情況的工具:

  1. Android Studio 中的記憶體監視器(Memory Monitor)會顯示你的應用在單個會話過程中如何分配記憶體。該工具除了顯示了隨時間分佈的可用的和已分配的 Java 記憶體圖示,還包括垃圾回收事件(garbage collection)按鈕。你可以啟動 GC 事件,並在應用執行時拍攝 Java 堆的快照。記憶體監視器工具的輸出可以幫助你識別出你的應用由於過多的 GC 事件導致應用緩慢的點。

  2. Android Studio 中的分配跟蹤(Allocation Tracker)工具可讓你詳細瞭解應用如何分配記憶體。分配跟蹤器記錄了應用的記憶體分配情況,並列出分析快照中的所有已分配物件。你可以使用此工具跟蹤到分配了太多物件的程式碼部分。

適時釋放記憶體

Android 裝置可以使用不同的可用記憶體,這具體取決於裝置上 RAM 的大小以及使用者的操作方式。當處於記憶體緊張時,系統會廣播訊號以表明該情況,應用應監聽這些訊號並酌情調整其記憶體使用情況。

你可以使用 ComponentCallbacks2 API 來監聽這些訊號,然後根據應用生命週期或裝置事件調整記憶體使用情況。onTrimMemory() 方法允許你的應用在應用在前臺執行(在螢幕顯示)以及在後臺執行時監聽與記憶體相關的事件。
要監聽這些事件,請在 Activity 類中實現 onTrimMemory() 回撥,如下面的程式碼片段所示。

import android.content.ComponentCallbacks2;
// Other import statements ...

public class MainActivity extends AppCompatActivity
    implements ComponentCallbacks2 {

    // Other activity code ...

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that was raised.
     */
    public void onTrimMemory(int level) {

        // Determine which lifecycle or system event was raised.
        switch (level) {

            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:

                /*
                   Release any UI objects that currently hold memory.

                   The user interface has moved to the background.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:

                /*
                   Release any memory that your app doesn't need to run.

                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will
                   begin killing background processes.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:

                /*
                   Release as much memory as the process can.

                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process will be one of
                   the first to be terminated.
                */

                break;

            default:
                /*
                  Release any non-critical data structures.

                  The app received an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
                break;
        }
    }
}

onTrimMemory() 回撥是在 Android 4.0(API 級別 14)中增加的。對於早期版本,你可以使用 onLowMemory() 回撥來適配舊版本,該回調大致相當於 TRIM_MEMORY_COMPLETE 事件。

檢查你應該使用多少記憶體

為了允許多程序,Android 為每個應用分配的堆大小設定了一個強制限制。基於裝置可用總體 RAM 數量,裝置之間的確切的堆大小限制會有所不同。如果你的應用已達到堆容量並嘗試分配更多記憶體,系統將丟擲 OutOfMemoryError

為了避免記憶體不足,你可以向系統查詢以確定當前裝置上可用的堆空間。你可以通過呼叫 getMemoryInfo() 來查詢系統的狀態。這將返回一個 ActivityManager.MemoryInfo 物件,該物件提供有關裝置當前記憶體狀態的資訊,包括可用記憶體,總記憶體和記憶體閾值(系統開始殺死程序的記憶體限制大小)。ActivityManager.MemoryInfo 類還有一個布林欄位 lowMemory 以指示裝置是否執行在記憶體不足的狀態。

以下程式碼片段為在應用中使用 getMemoryInfo() 的示例。

public void doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check to see whether the device is in a low memory state.
    ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();

    if (!memoryInfo.lowMemory) {
        // Do memory intensive work ...
    }
}

// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
    ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    activityManager.getMemoryInfo(memoryInfo);
    return memoryInfo;
}

使用更多記憶體高效的程式碼構造

有一些 Android 功能、Java 類和程式碼結構往往比其他功能使用更多的記憶體。你可以通過在程式碼中選擇更有效率的替代方案來減少你的應用使用的記憶體量。

謹慎使用 Service

在不必要時執行 service 可能是 Android 應用記憶體管理中最糟糕的錯誤之一。如果你的應用需要在後臺使用 service,除非它被觸發並執行一個任務,否則其他時候 service 都應該是停止狀態。並且記得在完成任務後停止 service。否則,你可能會無意中導致記憶體洩漏。

當你啟動 service 時,系統會傾向為了保留這個 service 而一直保留 service 所在的程序。這使得程序的執行代價很高,因為 service 使用的 RAM 對其他程序是不可用的。這減少了系統可以存放到 LRU 快取中的程序數量,從而降低了應用切換效率。當記憶體緊張到系統無法維護足夠的程序來承載當前正在執行的所有 service 時,甚至可能導致系統記憶體使用不穩定。

一般應避免使用永續性 service,因為它們持續使用可用的記憶體。相應地,我們建議你使用其他實現方式,例如 JobScheduler 。 關於如何使用 JobScheduler 排程後臺程序的更多資訊,請參閱: Background Optimizations

如果你必須使用服務,則限制 service 使用壽命的最佳方法是使用 IntentService,該 service 將在處理完交代給它的 Intent 任務之後儘快結束自己。有關更多資訊,請參閱:Running in a Background Service

使用優化的資料容器

程式語言提供的一些類不會針對移動裝置進行優化。例如,通常的 HashMap 的實現方式更加消耗記憶體,因為它需要一個額外的例項物件來記錄 Mapping 操作。

Android Framework 包含了幾個優化過的資料容器:SparseArraySparseBooleanArrayLongSparseArray。 例如,SparseArray類更有效率,因為它們避免了對 key 與 value 的 autobox 自動裝箱和因此產生的額外值(每個條目建立了另外一個或兩個物件)。

如果需要,你可以隨時切換到一個非常精簡的資料結構的原始實現。

注意程式碼“抽象”

開發人員通常將抽象簡單地作為”好的程式設計實踐”,因為抽象可以提高程式碼的靈活性和可維護性。然而,抽象成本很高:一般來說,它們需要相當多的程式碼用於執行,需要更多的時間和更多的 RAM 才能將該程式碼對映到記憶體中。所以如果你的抽象沒有顯著地提升效率,應該儘量避免他們。

例如,列舉通常需要靜態常量兩倍多的記憶體,你應該嚴格避免在 Android 上使用列舉。

使用 nano protobufs

Protocol buffers 是由 Google 為序列化結構資料而設計的,一種語言無關、平臺無關、具有良好擴充套件性的協議。它類似於 XML,但更輕量,快速,簡單。如果你決定對你的資料使用 protobufs,則應始終在客戶端程式碼中使用 nano protobufs。但通常 protobuf 會生成額外的程式碼,這可能會給你的應用帶來各種問題,如增加 RAM 使用量,增加 APK 大小,減慢執行速度等。

有關更多資訊,請參閱protobuf readme檔案中的“Nano version”一節。

避免記憶體抖動

如前所述,GC 事件通常不會影響你的應用的效能。然而,短時間內大量 GC 事件可以快速消耗你的幀時間。系統花費在 GC 事件上的時間越多,它做其他的工作,如渲染或音訊流的時間就越少。

通常,記憶體抖動(memory churn)可能導致大量的 GC 事件發生。在實踐中,評價記憶體抖動的嚴重程度的標準為給定時間內產生、分配的臨時物件的數量。

例如,你可能在 for 迴圈中分配多個臨時物件。或者,你可能在 View 的 onDraw() 函式內建立新的 PaintBitmap 物件。在這兩種情況下,應用可能快速建立大量的物件。這將快速消耗可用記憶體,從而迫使 GC 事件發生。

當然,你需要在程式碼中找到記憶體抖動高的位置,然後再修復它們。這可以使用Analyze your RAM usage中討論的工具。

確定程式碼中的問題區域後,嘗試減少效能關鍵區域內的記憶體分配數量。考慮將其從內部迴圈中移出或者將其移動到基於 Factory 的分配結構中。

刪除記憶體消耗多的資源和庫

你的程式碼中的一些資源和庫可能在你不知道的情況下消耗記憶體。你的 APK 的總體尺寸(包括第三方庫或嵌入式資源)可能會影響應用消耗多少記憶體。你可以通過從程式碼中刪除任何冗餘的、不必要的或膨脹的元件、資源和庫來減少應用的記憶體消耗。

減少整體的 APK 大小

你可以通過減少應用的總體大小來顯著降低應用的記憶體使用量。Bitmap 大小,資源,動畫幀和第三方庫都會影響你的 APK 的大小。Android Studio 和 Android SDK 提供多種工具來幫助你減少資源和外部依賴的大小。

關於如何減少整體 APK 大小的更多資訊,請參閱:Reduce APK Size

使用 Dagger 2 進行依賴注入

依賴注入框架可以簡化你編寫的程式碼,並提供可用於測試和其他可更改配置的自適應的環境。

如果你打算在應用中使用依賴注入框架,請考慮使用 Dagger 2。Dagger 2不使用反射來掃描你的應用的程式碼。Dagger 2 實現在靜態編譯時,這意味著它可以在 Android 應用中使用,而無需耗費執行時或記憶體。

使用反射的其他依賴注入框架往往通過掃描程式碼來執行初始化操作。此過程可能需要更多的 CPU 週期和 RAM,並且在應用啟動時可能會導致明顯的卡頓。

小心使用第三方庫

第三方庫通常都不是為行動網路環境而編寫的,如果用於移動客戶端上,可能工作時效率低下。當你決定使用第三方庫時,可能需要針對移動裝置優化。在決定使用它之前,可以根據程式碼大小和 RAM 佔用空間進行必要的分析。

即便是一些針對移動裝置優化的庫也可能由於不同的實現而導致問題。例如,一個庫可以使用 nano protobufs,而另一個庫可以使用 micro protobufs,從而在你的應用中導致兩種不同的 protobuf 實現。這種情況可能發生在使用不同的日誌記錄,分析,影象載入框架,快取以及你不期望的許多其他事情上。

雖然 ProGuard 可以通過使用正確的標誌來刪除 API 和資源,但它無法刪除庫的內部的大量依賴關係。你所需的庫的功能可能需要更低級別的依賴。以下幾種情況將突顯出這個問題:使用 Activity 子類的庫(這將趨向於具有大量的依賴關係);當庫使用反射(這是常見的,這意味著你需要花費大量的時間手動調整 ProGuard 才能使其起作用)等等。

同時要避免為了 1 個或者 2 個功能而匯入整個庫。你一定不想加入大量你甚至都不會使用到的程式碼。當你考慮是否使用庫時,請查詢與你的需求密切匹配的實現方案。否則,你可能需要考慮自己去實現。

這篇文章是 Google 官方培訓課程文件中的一篇,拿 Google 翻譯了一下,轉成中文放在這裡,有不恰當的地方歡迎指出。

相關推薦

Android 效能優化——管理應用記憶體

請保持淡定,分析程式碼,記住:效能很重要。 隨機存取儲存器(RAM)在任何軟體開發環境中都是一個很寶貴的資源。這一點在實體記憶體通常很有限的移動作業系統上,顯得尤為突出。儘管 Android Runtime(ART)和 Dalvik 虛擬機器都扮演了常

Android效能優化篇之記憶體優化--記憶體洩漏

文章目錄 介紹 什麼是記憶體洩露 android中導致記憶體洩漏的主要幾種情況 1.單例模式 2.使用非靜態內部類 3.使用非同步事件處理機制Handler 4.使用靜態

Android效能優化系列之記憶體優化

在Java中,記憶體的分配是由程式完成的,而記憶體的釋放是由垃圾收集器(Garbage Collection,GC)完成的,程式設計師不需要通過呼叫函式來釋放記憶體,但也隨之帶來了記憶體洩漏的可能,上篇部落格,我介紹了 Android效能優化系列之佈局優化,本篇部落格,我將介

Android 效能優化應用啟動

寫在前面 最近工作轉到Android 效能優化方向,剛轉過來,相關經驗缺乏,紀錄一個目前讓人惱火的問題。非常遺憾,本文到目前為止還未能提供解決問題的優化方案,也沒有明確定位到導致效能問題的瓶頸所在。就像解數學題一樣,花費了大把時間,然並卵。之所以寫它

Android效能優化——渲染、記憶體、電源優化

越整理越要找更多資料,越寫越發覺自己不懂的東西更多。學習的路還很長… 本文主要從 介面,記憶體,電量優化三個方面展開,梳理一下自己的知識。 介面、GPU 渲染效能 大多數使用者感知到的卡頓等效能問題的最主要根源都是因為渲染效能。從設計師的角

[Android 效能優化系列]記憶體之基礎篇--Android如何管理記憶體

轉載請標明出處(http://blog.csdn.net/kifile),再次感謝 在接下來的一段時間裡,我會每天翻譯一部分關於效能提升的Android官方文件給大家 下面是本次的正文: ################ 隨機訪問儲存器(Ram) 不管在哪種軟體開發

Android效能優化Android圖片載入方案--Bitmap的記憶體管理優化方案

如果你覺得對你有幫助的話,希望可以star/follow一下喲,我會持續保持更新。 寫在前面:筆者的上一篇博文有提到過,如果不恰當的使用Bitmap,很容易造成OOM。這篇博文就來談談應該如何正確的管理Bitmap的記憶體,以及優化策略。

Android 效能優化——記憶體

歡迎轉載,轉載請標明出處【Hoolay Team】: http://www.cnblogs.com/hoolay/p/6278229.html Author : Hoolay Android Team  Chiclaim 一、android官方一

android效能優化——記憶體洩漏

在專案初期階段或者業務邏輯很簡單的時候對於app效能之一塊沒有太多感覺,但是隨著專案版本的迭代和專案業務邏輯越來越大,越來越複雜的時候,就會逐漸感覺到app效能的重要性,所以在專案初期階段時,就要有app效能這一意識,也便於專案後期的版本迭代和業務擴充套件;這裡所提到的效能優化問題是:記憶體洩漏

Java效能優化三:記憶體管理與垃圾回收機制,開發必備優化技巧!

一、Java 類載入機制的特點: (1)基於父類的委託機制:執行一個程式時,總是由 AppClass Loader (系統類載入器)開始載入指定的類,在載入類時,每個類載入器會將載入任務上交給其父,如果其父找不到,再由自己去載入, Bootstrap Loader (啟動類載入器)是最頂級的類載

Android 效能優化記憶體洩漏檢測以及記憶體優化(中)

Android 記憶體洩漏檢測   通過上篇部落格我們瞭解了 Android JVM/ART 記憶體的相關知識和洩漏的原因,再來歸類一下記憶體洩漏的源頭,這裡我們簡單將其歸為一下三類:自身編碼引起由專案開發人員自身的編碼造成;第三方程式碼引起這裡的第三

Android 效能優化記憶體

一、android官方一些記憶體方面的記憶體tips 1、避免建立不必要的物件。 如儘量避免字串的加號拼接,可以使用StringBuilder來拼接。 如果需要TextView設定多個字串片段,可以使用textView.append方法,不要直接用加號拼起

Android 效能優化記憶體檢測、卡頓優化、耗電優化、APK瘦身

導語 自2008年智慧時代開始,Android作業系統一路高歌,10年智慧機發展之路,如今 Android 9.0 代號P  都發布了,Android系統性能已經非常流暢了。但是,到了各大廠商手裡,改原始碼自定系統,使得Android原生系統變得魚龍混雜。另外,到了不同層次的

Android效能優化(一)記憶體洩露優化(靜態變數、單例模式、屬性動畫)

記憶體洩露優化分為兩個方面,一方面是在開發過程中避免寫出有記憶體洩露的程式碼,另一方面是通過一些分析工具比如 MAT來找出潛在的記憶體洩露繼而解決。 一、靜態變數導致記憶體洩露。一般情況下靜態變數引用

Android效能優化:手把手帶你全面瞭解 記憶體洩露 & 解決方案

前言 在Android中,記憶體洩露的現象十分常見;而記憶體洩露導致的後果會使得應用Crash 本文 全面介紹了記憶體洩露的本質、原因 & 解決方案,最終提供一些常見的記憶體洩露分析工具,希望你們會喜歡。 掃碼檢視公眾號: 目錄 1. 簡介 即 ML (

Android 效能優化記憶體洩漏的檢測與修復

在 Android 開發中, 記憶體優化是APP效能優化中很重要的一個部分. 而在記憶體優化中, 最重要的就是修復記憶體洩漏問題. 本文就來介紹一下記憶體洩漏的基本概念以及常用的檢測手段. 1. 什麼是記憶體洩漏 簡單來說, 當一個物件不再被使用時,

Android 效能優化記憶體洩漏檢測以及記憶體優化(下)

Android 記憶體優化   上篇部落格描述瞭如何檢測和處理記憶體洩漏,這種問題從某種意義上講是由於程式碼的錯誤導致的,但是也有一些是程式碼沒有錯誤,但是我們可以通過很多方式去降低記憶體的佔用,使得應用的整體記憶體處於一個健康的水平,下面總結一下記憶

Android效能優化--記憶體

看到很多關於記憶體優化的部落格好文,也收藏了好多地址,但每次看時都需要在眾多收藏的地址裡尋找一番,不如寫到自己的部落格裡,方便隨時翻看。 記憶體優化也是android進階的必學內容。APP記憶體的使用,是評價一款應用效能高低的一個重要指標。 1.記憶體與記憶

Android UI效能優化 檢測應用中的UI卡頓

一、概述 在做app效能優化的時候,大家都希望能夠寫出絲滑的UI介面,以前寫過一篇部落格,主要是基於Google當時釋出的效能優化典範,主要提供一些UI優化效能示例: 實際上,由於各種機型的配置不同、程式碼迭代歷史悠久,程式碼中可能會存在很多在U

Android效能優化 -- 應用啟動優化之啟動頁設計

上篇部落格我們學習了應用啟動優化的一些優化思路,經過這些優化後,如果還不能達到你的要求,我們一般會做個啟動頁。因為啟動頁一般View數量比較少,業務邏輯比較簡單,因此啟動比較快。一、設計思路常規啟動頁設計思路通常點選桌面就會執行Application中的邏輯,然後會跳入啟動頁