主頁 > 前端設計 > Android記憶體管理機制官方詳解檔案

Android記憶體管理機制官方詳解檔案

2020-12-11 12:22:39 前端設計

很早之前寫過一篇《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說明檔案《管理應用記憶體》

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/233134.html

標籤:其他

上一篇:Format_Date_Simple()【將日期轉成指定的簡單格式。】

下一篇:Qt 6.0正式版2020-12-08發布

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • vue移動端上拉加載

    可能做得過于簡單或者比較low,請各位大佬留情,一起探討技術 ......

    uj5u.com 2020-09-10 04:38:07 more
  • 優美網站首頁,頂部多層導航

    一個個人用的瀏覽器首頁,可以把一下常用的網站放在這里,平常打開會比較方便。 第一步,HTML代碼 <script src=https://www.cnblogs.com/szharf/p/"js/jquery-3.4.1.min.js"></script> <div id="navigate"> <ul> <li class="labels labels_1"> ......

    uj5u.com 2020-09-10 04:38:47 more
  • 頁面為要加<!DOCTYPE html>

    最近因為寫一個js函式,需要用到$(window).height(); 由于手寫demo的時候,過于自信,其實對前端方面的認識也不夠體系,用文本檔案直接敲出來的html代碼,第一行沒有加上<!DOCTYPE html> 導致了$(window).height();的結果直接是整個document的高 ......

    uj5u.com 2020-09-10 04:38:52 more
  • WordPress網站程式手動升級要做好資料備份

    WordPress博客網站程式在進行升級前,必須要做好網站資料的備份,這個問題良家佐言是遇見過的;在剛開始接觸WordPress博客程式的時候,因為升級問題和博客網站的修改的一些嘗試,良家佐言是吃盡了苦頭。因為購買的是西部數碼的空間和域名,每當佐言把自己的WordPress博客網站搞到一塌糊涂的時候 ......

    uj5u.com 2020-09-10 04:39:30 more
  • WordPress程式不能升級為5.4.2版本的原因

    WordPress是一款個人博客系統,受到英文博客愛好者和中文博客愛好者的追捧,并逐步演化成一款內容管理系統軟體;它是使用PHP語言和MySQL資料庫開發的,用戶可以在支持PHP和MySQL資料庫的服務器上使用自己的博客。每一次WordPress程式的更新,就會牽動無數WordPress愛好者的心, ......

    uj5u.com 2020-09-10 04:39:49 more
  • 使用CSS3的偽元素進行首字母下沉和首行改變樣式

    網頁中常見的一種效果,首字改變樣式或者首行改變樣式,效果如下圖。 代碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, ......

    uj5u.com 2020-09-10 04:40:09 more
  • 關于a標簽的講解

    什么是a標簽? <a> 標簽定義超鏈接,用于從一個頁面鏈接到另一個頁面。 <a> 元素最重要的屬性是 href 屬性,它指定鏈接的目標。 a標簽的語法格式:<a href=https://www.cnblogs.com/summerxbc/p/"指定要跳轉的目標界面的鏈接">需要展示給用戶看見的內容</a> a標簽 在所有瀏覽器中,鏈接的默認外觀如下: 未被訪問的鏈接帶 ......

    uj5u.com 2020-09-10 04:40:11 more
  • 前端輪播圖

    在需要輪播的頁面是引入swiper.min.js和swiper.min.css swiper.min.js地址: 鏈接:https://pan.baidu.com/s/15Uh516YHa4CV3X-RyjEIWw 提取碼:4aks swiper.min.css地址 鏈接:https://pan.b ......

    uj5u.com 2020-09-10 04:40:13 more
  • 如何設定html中的背景圖片(全屏顯示,且不拉伸)

    1 <style>2 body{background-image:url(https://uploadbeta.com/api/pictures/random/?key=BingEverydayWallpaperPicture); 3 background-size:cover;background ......

    uj5u.com 2020-09-10 04:40:16 more
  • Java學習——HTML詳解(上)

    HTML詳解 初識HTML Hyper Text Markup Language(超文本標記語言) 1 <!--DOCTYPE:告訴瀏覽器我們要使用什么規范--> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <!--meta 描述性的標簽,描述一些 ......

    uj5u.com 2020-09-10 04:40:33 more
最新发布
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 07:59:23 more
  • 生產事故-走近科學之消失的JWT

    入職多年,面對生產環境,盡管都是小心翼翼,慎之又慎,還是難免捅出簍子。輕則滿頭大汗,面紅耳赤。重則系統停擺,損失資金。每一個生產事故的背后,都是寶貴的經驗和教訓,都是專案成員的血淚史。為了更好地防范和遏制今后的各類事故,特開此專題,長期更新和記錄大大小小的各類事故。有些是親身經歷,有些是經人耳傳口授 ......

    uj5u.com 2023-04-18 07:55:04 more
  • 記錄--Canvas實作打飛字游戲

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 打開游戲界面,看到一個畫面簡潔、卻又富有挑戰性的游戲。螢屏上,有一個白色的矩形框,里面不斷下落著各種單詞,而我需要迅速地輸入這些單詞。如果我輸入的單詞與螢屏上的單詞匹配,那么我就可以獲得得分;如果我輸入的單詞錯誤或者時間過長,那么我就會輸 ......

    uj5u.com 2023-04-04 08:35:30 more
  • 了解 HTTP 看這一篇就夠

    在學習網路之前,了解它的歷史能夠幫助我們明白為何它會發展為如今這個樣子,引發探究網路的興趣。下面的這張圖片就展示了“互聯網”誕生至今的發展歷程。 ......

    uj5u.com 2023-03-16 11:00:15 more
  • 藍牙-低功耗中心設備

    //11.開啟藍牙配接器 openBluetoothAdapter //21.開始搜索藍牙設備 startBluetoothDevicesDiscovery //31.開啟監聽搜索藍牙設備 onBluetoothDeviceFound //30.停止監聽搜索藍牙設備 offBluetoothDevi ......

    uj5u.com 2023-03-15 09:06:45 more
  • canvas畫板(滑鼠和觸摸)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>canves</title> <style> #canvas { cursor:url(../images/pen.png),crosshair; } #canvasdiv{ bo ......

    uj5u.com 2023-02-15 08:56:31 more
  • 手機端H5 實作自定義拍照界面

    手機端 H5 實作自定義拍照界面也可以使用 MediaDevices API 和 <video> 標簽來實作,和在桌面端做法基本一致。 首先,使用 MediaDevices.getUserMedia() 方法獲取攝像頭媒體流,并將其傳遞給 <video> 標簽進行渲染。 接著,使用 HTML 的 < ......

    uj5u.com 2023-01-12 07:58:22 more
  • 記錄--短視頻滑動播放在 H5 下的實作

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 短視頻已經無數不在了,但是主體還是使用 app 來承載的。本文講述 H5 如何實作 app 的視頻滑動體驗。 無聲勝有聲,一圖頂百辯,且看下圖: 網址鏈接(需在微信或者手Q中瀏覽) 從上圖可以看到,我們主要實作的功能也是本文要講解的有: ......

    uj5u.com 2023-01-04 07:29:05 more
  • 一文讀懂 HTTP/1 HTTP/2 HTTP/3

    從 1989 年萬維網(www)誕生,HTTP(HyperText Transfer Protocol)經歷了眾多版本迭代,WebSocket 也在期間萌芽。1991 年 HTTP0.9 被發明。1996 年出現了 HTTP1.0。2015 年 HTTP2 正式發布。2020 年 HTTP3 或能正... ......

    uj5u.com 2022-12-24 06:56:02 more
  • 【HTML基礎篇002】HTML之form表單超詳解

    ??一、form表單是什么

    ??二、form表單的屬性

    ??三、input中的各種Type屬性值

    ??四、標簽 ......

    uj5u.com 2022-12-18 07:17:06 more