使用精簡程序維持背景工作

許多應用程式在 UI 不再顯示時,仍會執行合法工作,例如下載大型資產包、同步處理本機資料庫、串流音訊、記錄運動訓練,或維護連結裝置工作階段。如果這項背景工作與前景 UI 和圖形引擎在同一個 OS 程序中執行,整個程序會與 UI 分配的所有記憶體一起保持在較高的重要性層級 (oom_score_adj 介於 0 和 250 之間)。

本頁面說明這類做法為何會對應用程式和裝置其他部分造成負面影響、如何使用 android:process 將背景工作移至專屬的輕量程序,以及如何使用 adb shell 和 Perfetto 驗證記憶體節省量。

為什麼單一程序工作會保留 UI 記憶體

Android 管理記憶體和終止程序時,是以 OS 程序 (pid) 為單位,而非個別元件 (Activity、Service 或 BroadcastReceiver)。Activity Manager (OomAdjuster) 會為每個程序指派單一 oom_score_adj 值,反映該程序中最重要的活動元件或傳入繫結:

  • 當使用者與 Activity 互動時,程序會在 TOP 狀態 (oom_score_adj = 0) 下執行。
  • 當使用者離開,且應用程式沒有任何執行中的背景元件時,程序會移至 PREV (oom_score_adj = 700) 或 CACHED (oom_score_adj >= 900) 值區。在快取值區中,平台會使用 cgroup v2 凍結器凍結程序,使用 CachedAppOptimizer 壓縮記憶體,並讓 lmkd 在記憶體壓力下優先終止程序。
  • 不過,如果同一個程序也執行前景服務 (例如 oom_score_adj = 200 的 dataSync、mediaPlayback、connectedDevice 或 location 類型),或託管系統或其他重要用戶端繫結的服務,則即使 Activity 停止,整個程序仍會維持較高的優先順序。
比較單一程序共置 (將 UI 記憶體固定在 oom_score_adj 200) 與分割程序隔離 (僅將 worker 保持在 oom_score_adj 200,而 UI 程序則降至快取)。
圖 1. 背景工作期間,單一程序共置與分割程序隔離的比較。

當繁重的 UI 程序停留在 oom_score_adj = 200 時,記憶體中會保留幾個大型類別:

  • 已停止 Activity 和檢視區塊階層:除非 Activity 明確完成,否則當使用者在其他應用程式中時,其 Window、檢視區塊樹狀結構、版面配置結構和檢視區塊狀態仍可在 Java 堆積上存取。
  • 原生圖形內容和 GPU 分配:標準 HardwareRenderer 會保留其 GPU 內容和快取,而停止的 Activity 則會存在於未快取的程序中。建立自己的 Vulkan (VkDevice、VkSwapchainKHR、描述元集區、管道快取) 或 OpenGL ES (EGLContext、紋理、頂點緩衝區) 內容的應用程式和遊戲引擎,也會保留其驅動程式結構、著色器程式和圖形緩衝區,除非引擎在停止時明確拆除圖形裝置。
  • 堆積片段和執行階段負擔:即使應用程式在 onTrimMemory 中釋放快取,部分填入的原生配置器頁面、髒 Zygote 寫入時複製頁面、載入的共用程式庫 (.so) 和執行階段中繼資料仍會保留在程序的私人記憶體中。由於處理程序高於快取門檻 (oom_score_adj < 900),平台不會凍結處理程序,也不會對其執行快取應用程式壓縮。如要進一步瞭解繫結服務的這項效果,請參閱「將繫結服務與記憶體密集型 UI 放在同一位置」。

精簡的背景程序如何保護應用程式

在背景狀態下保留前景大小的記憶體,會使背景工作面臨風險:

  • 背景記憶體限制比前景限制更嚴格。在 Android 17 以上版本中,如果應用程式沒有任何程序顯示可見 UI (包括前景服務執行時),記憶體限制器會對應用程式套用較小的不可見記憶體限制。這項限制涵蓋應用程式的所有程序。如果執行前景服務的程序仍保留 UI 和圖形記憶體,該記憶體會計入較小的限制。如果應用程式超出限制,核心會限制應用程式,同時回收其頁面,導致下載、同步或播放速度變慢。如果應用程式在交換空間用盡後仍繼續分配,系統就會終止該應用程式。如要確認應用程式是否受到影響,請參閱「應用程式記憶體限制」。
  • 記憶體不足時,系統會先終止較大的程序。當 lmkd 必須釋出記憶體,且設定為選擇重要性值區 (ro.lmk.kill_heaviest_task) 中最繁重的程序時,系統會先終止同樣保留數百 MB UI 記憶體的前景服務程序,再終止重要性相同但較小的程序,而背景工作也會隨之停止。請參閱「kswapd 和 lmkd 互動」。
  • 快取的 UI 狀態可以凍結、壓縮,並寫回快閃記憶體。允許前景 UI 狀態移至快取程序,可讓 OS 凍結程序、將記憶體壓縮至 zRAM,並將冷頁面寫回快閃儲存空間。其常駐記憶體用量會縮減,而 UI 狀態則保持不變,讓背景工作和快取的 UI 都有最大機會留存到使用者返回。
  • 每個程序的記憶體預算變得實用。只執行背景工作的程序具有可預測的小型工作集,因此您可以為其提供嚴格的記憶體預算,而不會限制 UI。

執行第二個程序可能會增加應用程式的總記憶體用量,因為第二個執行階段的基準成本較低。只有在單一程序轉換至背景工作時,能完全釋出所有 UI 和圖形狀態,這項比較才成立,但應用程式和 UI 工具包在實務上無法可靠地做到這一點。讓 OS 凍結、壓縮及寫回獨立的快取 UI 程序,通常會使用較少的實體記憶體,讓應用程式保持在不可見的限制範圍內,並減少應用程式和整個裝置的記憶體不足終止情形。

在專屬程序中隔離背景工作

如要在背景工作期間消除 UI 和圖形常駐,請在專屬的輕量程序中執行長時間執行的背景元件,同時將 UI 和圖形引擎保留在主程序中:

程序 資訊清單宣告 託管元件 使用者在背景工作期間離開 UI 時的狀態
主要 UI 程序 預設值 (com.example.app) Activity、UI 檢視畫面、圖片快取、Vulkan 或 OpenGL ES 轉譯器、音訊和場景引擎 降至 PREV,然後降至 CACHED (oom_score_adj >= 900)。凍結、壓縮並寫回快閃記憶體。
背景工作站程序 android:process=":bg" 前景 Service、背景資產下載工具、同步處理工作站或連結裝置管理工具 在 FGS (oom_score_adj = 200) 執行,只有 worker 自己的堆積和程式碼,沒有 UI 檢視畫面或 GPU 情境。

宣告程序和記憶體預算

在 AndroidManifest.xml 中設定 android:process=":bg",將背景 Service 指派給私人子程序。開頭的半形冒號 (:) 會建立一個私人程序,範圍限定為應用程式的 UID。

在 Android 17 QPR2 (API 級別 37.2) 以上版本中,請在 <processes> 區塊內,將程序分割與個別程序的應用程式記憶體預算結合,讓作業系統對背景工作站強制執行嚴格的工作集上限,而不限制前景 UI 程序:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:name=".HeavyApplication"
        android:label="@string/app_name">

        <!-- App-wide budgets that cover every process, including :bg -->
        <memory-budget android:maxMb="512" />
        <memory-budget
            android:maxMb="128"
            android:state="background" />

        <processes>
            <!-- Tight budget for the headless background worker process -->
            <process android:process=":bg">
                <memory-budget android:maxMb="32" />
            </process>
        </processes>

        <activity
            android:name=".MainActivity"
            android:exported="true" />

        <service
            android:name=".AssetDownloadService"
            android:process=":bg"
            android:foregroundServiceType="dataSync"
            android:exported="false" />

    </application>
</manifest>

:bg 中的記憶體用量會同時計入自身預算和應用程式層級預算。在搭載 Android 17 QPR2 以下版本的裝置上,系統會忽略 <memory-budget>,而 android:process=":bg" 則適用於所有 API 級別。

在 Application.onCreate 中初始化閘道

Android 會例項化 Application 類別,並在為套件產生的「每個」程序中呼叫 Application.onCreate。採用 android:process=":bg" 時,常見的錯誤是將繁重的 UI 工具包、圖片載入集區、Analytics SDK 或原生引擎初始化 (System.loadLibrary) 留在未受保護的 Application.onCreate 方法中。這麼做會使 :bg 程序膨脹,產生您想避免的相同基準足跡。

在 onCreate 中檢查 Application.getProcessName (適用於 Android 9 (API 級別 28) 以上版本),並僅初始化目前程序所需的依附元件:

Kotlin

class HeavyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val processName = Application.getProcessName()
        if (processName != null && processName.endsWith(BG_PROCESS_SUFFIX)) {
            initBackgroundWorkerProcess()
            return
        }
        initMainUiProcess()
    }

    private fun initBackgroundWorkerProcess() {
        // Initialize only lightweight HTTP, storage, and sync primitives.
        // Avoid loading native rendering libraries or allocating UI caches.
    }

    private fun initMainUiProcess() {
        // Initialize UI frameworks, image caches, and native engines.
        System.loadLibrary("heavy_engine_jni")
    }

    companion object {
        private const val BG_PROCESS_SUFFIX = ":bg"
    }
}

Java

public final class HeavyApplication extends Application {
    private static final String BG_PROCESS_SUFFIX = ":bg";

    @Override
    public void onCreate() {
        super.onCreate();
        String processName = Application.getProcessName();
        if (processName != null && processName.endsWith(BG_PROCESS_SUFFIX)) {
            initBackgroundWorkerProcess();
            return;
        }
        initMainUiProcess();
    }

    private void initBackgroundWorkerProcess() {
        // Initialize only lightweight HTTP, storage, and sync primitives.
        // Avoid loading native rendering libraries or allocating UI caches.
    }

    private void initMainUiProcess() {
        // Initialize UI frameworks, image caches, and native engines.
        System.loadLibrary("heavy_engine_jni");
    }
}

透過內容供應器初始化的程式庫 (例如 Jetpack App Startup),只會在主要程序中初始化,因為未宣告 android:process 的內容供應器會在主要程序中執行。如果 :bg 中的程式碼依附於這類程式庫,請在 initBackgroundWorkerProcess 中明確初始化。

如果您使用 Unity 等遊戲引擎建構,請在以 Java 或 Kotlin 編寫、不參照 UnityPlayer 等引擎類別的一般 Android 程式庫或外掛程式中實作背景服務。:bg 程序不會載入引擎原生資料庫。如要確認,請檢查 :bg 程序的 adb shell cat /proc/<pid>/maps 是否未列出 libunity.so 或 libil2cpp.so。

不固定 UI 程序,直接進行通訊

當使用者開啟 MainActivity,而 AssetDownloadService 在 :bg 中執行時,UI 通常需要顯示即時下載進度。程序重要性會從用戶端流向託管服務或內容供應器的程序,因此每個依附元件的方向都很重要:

  • 與 worker 的 UI 繫結是安全的。當 MainActivity 繫結至 AssetDownloadService 時,系統會將 :bg 提升至可見 UI 的重要性。使用者離開後,UI 程序會快取,繫結也不會再提升任何內容。
  • 依賴 UI 程序的工作站並不安全。如果 :bg 中的程式碼繫結至服務,或查詢在主程序中執行的內容供應器,系統會將主程序提升至工作站的提升重要性,且 UI 記憶體會再次固定。請改為讓 UI 向 worker 註冊回呼,例如 Messenger 或 AIDL 回呼介面。將繫結器物件做為回呼傳遞,不會改變任一程序的重要性。

請先取消註冊回呼,再取消繫結,這樣系統凍結 worker 後,worker 就不會呼叫 UI 程序。以下範例只會在 UI 顯示時繫結至 worker:

Kotlin

class MainActivity : Activity() {
    private var downloadService: IBinder? = null

    private val downloadConnection = object : ServiceConnection {
        override fun onServiceConnected(
                name: ComponentName,
                service: IBinder
        ) {
            downloadService = service
            // Register a Messenger or AIDL callback for progress updates.
        }

        override fun onServiceDisconnected(name: ComponentName) {
            // The binding stays active and reconnects if the worker restarts.
            downloadService = null
        }
    }

    override fun onStart() {
        super.onStart()
        val intent = Intent(this, AssetDownloadService::class.java)
        bindService(intent, downloadConnection, Context.BIND_AUTO_CREATE)
    }

    override fun onStop() {
        super.onStop()
        if (downloadService != null) {
            // Unregister the progress callback here, before unbinding.
            downloadService = null
        }
        // Unbind the ServiceConnection even if onServiceConnected hasn't run
        // yet.
        unbindService(downloadConnection)
    }
}

Java

public final class MainActivity extends Activity {
    private IBinder downloadService;

    private final ServiceConnection downloadConnection =
            new ServiceConnection() {
                @Override
                public void onServiceConnected(
                        ComponentName name, IBinder service) {
                    downloadService = service;
                    // Register a Messenger or AIDL callback for progress
                    // updates.
                }

                @Override
                public void onServiceDisconnected(ComponentName name) {
                    // The binding stays active and reconnects if the worker
                    // restarts.
                    downloadService = null;
                }
            };

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, AssetDownloadService.class);
        bindService(intent, downloadConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (downloadService != null) {
            // Unregister the progress callback here, before unbinding.
            downloadService = null;
        }
        // Unbind the ServiceConnection even if onServiceConnected hasn't run
        // yet.
        unbindService(downloadConnection);
    }
}

如要進一步瞭解繫結旗標如何影響跨程序的 oom_score_adj,請參閱「服務繫結和程序狀態」。

安全地協調共用的磁碟狀態

由於主要程序和 :bg 在不同的位址空間中執行,且具有不同的執行階段和檔案描述元:

  • SQLite 和 Room:使用預寫記錄 (大多數裝置上 Room 的預設日誌模式),讓兩個程序可以同時讀取及寫入資料庫。請勿從 :bg 呼叫 enableMultiInstanceInvalidation。Room 會在主要程序中託管其撤銷服務,因此 worker 會繫結至主要程序,並將 UI 記憶體保留在 worker 的重要性提升。而是讓 worker 透過 UI 註冊的回呼回報變更,並讓 UI 在啟動時重新查詢資料庫。
  • 偏好設定:請勿跨程序共用 SharedPreferences。請在多程序程式碼中使用 DataStore,或透過 IPC 明確傳遞狀態。
  • 資產檔案:將下載的資產包寫入 :bg 中的暫存檔案,在寫入後呼叫 :bg 中的 fdatasync,然後呼叫 posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED) (如「將記憶體頁面傳回 OS」所示),並以不可分割的方式將檔案重新命名。讓 UI 在下次啟動並繫結至 worker 時擷取結果,而不是呼叫可能凍結的 UI 程序。

在 ADB 殼層中觀察效果

您可以前往主畫面,然後檢查 Activity Manager 程序狀態和 dumpsys meminfo,確認分割前景和背景程序是否能隔離升高的記憶體用量。本節的範例輸出內容來自 MemoryLab 範例應用程式,該應用程式在兩種設定的 UI 程序中執行相同的 64 MB 原生分配,並在 :remote 程序中託管分割的前景服務。

1. 比較程序狀態和 OOM 分數

啟動背景工作負載,按下主畫面按鈕,讓 Activity 停止,然後查詢 Activity Manager:

adb shell dumpsys activity processes com.example.app | grep -E "Proc #|oom:|state:"

在單一程序架構中,即使 Activity 停止,主要程序仍會維持提升狀態。使用者離開後約 15 秒,Activity Manager 會保留在 oom_score_adj = 50 (fg-service-act) 執行前景服務的程序,然後將其降至 200 (fg-service)。按下主畫面按鈕 20 秒後,系統會擷取下列輸出內容:

    Proc #1: prcp     F/S/FGS  ---NFUATI  t: 0 13794:com.android.memorylab/u0a284 (fg-service)
        oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
        state: cur=FGS  set=FGS   lastRss=0.00 lastCachedRss=0.00

在分割程序架構中,只有 worker 程序會停留在 oom_score_adj = 200,而主要 UI 程序會降至 PREV (oom_score_adj = 700),之後再降至快取值區 (oom_score_adj >= 900):

    Proc #3: prcp     F/S/FGS  ---NFUATI  t: 0 13544:com.android.memorylab:remote/u0a284 (fg-service)
        oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
        state: cur=FGS  set=FGS   lastRss=0.00 lastCachedRss=0.00
    Proc #1: prev     b/ /LAST -------TI  t: 0 13312:com.android.memorylab/u0a284 (previous)
        oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
        state: cur=LAST set=LAST  lastRss=0.00 lastCachedRss=0.00

前一個應用程式逾時後,主要程序會移至快取值區 (cch、oom_score_adj = 900、previous-expired),並失去所有程序功能 (---------),包括隱含 CPU 作業時間 (I)。此時,系統會凍結該程序,並可隨時壓縮或回收,而不會中斷工作站程序。

2. 比較不同程序的 dumpsys meminfo

比較權限提升工作站的 dumpsys meminfo 摘要與 UI 程序:

adb shell dumpsys meminfo com.example.app:bg
adb shell dumpsys meminfo com.example.app

oom_score_adj = 200 處 MemoryLab worker 程序 App Summary 輸出內容範例:

 App Summary
                       Pss(KB)                        Rss(KB)
                        ------                         ------
           Java Heap:      380                          13840
         Native Heap:     1548                           2832
                Code:      132                          55816
               Stack:      200                            208
            Graphics:        0                              0
       Private Other:      496
              System:     1926
             Unknown:                                    4168

           TOTAL PSS:     4682            TOTAL RSS:    76864       TOTAL SWAP PSS:      441

oom_score_adj = 700 上的 MemoryLab UI 程序範例 App Summary 輸出,其中保留 Private Other 中的 64 MB 原生分配:

 App Summary
                       Pss(KB)                        Rss(KB)
                        ------                         ------
           Java Heap:     1316                          19816
         Native Heap:    30408                          31596
                Code:     3148                         115796
               Stack:      656                            664
            Graphics:        0                              0
       Private Other:    67044
              System:    13687
             Unknown:                                   71092

           TOTAL PSS:   116259            TOTAL RSS:   238964       TOTAL SWAP PSS:      259

工作站程序需要小於 5 MB 的 PSS,因為它與 Zygote 共用大部分的程式碼和執行階段頁面。在以 Vulkan 或 OpenGL ES 算繪的應用程式中,UI 程序的 Graphics 列也會包含 GPU 記憶體。這個範例是在 Cuttlefish 虛擬裝置上擷取,並使用軟體算繪,因此兩個程序中的 Graphics 都是 0。

3. 檢查個別程序 cgroup 帳戶

在 UID 下,每個程序都有自己的 cgroup v2 目錄,位於應用程式的父項 cgroup 內。下列指令會列出應用程式每個程序的 oom_score_adj、memory.current 和應用程式層級 memory.high:

adb shell '
  for PID in $(pidof com.example.app com.example.app:bg); do
    CG=/sys/fs/cgroup$(sed -n "s/^0:://p" /proc/$PID/cgroup)
    echo "pid=$PID ($(tr -d "\0" < /proc/$PID/cmdline))" \
      "adj=$(cat /proc/$PID/oom_score_adj)" \
      "memory.current=$(cat $CG/memory.current)" \
      "app.memory.high=$(cat ${CG%/*}/memory.high)"
  done
'

按下「主畫面」後,MemoryLab 的輸出內容範例:

pid=15301 (com.android.memorylab) adj=700 memory.current=103260160 app.memory.high=966787072
pid=16518 (com.android.memorylab:remote) adj=200 memory.current=3608576 app.memory.high=966787072

工作站程序收費不到 4 MB,而 UI 程序收費約 98 MB。每個程序都有自己的 memory.current,但記憶體限制器會對包含應用程式所有程序的父項 cgroup 設定限制。兩行都顯示相同的 app.memory.high (966,787,072 位元組),也就是下列輸出內容中未顯示的 922 MB 限制。

如要查看記憶體限制器在裝置上套用的可見和不可見限制,請執行下列指令:

adb shell am memory-limiter status

以下是 RAM 為 4 GB 的裝置輸出範例,其中不可見限制為可見限制的一半:

Memory limiter
  enabled                  limits=true              killing=true             ignored=none
  visible:                 memHigh=1843MB           memMax=2048MB            swapMax=1024MB
  not-visible:             memHigh=922MB            memMax=1024MB            swapMax=1024MB

在 Perfetto 中觀察效果

Perfetto 可讓您以視覺化方式呈現 oom_score_adj 和 RSS 在兩個程序中的時間軸,當使用者從前景 UI 移至主畫面時,背景工作仍會繼續。

1. 錄製追蹤記錄

在應用程式中啟動背景工作並前往主畫面時,記錄 15 秒的追蹤記錄,擷取程序記憶體計數器、GPU 記憶體和 oom_score_adj 變更:

adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/process-splitting.perfetto-trace <<'EOF'
buffers: {
  size_kb: 32768
  fill_policy: DISCARD
}
data_sources: {
  config {
    name: "linux.process_stats"
    process_stats_config {
      scan_all_processes_on_start: true
      proc_stats_poll_ms: 250
    }
  }
}
data_sources: {
  config {
    name: "android.gpu.memory"
  }
}
data_sources: {
  config {
    name: "linux.ftrace"
    ftrace_config {
      ftrace_events: "oom/oom_score_adj_update"
      ftrace_events: "gpu_mem/gpu_mem_total"
    }
  }
}
duration_ms: 15000
EOF
adb pull /data/misc/perfetto-traces/process-splitting.perfetto-trace

2. 在 Perfetto UI 中檢查程序追蹤

在 Perfetto UI 中開啟追蹤記錄,並展開 com.example.app 和 com.example.app:bg 的程序群組:

  1. 固定這兩個程序的 oom_score_adj 和 mem.rss.anon 追蹤記錄,讓兩者並排顯示在時間軸頂端。
  2. 找出使用者按下主畫面按鈕的時間戳記:
    • 在 worker 程序中,oom_score_adj 保持在 200,mem.rss.anon 則保持平穩且較小。
    • 在 UI 程序中,oom_score_adj 從 0 升至 200,在 Activity 停止時停留約一秒,然後升至 700 (PREV)。在先前應用程式逾時後,它會移至快取範圍,系統可以在不影響工作站的情況下壓縮並回收其記憶體。
Perfetto 追蹤 UI 程序和 worker 程序。UI 程序 oom_score_adj 在主畫面後從 0 升至 200,然後升至 700,而工作站程序則維持在 200,匿名 RSS 約為 19 MB。
圖 2. 使用者按下主畫面按鈕後,分割程序。

在圖 2 中,UI 程序會短暫停留在 200,而 Activity 則會停止,然後上升至 700,而 worker 則會停留在 200,並有約 19 MB 的匿名 RSS。相較之下,圖 3 顯示共置設定的 35 秒追蹤記錄,其中單一程序會停留在 50,然後停留在 200,並保留所有 UI 記憶體。

Perfetto 追蹤單一共置程序。oom_score_adj 在 Home 之後從 0 升至 50,並在大約 15 秒後升至 200,而匿名 RSS 則維持在高位。
圖 3. 使用者按下主畫面按鈕後,系統會執行共置程序。

如要探索這些檢視畫面,請在 Perfetto UI 中開啟分割追蹤記錄和共置追蹤記錄。

3. 使用 PerfettoSQL 查詢記憶體用量偏高的問題

在「Query (SQL)」分頁中執行下列 PerfettoSQL 查詢,測量應用程式各程序在每個 oom_score_adj 層級保留的平均匿名和檔案 RSS:

INCLUDE PERFETTO MODULE android.memory.process;

SELECT
  process_name,
  score AS oom_score_adj,
  ROUND(SUM(dur) / 1e9, 2) AS duration_sec,
  ROUND(SUM(anon_rss * dur) / SUM(dur) / 1048576.0, 1) AS avg_anon_mb,
  ROUND(SUM(file_rss * dur) / SUM(dur) / 1048576.0, 1) AS avg_file_mb
FROM memory_oom_score_with_rss_and_swap_per_process
WHERE process_name GLOB 'com.example.app*'
GROUP BY process_name, score
ORDER BY process_name, score;

圖 2 分割追蹤記錄的輸出內容範例:

process_name                  oom_score_adj  duration_sec  avg_anon_mb  avg_file_mb
com.android.memorylab                     0          4.11         93.3        110.4
com.android.memorylab                   200          1.06        126.8        110.5
com.android.memorylab                   700          9.72        115.1        110.5
com.android.memorylab:remote              0          0.01         NULL         NULL
com.android.memorylab:remote            200         12.87         18.9         53.7

圖 3 顯示共置追蹤記錄的輸出內容範例:

process_name           oom_score_adj  duration_sec  avg_anon_mb  avg_file_mb
com.android.memorylab              0          4.10         96.0        110.4
com.android.memorylab             50         16.04        117.6        110.5
com.android.memorylab            200         14.68        116.5        110.6

在分割設定中,第一秒後只有約 19 MB 的匿名記憶體保留在 oom_score_adj = 200。在共置設定中,只要前景服務執行,約 117 MB 的記憶體就會保留在 50,然後保留在 200。


← 將記憶體頁面傳回 OS | ↑ 向上