Android內存管理機制官方詳解文檔

很早之前寫過一篇《Android內存管理機制詳解》點擊量已7萬+,現把Google官方文檔整理輸出一下,供各位參考。

一、內存管理概覽

Android 運行時 (ART) 和 Dalvik 虛擬機使用分頁和內存映射來管理內存。這意味着應用修改的任何內存,無論修改的方式是分配新對象還是輕觸內存映射的頁面,都會一直駐留在 RAM 中,並且無法換出。要從應用中釋放內存,只能釋放應用保留的對象引用,使內存可供垃圾回收器回收。這種情況有一個例外:對於任何未經修改的內存映射文件(如代碼),如果系統想要在其他位置使用其內存,可將其從 RAM 中換出。

本頁面介紹了 Android 如何管理應用進程和內存分配。如需詳細瞭解如何在應用中更高效地管理內存,請參閱管理應用內存。

垃圾回收

ART 或 Dalvik 虛擬機之類的受管內存環境會跟蹤每次內存分配。一旦確定程序不再使用某塊內存,它就會將該內存重新釋放到堆中,無需程序員進行任何干預。這種回收受管內存環境中的未使用內存的機制稱爲“垃圾回收”。垃圾回收有兩個目標:在程序中查找將來無法訪問的數據對象,並回收這些對象使用的資源。

Android 的內存堆是分代的,這意味着它會根據分配對象的預期壽命和大小跟蹤不同的分配存儲分區。例如,最近分配的對象屬於“新生代”。當某個對象保持活動狀態達足夠長的時間時,可將其提升爲較老代,然後是永久代。

堆的每一代對相應對象可佔用的內存量都有其自身的專用上限。每當一代開始填滿時,系統便會執行垃圾回收事件以釋放內存。垃圾回收的持續時間取決於它回收的是哪一代對象以及每一代有多少個活動對象。

儘管垃圾回收速度非常快,但仍會影響應用的性能。通常情況下,您無法從代碼中控制何時發生垃圾回收事件。系統有一套專門確定何時執行垃圾回收的標準。當條件滿足時,系統會停止執行進程並開始垃圾回收。如果在動畫或音樂播放等密集型處理循環過程中發生垃圾回收,則可能會增加處理時間,進而可能會導致應用中的代碼執行超出建議的 16ms 閾值,無法實現高效、流暢的幀渲染。

此外,您的代碼流執行的各種工作可能迫使垃圾回收事件發生得更頻繁或導致其持續時間超過正常範圍。 例如,如果您在 Alpha 混合動畫的每一幀期間,在 for 循環的最內層分配多個對象,則可能會使內存堆受到大量對象的影響。在這種情況下,垃圾回收器會執行多個垃圾回收事件,並可能降低應用的性能。

如需詳細瞭解有關垃圾回收的一般信息,請參閱垃圾回收。

共享內存

爲了在 RAM 中容納所需的一切,Android 會嘗試跨進程共享 RAM 頁面。它可以通過以下方式實現這一點:

  • 每個應用進程都從一個名爲 Zygote 的現有進程分叉。系統啓動並加載通用框架代碼和資源(如 Activity 主題背景)時,Zygote 進程隨之啓動。爲啓動新的應用進程,系統會分叉 Zygote 進程,然後在新進程中加載並運行應用代碼。這種方法使爲框架代碼和資源分配的大多數 RAM 頁面可在所有應用進程之間共享。
  • 大多數靜態數據會內存映射到一個進程中。這種方法使得數據不僅可以在進程之間共享,還可以在需要時換出。靜態數據示例包括:Dalvik 代碼(通過將其放入預先鏈接的 .odex 文件中進行直接內存映射)、應用資源(通過將資源表格設計爲可內存映射的結構以及通過對齊 APK 的 zip 條目)和傳統項目元素(如 .so 文件中的原生代碼)。
  • 在很多地方,Android 使用明確分配的共享內存區域(通過 ashmem 或 gralloc)在進程間共享同一動態 RAM。例如,窗口 surface 使用在應用和屏幕合成器之間共享的內存,而光標緩衝區則使用在內容提供器和客戶端之間共享的內存。
    由於共享內存的廣泛使用,在確定應用使用的內存量時需要小心謹慎。有關正確確定應用內存使用量的技巧,請參閱調查 RAM 使用量。

分配與回收應用內存

Dalvik 堆侷限於每個應用進程的單個虛擬內存範圍。這定義了邏輯堆大小,該大小可以根據需要增長,但不能超過系統爲每個應用定義的上限。

堆的邏輯大小與堆使用的物理內存量不同。在檢查應用堆時,Android 會計算按比例分攤的內存大小 (PSS) 值,該值同時考慮與其他進程共享的髒頁和乾淨頁,但其數量與共享該 RAM 的應用數量成正比。此 (PSS) 總量是系統認爲的物理內存佔用量。有關 PSS 的詳情,請參閱調查 RAM 使用量指南。

Dalvik 堆不壓縮堆的邏輯大小,這意味着 Android 不會對堆進行碎片整理來縮減空間。只有當堆末尾存在未使用的空間時,Android 才能縮減邏輯堆大小。但是,系統仍然可以減少堆使用的物理內存。垃圾回收之後,Dalvik 遍歷堆並查找未使用的頁面,然後使用 madvise 將這些頁面返回給內核。因此,大數據塊的配對分配和解除分配應該使所有(或幾乎所有)使用的物理內存被回收。但是,從較小分配量中回收內存的效率要低得多,因爲用於較小分配量的頁面可能仍在與其他尚未釋放的數據塊共享。

限制應用內存

爲了維持多任務環境的正常運行,Android 會爲每個應用的堆大小設置硬性上限。不同設備的確切堆大小上限取決於設備的總體可用 RAM 大小。如果您的應用在達到堆容量上限後嘗試分配更多內存,則可能會收到 OutOfMemoryError。

在某些情況下,例如,爲了確定在緩存中保存多少數據比較安全,您可能需要查詢系統以確定當前設備上確切可用的堆空間大小。您可以通過調用 getMemoryClass() 向系統查詢此數值。此方法返回一個整數,表示應用堆的可用兆字節數。

切換應用

當用戶在應用之間切換時,Android 會將非前臺應用保留在緩存中。非前臺應用就是指用戶看不到或未運行前臺服務(如音樂播放)的應用。例如,當用戶首次啓動某個應用時,系統會爲其創建一個進程;但是當用戶離開此應用時,該進程不會退出。系統會將該進程保留在緩存中。如果用戶稍後返回該應用,系統就會重複使用該進程,從而加快應用切換速度。

如果您的應用具有緩存的進程且保留了目前不需要的資源,那麼即使用戶未使用您的應用,它也會影響系統的整體性能。當系統資源(如內存)不足時,它將會終止緩存中的進程。系統還會考慮終止佔用最多內存的進程以釋放 RAM。

注意:當應用處於緩存中時,所佔用的內存越少,就越有可能免於被終止並得以快速恢復。但是,系統也可能根據當下的需求不考慮緩存進程的資源使用情況而隨時將其終止。

二、進程間的內存分配

Android 平臺在運行時不會浪費可用的內存。它會一直嘗試利用所有可用內存。例如,系統會在應用關閉後將其保留在內存中,以便用戶快速切回到這些應用。因此,通常情況下,Android 設備在運行時幾乎沒有可用的內存。要在重要系統進程和許多用戶應用之間正確分配內存,內存管理至關重要。

本章討論了 Android 如何爲系統和用戶應用分配內存的基礎知識,另外還說明了操作系統如何應對低內存情況。

內存類型

Android 設備包含三種不同類型的內存:RAM、zRAM 和存儲器。請注意,CPU 和 GPU 訪問同一個 RAM。
在這裏插入圖片描述
圖 1. 內存類型 - RAM、zRAM 和存儲器

  • RAM 是最快的內存類型,但其大小通常有限。高端設備通常具有最大的 RAM 容量。
  • zRAM 是用於交換空間的 RAM 分區。所有數據在放入 zRAM 時都會進行壓縮,然後在從 zRAM 向外複製時進行解壓縮。這部分 RAM 會隨着頁面進出 zRAM 而增大或縮小。設備製造商可以設置 zRAM 大小上限。
  • 存儲器中包含所有持久性數據(例如文件系統等),以及爲所有應用、庫和平臺添加的對象代碼。存儲器比另外兩種內存的容量大得多。在 Android 上,存儲器不像在其他 Linux 實現上那樣用於交換空間,因爲頻繁寫入會導致這種內存出現損壞,並縮短存儲媒介的使用壽命。

內存頁面

RAM 分爲多個“頁面”。通常,每個頁面爲 4KB 的內存。

系統會將頁面視爲“可用”或“已使用”。可用頁面是未使用的 RAM。已使用的頁面是系統目前正在使用的 RAM,並分爲以下類別:

  • 緩存頁:有存儲器中的文件(例如代碼或內存映射文件)支持的內存。緩存內存有兩種類型:
    • 私有頁:由一個進程擁有且未共享
      • 乾淨頁:存儲器中未經修改的文件副本,可由 kswapd 刪除以增加可用內存
      • 髒頁:存儲器中經過修改的文件副本;可由 kswapd 移動到 zRAM 或在 zRAM 中進行壓縮以增加可用內存
    • 共享頁:由多個進程使用
      • 乾淨頁:存儲器中未經修改的文件副本,可由 kswapd 刪除以增加可用內存
      • 髒頁:存儲器中經過修改的文件副本;允許通過 kswapd 或者通過明確使用 msync() 或 munmap() 將更改寫回存儲器中的文件,以增加可用空間
  • 匿名頁:沒有存儲器中的文件支持的內存(例如,由設置了 MAP_ANONYMOUS 標記的 mmap() 進行分配)
    • 髒頁:可由 kswapd 移動到 zRAM/在 zRAM 中進行壓縮以增加可用內存

注意: 乾淨頁包含存在於存儲器中的文件(或文件一部分)的精確副本。如果幹淨頁不再包含文件的精確副本(例如,因應用操作所致),則會變成髒頁。乾淨頁可以刪除,因爲始終可以使用存儲器中的數據重新生成它們;髒頁則不能刪除,否則數據將會丟失。
隨着系統積極管理 RAM,可用和已使用頁面的比例會不斷變化。本部分介紹的概念對於管理內存不足的情況至關重要。本文檔的下一部分將對這些概念進行更詳細的說明。

內存不足管理

Android 有兩種處理內存不足情況的主要機制:內核交換守護進程和低內存終止守護進程。
內核交換守護進程
內核交換守護進程 (kswapd) 是 Linux 內核的一部分,用於將已使用內存轉換爲可用內存。當設備上的可用內存不足時,該守護進程將變爲活動狀態。Linux 內核設有可用內存上下限閾值。當可用內存降至下限閾值以下時,kswapd 開始回收內存。當可用內存達到上限閾值時,kswapd 停止回收內存。

kswapd 可以刪除乾淨頁來回收它們,因爲這些頁受到存儲器的支持且未經修改。如果某個進程嘗試處理已刪除的乾淨頁,則系統會將該頁面從存儲器複製到 RAM。此操作稱爲“請求分頁”。
在這裏插入圖片描述
圖 2. 由存儲器支持的乾淨頁已刪除

kswapd 可以將緩存的私有髒頁和匿名髒頁移動到 zRAM 進行壓縮。這樣可以釋放 RAM 中的可用內存(可用頁面)。如果某個進程嘗試處理 zRAM 中的髒頁,該頁將被解壓縮並移回到 RAM。如果與壓縮頁面關聯的進程被終止,則該頁面將從 zRAM 中刪除。

如果可用內存量低於特定閾值,系統會開始終止進程。
在這裏插入圖片描述
圖 3. 髒頁被移至 zRAM 並進行壓縮

低內存終止守護進程
很多時候,kswapd 不能爲系統釋放足夠的內存。在這種情況下,系統會使用 onTrimMemory() 通知應用內存不足,應該減少其分配量。如果這還不夠,內核會開始終止進程以釋放內存。它會使用低內存終止守護進程 (LMK) 來執行此操作。

LMK 使用一個名爲 oom_adj_score 的“內存不足”分值來確定正在運行的進程的優先級,以此決定要終止的進程。最高得分的進程最先被終止。後臺應用最先被終止,系統進程最後被終止。下表列出了從高到低的 LMK 評分類別。評分最高的類別,即第一行中的項目將最先被終止:
在這裏插入圖片描述
圖 4. Android 進程,高分在上,低分在下

以下是上表中各種類別的說明:

  • 後臺應用:之前運行過且當前不處於活動狀態的應用。LMK 將首先從具有最高 oom_adj_score 的應用開始終止後臺應用。
  • 上一個應用:最近用過的後臺應用。上一個應用比後臺應用具有更高的優先級(得分更低),因爲相比某個後臺應用,用戶更有可能切換到上一個應用。
  • 主屏幕應用:這是啓動器應用。終止該應用會使壁紙消失。
  • 服務:服務由應用啓動,可能包括同步或上傳到雲端。
  • 可覺察的應用:用戶可通過某種方式察覺到的非前臺應用,例如運行一個顯示小界面的搜索進程或聽音樂。
  • 前臺應用:當前正在使用的應用。終止前臺應用看起來就像是應用崩潰了,可能會向用戶提示設備出了問題。
  • 持久性(服務):這些是設備的核心服務,例如電話和 WLAN。
  • 系統:系統進程。這些進程被終止後,手機可能看起來即將重新啓動。
  • 原生:系統使用的極低級別的進程(例如,kswapd)。

設備製造商可以更改 LMK 的行爲。

計算內存佔用量

內核會跟蹤系統中的所有內存頁面。
在這裏插入圖片描述
圖 5. 不同進程使用的頁面

在確定應用使用的內存量時,系統必須考慮共享的頁面。訪問相同服務或庫的應用將共享內存頁面。例如,Google Play 服務和某個遊戲應用可能會共享位置信息服務。這樣便很難確定屬於整個服務和每個應用的內存量分別是多少。
在這裏插入圖片描述
圖 6. 由兩個應用共享的頁面(中間)

如需確定應用的內存佔用量,可以使用以下任一指標:

  • 常駐內存大小 (RSS):應用使用的共享和非共享頁面的數量
  • 按比例分攤的內存大小 (PSS):應用使用的非共享頁面的數量加上共享頁面的均勻分攤數量(例如,如果三個進程共享 3MB,則每個進程的 PSS 爲 1MB)
  • 獨佔內存大小 (USS):應用使用的非共享頁面數量(不包括共享頁面)

如果操作系統想要知道所有進程使用了多少內存,那麼 PSS 非常有用,因爲頁面只會統計一次。計算 PSS 需要花很長時間,因爲系統需要確定共享的頁面以及共享頁面的進程數量。RSS 不區分共享和非共享頁面(因此計算起來更快),更適合跟蹤內存分配量的變化。

三、管理應用內存

隨機存取存儲器 (RAM) 在任何軟件開發環境中都是一項寶貴資源,但在移動操作系統中,由於物理內存通常都有限,因此 RAM 就更寶貴了。雖然 Android 運行時 (ART) 和 Dalvik 虛擬機都執行例行的垃圾回收任務,但這並不意味着您可以忽略應用分配和釋放內存的位置和時間。您仍然需要避免引入內存泄漏問題(通常因在靜態成員變量中保留對象引用而引起),並在適當時間(如生命週期回調所定義)釋放所有 Reference 對象。

本頁面介紹瞭如何積極減少應用的內存使用量。如需瞭解 Android 操作系統如何管理內存,請參閱 Android 內存管理概覽。

監控可用內存和內存使用量

您需要先找到應用中的內存使用問題,然後才能修復問題。Android Studio 中的內存性能剖析器可以通過以下方式幫助您查找和診斷內存問題:

瞭解您的應用在一段時間內如何分配內存。內存分析器可以顯示實時圖表,說明應用的內存使用量、分配的 Java 對象數量以及垃圾回收事件發生的時間。
發起垃圾回收事件,並在應用運行時拍攝 Java 堆的快照。
記錄應用的內存分配情況,然後檢查所有分配的對象、查看每個分配的堆棧軌跡,並在 Android Studio 編輯器中跳轉到相應代碼。

釋放內存以響應事件
如 Android 內存管理概覽中所述,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;
            }
        }
    }

Android 4.0(API 級別 14)中添加了 onTrimMemory() 回調。對於早期版本,您可以使用 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 類和代碼結構所使用的內存往往多於其他功能、類和結構。您可以在代碼中選擇效率更高的替代方案,以儘可能降低應用的內存使用量。

謹慎使用服務
在不需要某項服務時讓其保持運行狀態,是 Android 應用可能犯下的最嚴重的內存管理錯誤之一。如果您的應用需要某項服務在後臺執行工作,請不要讓其保持運行狀態,除非其需要運行作業。請注意在服務完成任務後使其停止運行。否則,您可能會在無意中導致內存泄漏。

在您啓動某項服務後,系統更傾向於讓此服務的進程始終保持運行狀態。這種行爲會導致服務進程代價十分高昂,因爲一旦服務使用了某部分 RAM,那麼這部分 RAM 就不再可供其他進程使用。這會減少系統可以在 LRU 緩存中保留的緩存進程數量,從而降低應用切換效率。當內存緊張,並且系統無法維護足夠的進程以託管當前運行的所有服務時,這甚至可能導致系統出現抖動。

您通常應該避免使用持久性服務,因爲它們會對可用內存提出持續性的要求。我們建議您採用 JobSchedulerJobScheduler 等替代實現方式。要詳細瞭解如何使用 JobScheduler 調度後臺進程,請參閱後臺優化。

如果您必須使用某項服務,則限制此服務的生命週期的最佳方式是使用 IntentService,它會在處理完啓動它的 intent 後立即自行結束。有關詳情,請參閱在後臺服務中運行。

使用經過優化的數據容器
編程語言所提供的部分類並未針對移動設備做出優化。例如,常規 HashMap 實現的內存效率可能十分低下,因爲每個映射都需要分別對應一個單獨的條目對象。

Android 框架包含幾個經過優化的數據容器,包括 SparseArray、SparseBooleanArray 和 LongSparseArray。 例如,SparseArray 類的效率更高,因爲它們可以避免系統需要對鍵(有時還對值)進行自動裝箱(這會爲每個條目分別再創建 1-2 個對象)。

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

謹慎對待代碼抽象
開發者往往會將抽象簡單地當做一種良好的編程做法,因爲抽象可以提高代碼靈活性和維護性。不過,抽象的代價很高:通常它們需要更多的代碼才能執行,需要更多的時間和更多的 RAM 才能將代碼映射到內存中。因此,如果抽象沒有帶來顯著的好處,您就應該避免使用抽象。

針對序列化數據使用精簡版 Protobuf
協議緩衝區是 Google 設計的一種無關乎語言和平臺,並且可擴展的機制,用於對結構化數據進行序列化。該機制與 XML 類似,但更小、更快也更簡單。如果您決定針對數據使用 Protobuf,則應始終在客戶端代碼中使用精簡版 Protobuf。常規 Protobuf 會生成極其冗長的代碼,這會導致應用出現多種問題,例如 RAM 使用量增多、APK 大小顯著增加以及執行速度變慢。

有關詳情,請參閱 Protobuf 自述文件中的“精簡版”部分。

避免內存抖動
如前所述,垃圾回收事件通常不會影響應用的性能。不過,如果在短時間內發生許多垃圾回收事件,就可能會快速耗盡幀時間。系統花在垃圾回收上的時間越多,能夠花在呈現或流式傳輸音頻等其他任務上的時間就越少。

通常,“內存抖動”可能會導致出現大量的垃圾回收事件。實際上,內存抖動可以說明在給定時間內出現的已分配臨時對象的數量。

例如,您可以在 for 循環中分配多個臨時對象。或者,您也可以在視圖的 onDraw() 函數中創建新的 Paint 或 Bitmap 對象。在這兩種情況下,應用都會快速創建大量對象。這些操作可以快速消耗新生代 (young generation) 區域中的所有可用內存,從而迫使垃圾回收事件發生。

當然,您必須先在代碼中找到內存抖動較高的位置,然後才能進行修復。爲此,您應該使用 Android Studio 中的內存分析器。

確定代碼中的問題區域後,請嘗試減少對性能至關重要的區域中的分配數量。您可以考慮將某些代碼邏輯從內部循環中移出,或將其移到基於 Factory 的分配結構中。

移除會佔用大量內存的資源和庫

代碼中的某些資源和庫可能會在您不知情的情況下吞噬內存。APK 的總體大小(包括第三方庫或嵌入式資源)可能會影響應用的內存消耗量。您可以通過從代碼中移除任何冗餘、不必要或臃腫的組件、資源或庫,降低應用的內存消耗量。

縮減總體 APK 大小
您可以通過縮減應用的總體大小來顯著降低應用的內存使用量。位圖大小、資源、動畫幀數和第三方庫都會影響 APK 的大小。Android Studio 和 Android SDK 提供了可幫助您縮減資源和外部依賴項大小的多種工具。這些工具支持現代代碼收縮方法,例如 R8 編譯。(Android Studio 3.3 及更低版本使用 ProGuard,而不是 R8 編譯。)

要詳細瞭解如何縮減 APK 的總體大小,請參閱有關如何縮減應用大小的指南。

使用 Dagger 2 實現依賴注入
依賴注入框架可以簡化您編寫的代碼,並提供一個可供您進行測試及其他配置更改的自適應環境。

如果您打算在應用中使用依賴注入框架,請考慮使用 Dagger 2。Dagger 不使用反射來掃描您應用的代碼。Dagger 的靜態編譯時實現意味着它可以在 Android 應用中使用,而不會帶來不必要的運行時代價或內存消耗量。

其他使用反射的依賴注入框架傾向於通過掃描代碼中的註釋來初始化進程。此過程可能需要更多的 CPU 週期和 RAM,並可能在應用啓動時導致出現明顯的延遲。

謹慎使用外部庫
外部庫代碼通常不是針對移動環境編寫的,在移動客戶端上運行時可能效率低下。如果您決定使用外部庫,則可能需要針對移動設備優化該庫。在決定是否使用該庫之前,請提前規劃,並在代碼大小和 RAM 消耗量方面對庫進行分析。

即使是一些針對移動設備進行了優化的庫,也可能因實現方式不同而導致問題。例如,一個庫可能使用的是精簡版 Protobuf,而另一個庫使用的是 Micro Protobuf,導致您的應用出現兩種不同的 Protobuf 實現。日誌記錄、分析、圖像加載框架和緩存以及許多您意料之外的其他功能的不同實現都可能導致這種情況。

雖然 ProGuard 可以使用適當的標記移除 API 和資源,但無法移除庫的大型內部依賴項。您所需要的這些庫中的功能可能需要較低級別的依賴項。如果存在以下情況,這就特別容易導致出現問題:您使用某個庫中的 Activity 子類(往往會有大量的依賴項)、庫使用反射(這很常見,意味着您需要花費大量的時間手動調整 ProGuard 以使其運行)等。

此外,請避免僅針對數十個功能中的一兩個功能使用共享庫。您一定不希望產生大量您甚至根本用不到的代碼和開銷。在考慮是否使用某個庫時,請查找與您的需求十分契合的實現。否則,您可以決定自己去創建實現。

本文翻譯整理自Google說明文檔《管理應用內存》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章