Android 如何優化APP內存


極力推薦Android 開發大總結文章:歡迎收藏
程序員Android 力薦 ,Android 開發者需要的必備技能

隨機存取存儲器(RAM)在任何軟件開發環境中都是非常有價值的資源,但對於物理內存經常受到限制的移動操作系統來說,它更有價值。 盡管Android運行時(ART)和Dalvik虛擬機都執行常規垃圾收集,但這並不意味着您可以忽略應用程序分配和釋放內存的時間和位置。 您仍然需要避免引入內存泄漏,通常由靜態成員變量中的對象引用引起,並在生命周期回調定義的適當時間釋放任何引用對象。

本頁面介紹了如何主動減少應用程序中的內存使用量。 有關Android操作系統如何管理內存的信息,請參閱Android內存管理概述

本篇文章主要介紹 Android 開發中的部分知識點,通過閱讀本篇文章,您將收獲以下內容:

  1. 監視可用內存和內存使用情況
  2. 為響應事件釋放內存
  3. 分析檢查你的app需要用多少內存
  4. 使用內存優化框架
  5. 移除內存密集型資源,以及lib庫

1. 監視可用內存和內存使用情況

在修復解決 APP 中的內存使用問題之前,首先需要找到它們。 Android Studio中的內存分析器Memory Profiler可以幫助您通過以下方式查找和診斷內存問題:

  • 1 . 看看你的應用程序隨着時間的推移如何分配內存。

Memory Profiler顯示了一個實時圖,顯示您的應用程序使用了多少內存,分配了Java對象的數量以及何時發生垃圾回收。

    1. 啟動垃圾收集事件並在運行應用程序時抓取Java堆的快照。
    1. 記錄您的應用程序的內存分配

然后檢查所有分配的對象,查看每個分配的堆棧跟蹤,然后跳轉到Android Studio編輯器中的相應代碼。

Android內存管理概述中所述。

2. 為響應事件釋放內存

Android可以通過多種方式從您的應用程序中回收內存,或者在必要時將應用程序徹底關閉以釋放內存以用於關鍵任務。 為了進一步幫助平衡系統內存,避免系統需要終止應用程序進程,可以在Activity類中實現ComponentCallbacks2接口。 提供的onTrimMemory()回調方法允許您的應用程序在您的應用程序處於前台或后台時偵聽與內存相關的事件,然后釋放對象以響應應用程序生命周期或指示系統需要回收內存的系統事件。

例如,您可以實現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 時候添加的,之前版本請用onLowMemory() 方法,跟TRIM_MEMORY_COMPLETE事件處理一樣。

3. 分析檢查你的app需要用多少內存

為了允許多個正在運行的進程,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;
}

4. 使用內存優化框架

一些Android功能,Java類和代碼構造傾向於使用比其他更多的內存。 您可以通過在代碼中選擇更有效的替代方法來最大限度地減少應用程序使用的內存量

謹慎的使用Services

在不需要服務的情況下運行服務是Android應用程序可能造成的最嚴重的內存管理錯誤之一。
如果你的應用程序需要一個服務來在后台執行工作,那么除非它需要運行一個工作,否則不要讓它保持運行。 記得在完成任務時停止服務。 否則,您可能會無意中造成內存泄漏。
當你啟動一個服務時, 系統需要始終保持運行該服務的進程。此行為使得服務進程非常昂貴,因為服務使用的RAM對其他進程仍然不可用。這樣可以減少系統在LRU緩存中保留的緩存進程的數量,從而降低應用程序切換的效率。內存不足時系統甚至可能導致系統崩潰,系統無法維護足夠的進程來承載當前運行的所有服務。

您通常應該避免使用持久性服務,因為這些服務會放在可用內存上。相反,我們建議您使用諸如JobScheduler之類的替代實現。有關如何使用JobScheduler安排后台進程的更多信息,請參閱后台優化。

如果您必須使用服務,那么限制服務使用壽命的最好方法就是使用IntentService,一旦完成了處理啟動它的意圖,IntentService就會自動完成。有關更多信息,請閱讀在后台服務中運行。

使用更優化多數據容器

編程語言提供的某些類未針對在移動設備上使用進行優化。 例如,通用的HashMap實現可能是相當低效的內存,因為每個映射都需要單獨的入口對象。

Android框架包括幾個優化的數據容器,包括SparseArraySparseBooleanArrayLongSparseArray。 例如,SparseArray類更有效率,因為它們避免了系統需要自動復制密鑰的情況,有時還需要創建另外一個或兩個對象。

如有必要,您可以隨時切換到原始數組以獲得精簡的數據結構。

使用nano protobufs進行序列化數據

協議緩沖區是一種語言中立,平台無關,可擴展的機制,由Google設計,用於序列化結構化數據 - 類似於XML,但更小,更快,更簡單。 如果你決定為你的數據使用protobufs,你應該總是在你的客戶端代碼中使用nano protobufs。 經常protobufs生成非常詳細的代碼,這可能會導致您的應用程序中的許多種問題,如增加的RAM使用,APK大小增加,和較慢的執行。

有關更多信息,請參閱protobuf自述文件中的“Nano版本”部分

避免內存泄漏

如前所述,垃圾收集事件通常不會影響您的應用程序的性能。但是,很多短時間內發生的垃圾收集事件可能會很快消耗掉你的幀時間。系統花費在垃圾收集上的時間越多,執行其他內容(如渲染或流式傳輸音頻)的時間就越少。

內存流失通常會導致大量的垃圾收集事件發生。在實踐中,內存流失描述了在給定的時間內發生的分配的臨時對象的數量。

例如,您可以在for循環中分配多個臨時對象。或者您可以在視圖的onDraw()函數內創建新的PaintBitmap對象。在這兩種情況下,應用程序都會以大批量快速創建大量對象。這些可能會迅速消耗年輕一代中的所有可用內存,從而迫使垃圾收集事件發生。

當然,你需要在你的代碼中找到內存流失高的地方,然后才能修復它們。為此,您應該在Android Studio中使用Memory Profiler

一旦確定了代碼中的問題區域,請嘗試減少性能關鍵區域內的分配數量。考慮將內容移出內部循環,或者將它們移動到基於工廠的分配結構中。

5. 移除內存密集型資源,以及lib庫

你的代碼中的一些資源和庫可以在你不知道的情況下吞噬內存。 您的APK的整體大小(包括第三方庫或嵌入式資源)可能會影響您的應用消耗的內存量。 您可以通過從代碼中刪除冗余,不必要或臃腫的組件,資源或庫來改善應用程序的內存消耗。

減小APK的大小

您可以通過減少應用程序的整體大小來顯着減少應用程序的內存使用量。 位圖大小,資源,動畫幀和第三方庫都可以影響APK的大小。 Android StudioAndroid SDK提供了多種工具來幫助您減少資源和外部依賴的大小。

有關如何減少您的整體APK大小的更多信息,請參閱縮小APK大小。

使用Dagger 2進行依賴注入

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

如果您打算在應用程序中使用依賴項注入框架,請考慮使用Dagger 2. Dagger不使用反射來掃描您的應用程序的代碼。 Dagger的靜態,編譯時實現意味着它可以在Android應用程序中使用,而無需運行成本或內存使用。

其他使用反射的依賴注入框架傾向於通過掃描代碼來注釋來初始化進程。 這個過程可能需要更多的CPU周期和內存,並且在應用程序啟動時會引起明顯的滯后。

謹慎使用外部庫

外部庫代碼通常不是針對移動環境編寫的,而且在用於移動客戶端時可能效率低下。當您決定使用外部庫時,您可能需要為移動設備優化該庫。預先計划好這個工作,然后根據代碼大小和內存占用情況來分析這個庫,然后才決定使用它。

即使一些移動優化的庫可能由於不同的實現而導致問題。例如,一個庫可能使用nano protobufs,而另一個庫使用微型protobufs,導致您的應用程序中有兩個不同的protobuf實現。這可能發生在不同的日志記錄,分析,圖像加載框架,緩存以及許多您不希望的事情上。

盡管ProGuard可以幫助您使用正確的標志刪除API和資源,但它不能刪除庫的大型內部依賴關系。您需要在這些庫中的功能可能需要較低級別的依賴關系。如果庫使用反射(這是常見的,並且意味着您需要花費大量時間手動調整ProGuard才能使用反射),那么當您從庫中使用Activity子類時(這往往會產生大量的依賴關系)工作)等等。

還要避免使用一個共享庫只有一兩個功能。你不希望引入大量的代碼和開銷,甚至沒有使用。當您考慮是否使用庫時,請查找與您需要的強大匹配的實現。否則,您可能會決定創建自己的實現。

至此,本篇已結束,如有不對的地方,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!

微信關注公眾號:  程序員Android,領福利


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM