Android13凍結進程分析:如何提高設備性能和用戶體驗

来源:https://www.cnblogs.com/webers/archive/2023/08/09/android_freezer.html
-Advertisement-
Play Games

本文介紹了Android13中的凍結進程功能,它是一種重要的資源管理策略,可以提高系統性能和穩定性,同時最大限度地節省設備的資源和電池消耗。 文章討論瞭如何合理分配資源,包括CPU、記憶體等,以提高設備性能和用戶體驗。此外,文章還提到了凍結進程對應用程式線程的影響,並介紹了Android13與Andr... ...


Android13凍結進程分析:如何提高設備性能和用戶體驗

本文介紹了Android13中的凍結進程功能,它是一種重要的資源管理策略,可以提高系統性能和穩定性,同時最大限度地節省設備的資源和電池消耗。
文章討論瞭如何合理分配資源,包括CPU、記憶體等,以提高設備性能和用戶體驗。此外,文章還提到了凍結進程對應用程式線程的影響,並介紹了Android13與Android11的不同之處。

目前,Google 原生系統在 Android 11 或更高版本上支持 CACHE 應用的 CPU 凍結功能。當應用切換到後臺並且沒有其他活動時,系統會在一定時間內通過狀態判斷,將進程 ID 遷移到凍結的 cgroup 節點上,實現凍結 CACHE 應用。這項功能可以減少活躍緩存應用在後臺存在時所消耗的 CPU 資源,從而達到節電的目的。當應用再次切換到前臺時,系統會將該應用的進程解凍,以實現快速啟動。

Google 在這方面的開發進展相對緩慢,從 Android 11 到現在的 Android 13,該功能基本上還只是個雛形。然而,在國內廠商方面,早就已經開始開發了類似功能,利用 CPU 調頻、降速、凍結等基礎功能,許多廠商在 Android 6.0 及以上版本已經支持了這些功能。
CPU 凍結對於手持設備而言可以提升整機性能。由於電量和 CPU 資源都是有限的,必須合理地分配資源,以便讓用戶在使用設備時獲得更好的體驗。

基於 SourceCodeTrace 項目推崇的原則,本文代碼塊引用均有來源,SourceCodeTrace Project 幫助您在博客、文章記錄的過程中,引入對應項目以及版本,行號等信息,讓後續的讀者,通過引用來源,能夠進行更加深入的學習,在博客或文章中引入代碼塊時,儘量提供代碼的來源信息。

凍結進程的作用

  1. 進程的執行被暫停:凍結的進程會被暫停,其所有線程的執行將被停止,包括應用程式的主線程以及任何後臺線程。
  2. 資源釋放:凍結進程占用的資源,例如 CPU 和記憶體,會被釋放。這些資源將被系統重新分配給其他需要執行的進程或系統服務。
  3. 電池節省:凍結進程不會在後臺運行,因此可以節省設備的電池消耗。對於後臺的應用程式,凍結可以降低其電池使用量,延長設備的電池壽命。
  4. 系統穩定性:通過凍結不活躍或低優先順序的進程,可以避免它們競爭系統資源,從而提高系統的穩定性和響應能力。
  5. 快速恢復:凍結的進程可以快速恢復其執行狀態。當需要重新激活進程時,系統可以迅速將其恢復到之前的運行狀態,而無需重新啟動或載入應用程式。

凍結進程並不會終止進程的執行或銷毀應用程式。凍結只是暫時掛起進程,以優化資源使用。一旦系統需要再次運行該進程(例如用戶重新打開應用程式或系統需要其提供服務),它會被解凍並恢復運行。
凍結進程是 Android 系統中重要的資源管理策略之一,它有助於提高系統性能和穩定性,同時最大限度地節省設備的資源和電池消耗。

掛起進程的方式

CPU凍結是指將應用的進程掛起,不再分配CPU資源,以達到省電的目的,當應用再次切換到前臺的時候,系統會將該應用的進程解凍,以達到快速啟動的目的。

通過信號暫停進程

在Unix系統上也有一些任務控制信號(Job-Control-Signals),比如我們經常查殺應用 使用 kill -9 其實就是發送一個 SIGKILL=9 的信號給進程,保證進程被乾凈的清理。
通過發送 SIGSTOP 信號也能將進程狀態切換為暫停(pause),進程只有在收到 SIGCONT 信號時才會恢復執行。
但這個兩個信號存在一個問題,SIGCONT 信號可以被執行的進程監聽捕獲到,並用來做進程自有的邏輯,導致任務的暫停和恢復變得不可控。

echo $$ # 查看當前進程ID
16644
bash # 在 1664 進程裡面 Fork 一個子進程 bash
echo $$ # 查看當前進程ID
16690 # 子進程ID

# 在另一個終端上發送信號。
kill -SIGSTOP 16690 # 發送信號任務給進程ID16690暫停
kill -SIGCONT 16690 # 發送信號任務給進程ID16690恢復

# 這樣操作發送信號任務給進程ID16690退出,導致父進程 16644也同樣被退出。

cgroup-v1/freezer-subsystem.txt

信號這塊其實有很多使用場景,比如分析應用卡頓和性能相關問題時,我們有時候需要主動觸發應用來輸出堆棧,便於我們深入分析問題。
即通過 kill -3 {進程id} 來實現,停止進程並觸發進程指定的信號處理函數創建核心轉儲文件(dump file),後續會再分析。

通過cgroup freezer子系統

為了防止進程能夠捕獲到信號,然後對凍結行為進行干擾,比如應用保活行為裡面常見的雙進程互拉就是通過兩個進程相互監聽來實現保活的。

通過 cgroup 的 freezer子系統功能 就能在進程無感知的情況下,將進程凍結。

在Android11之前,大部分的廠商也是通過移植cgroup的freezer來實現的,freezer是Linux內核中用於控制和限制進程組的一種特性,它可以對進程組進行資源控制,包括CPU、記憶體、IO等。
freezer cgroup 通過將進程的狀態設置為 FROZEN 來實現凍結,這樣進程就不會再被調度,也不會再消耗CPU資源,直到進程的狀態被設置為 THAWED 為止。

開發者模式下開啟應用凍結

adb shell device_config put activity_manager_native_boot use_freezer true && adb reboot

檢查測試應用是否被凍結

被凍結的應用會被分配到對應的 cgroup 組,不同設備的節點文件也不一致,根據設備實際操作:

  • 比如檢查 /dev/freezer/frozen/cgroup.procs 中的進程ID列表,如下是我常用的命令:
adb shell cat /dev/freezer/frozen/cgroup.procs | grep -E "[0-9]{4}" | xargs adb shell ps -A | grep "com"
  • 檢查是否存在 /sys/fs/cgroup/uid_0/cgroup.freeze 文件。
adb shell cat /sys/fs/cgroup/uid_{應用UID}/cgroup.freeze
  • 查看日誌(一般日誌會有相應含義的字元,各家廠商也不一樣,這裡是個例子)
adb logcat | grep -i "\(freezing\|froze\)"

進程狀態

標準狀態

下麵這個圖表,是一個標準的進程狀態的定義:

標號 中文名 英文名 解釋
D 不可中斷的睡眠態 UNINTERRUPTIBLE SLEEP 進程正在跟硬體交互,並且交互過程不允許被其他進程或中斷打斷。通常出現在I/O阻塞
R 運行態 RUNNING OR RUNNABLE 表示進程在 CPU 的就緒隊列中,正在運行或者正在等待運行。
S 可中斷的睡眠態 INTERRUPTIBLE SLEEP 進程因為等待某個事件而被系統掛起。當進程等待的事件發生時,它會被喚醒併進入 R 狀態。
T 被跟蹤或已停止 STOOPED 表示進程處於暫停或者跟蹤狀態(命令行調試)
Z 僵屍態 ZOMBIE 進程實際上已經結束了,但是父進程還沒有回收它的資源(比如進程的描述符、PID 等)。
I 空閑狀態 IDLE 也就是空閑狀態,用在不可中斷睡眠的內核線程上。D 狀態的進程會導致平均負載升高, I 狀態的進程卻不會。
X 死亡狀態 DEAD 用TOP、PS命令獲取不到

下述這段代碼是引用特斯拉開源的Linux源碼,也是符合定義的標準的。

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
  */
  static const char * const task_state_array[] = {
  "R (running)",		/*   0 */
  "S (sleeping)",		/*   1 */
  "D (disk sleep)",	/*   2 */
  "T (stopped)",		/*   4 */
  "t (tracing stop)",	/*   8 */
  "X (dead)",		/*  16 */
  "Z (zombie)",		/*  32 */
  };

s/proc/array.c#L116-L124

CPU凍結後的進程狀態

如果應用處於被凍結的狀態,我們可以通過 adb shell ps -A 命令來查看進程的狀態,如果進程的狀態是 D 狀態,那麼就說明進程被凍結了。

對於一些做內核反調試的設備,有的也會來改這種狀態,比如將進程的狀態改成 X 狀態,這樣就能夠防止被調試器 attach 到進程上, 將所有進程狀態 t 改成 S狀態,讓應用無法捕獲到進程被調試,這塊也是可以展開討論的地方。

在手機設備中,當一個應用長期在後臺並且使用不頻繁,比如 com.tencent.wework 符合凍結條件後,進程的狀態從 S 狀態變成 D 狀態,這個時候進程就會被凍結,不會再被調度,也不會再消耗CPU資源,直到進程的狀態被設置為 THAWED 為止。

比如在內核版本 4.14(Linux localhost 4.14.186 #1 SMP PREEMPT Wed Apr 26 09:09:04 UTC 2023 aarch64)中,被凍結的應用顯示為 D 狀態。

adb shell ps -A | grep "D "
#USER            PID   PPID     VSZ    RSS WCHAN            ADDR S NAME
#system          646      1 11640900 44436 0                   0 D surfaceflinger
#root           1263      2       0      0 0                   0 D [kworker/u16:15]
#root           6635      2       0      0 0                   0 D [kworker/u16:5]
#u0_a193       15586    540 15865212 177720 0                  0 D com.tencent.wework

在不同版本上,隨著CPU的凍結這塊功能的不斷迭代,在高版本中已經不會對進程切到 D 狀態,而是保留 S 態,然後在 S 態的基礎上做 freeze 的處理。
比如常見的是,在進程被凍結之後,進程還是處於 S 態,但是會在內核函數 _refrigerator 或者 do_freezer_trap 上等待。

比如在內核版本 5.4 (Linux localhost 5.4.210 #4 SMP PREEMPT Tue Jul 11 03:50:44 UTC 2023 aarch64 Toybox ) 中,D 狀態以及不顯示了,會在 WCHAN 中顯示 do_freezer_trap.

root           108     2       0      0 ion_heap_deferred_free 0 S [ion_system_heap]
root           300     2       0      0 ion_heap_deferred_free 0 S [carveout_oemcry]
root           301     2       0      0 ion_heap_deferred_free 0 S [carveout_fd]
u0_a131       3915   683 15570584 164928 do_freezer_trap    0 S com.kuangxiangciweimao.novel
u0_a132       3951   683 15276424 100604 do_freezer_trap    0 S com.dianshijia.tvlive
u0_a125       4279   684 2124872 262268 do_freezer_trap     0 S com.fenqile.fenqile
u0_a123       4780   683 15050296 185368 do_freezer_trap    0 S com.qihoo.loan
u0_a130       5089   683 34064940 278784 do_freezer_trap    0 S com.edu24ol.newclass
u0_a130       5190   683 15091696 124916 do_freezer_trap    0 S com.edu24ol.newclass:configService
u0_a130       5263   683 15171540 131684 do_freezer_trap    0 S com.edu24ol.newclass:pushservice
u0_a130       5317   683 15519316 117572 do_freezer_trap    0 S com.edu24ol.newclass:core
u0_a126       5506   683 15589072 241948 do_freezer_trap    0 S cn.com.gjzq.yjb2
u0_a127       5587   684 1986720 121372 do_freezer_trap     0 S me.ele.crowdsource

::: tip
WCHAN 表示當前線程在內核上正在執行的函數名

wchan      WCHAN     name of the kernel function in which the process
                     is sleeping, a "-" if the process is running, or
                     a "*" if the process is multi-threaded and ps is
                     not displaying threads.

:::

凍結的流程

Android13 的變更

函數 freezeProcess 是最終調用, 凍結的流程,為了詳細瞭解這兩年版本的變化,這裡直接對比 Android 11的凍結邏輯,然後和 Android 13 最新的凍結邏輯做一個比對。
Android 11 和 13 的變化如下圖,左邊是11 右邊是13:
image

Android11上通過直接調用Process.setProcessFrozen來凍結進程,而 在Android 13上通過 ActivityManagerService 拿到進程更多的信息來做細分的凍結,而不是之前版本上的直接調用API,
通過 @GuardedBy({"mAm"}) 註釋指明瞭這個函數在被調用時需要持有mAm對象的鎖, 確保在多線程環境下訪問freezeProcess函數的安全性。
Android 13 針對binder 調用進行了優化,對進程的 binder 先進行凍結 freezeBinder,在此之前,如果隨意的凍結應用,會導致一些應用後臺的跨進程行為異常。

Google 最近幾年逐步將 cgroup 上的功能,在上層做了一層封裝,這樣就可以通過上層的調用來實現CPU、記憶體資源的控制。
CPU凍結這一塊,上層主要封裝在是通 Process.java類的兩個 native 函數中, 如下是這塊主要的兩個函數:

    /**
     * Freeze or unfreeze the specified process.
     *
     * @param pid Identifier of the process to freeze or unfreeze.
     * @param uid Identifier of the user the process is running under.
     * @param frozen Specify whether to free (true) or unfreeze (false).
     *
     * @hide
     */
    public static final native void setProcessFrozen(int pid, int uid, boolean frozen);

    /**
     * Enable or disable the freezer. When enable == false all frozen processes are unfrozen,
     * but aren't removed from the freezer. While in this state, processes can be added or removed
     * by using setProcessFrozen, but they won't actually be frozen until the freezer is enabled
     * again. If enable == true the freezer is enabled again, and all processes
     * in the freezer (including the ones added while the freezer was disabled) are frozen.
     *
     * @param enable Specify whether to enable (true) or disable (false) the freezer.
     *
     * @hide
     */
    public static final native void enableFreezer(boolean enable);

/dev/src/frameworks/base/core/java/android/os/Process.java?#L1097-L1119

setProcessFrozen, 這個函數是針對單個進程的凍結。
enableFreezer(Android 13 已經廢棄), 針對所有應用的解凍,之前的實現是將整個freezer 功能開關控制,在 Android13 上 enableFreezer 方法放在了 CachedAppOptimizer 類中,上層通過 AM 實現讀取應用狀態批量處理進程。

Native函數 setProcessFrozen

下麵以 native 函數 setProcessFrozen 介紹凍結的流程, 因為 Android 系統所規劃的路線,我的理解是儘量將內核上的變化,然後規整到 AOSP 的架構裡面,上層通過簡單的介面調用,實現對整個設備資源進行劃分。
通過簡單的配置一個

void android_os_Process_setProcessFrozen(
        JNIEnv *env, jobject clazz, jint pid, jint uid, jboolean freeze)
{
    bool success = true;

    if (freeze) {
        success = SetProcessProfiles(uid, pid, {"Frozen"});
    } else {
        success = SetProcessProfiles(uid, pid, {"Unfrozen"});
    }

    if (!success) {
        signalExceptionForGroupError(env, EINVAL, pid);
    }
}

/dev/src/frameworks/base/core/jni/android_util_Process.cpp?#L282-L296

cgroup 抽象層

Android 10 及更高版本將對照組 (cgroup) 抽象層和任務配置文件搭配使用,讓開發者能夠使用它們來描述應用於某個線程或進程的一組(或多組)限制。
然後,系統按照任務配置文件的規定操作來選擇一個或多個適當的 cgroup;
通過這種方式,系統可以應用各種限制,並對底層 cgroup 功能集進行更改,而不會影響較高的軟體層。

cgroup 提供一種機制,可將任務集(包括進程、線程及其所有未來的子級)聚合併分區到具有專門行為的層級組中。Android 使用 cgroup 控制及考量 CPU 和記憶體等系統資源的使用和分配情況,並支持 Linux 內核 cgroup v1 和 cgroup v2。

libprocessgroup 中的 SetProcessProfiles 函數,這個函數是通過 TaskProfiles 類來實現的,這個類主要是通過 TaskProfiles::GetInstance() 來獲取單例對象,然後調用 SetProcessProfiles 函數來實現進程的凍結。

通過傳輸三個參數 UID, PID, "Frozen" or "Unfrozen",來實現進程的凍結和解凍。

下麵通過 SetProcessProfiles 函數來介紹凍結的流程,通過傳輸下去的 "Frozen" 字元如何最終寫入到 cgroup 中。

bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles) {
    return TaskProfiles::GetInstance().SetProcessProfiles(uid, pid, profiles, false);
}

/dev/src/system/core/libprocessgroup/processgroup.cpp?#L150-L152

TaskProfiles 類中的 SetProcessProfiles 函數,這個函數主要是通過 TaskProfile 類來實現的,
這個類主要是通過 TaskProfile::EnableResourceCaching 函數讀取配置文件,通過 TaskProfile::ExecuteForProcess 函數來實現進程的凍結。

bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid,
                                      const std::vector<std::string>& profiles, bool use_fd_cache) {
    bool success = true;
    for (const auto& name : profiles) {
        TaskProfile* profile = GetProfile(name);
        if (profile != nullptr) {
            if (use_fd_cache) {
                profile->EnableResourceCaching(ProfileAction::RCT_PROCESS);
            }
            if (!profile->ExecuteForProcess(uid, pid)) {
                PLOG(WARNING) << "Failed to apply " << name << " process profile";
                success = false;
            }
        } else {
            PLOG(WARNING) << "Failed to find " << name << " process profile";
            success = false;
        }
    }
    return success;
}

/dev/src/system/core/libprocessgroup/task_profiles.cpp?#L807-L826

對於新的架構之下,TaskProfile 類中的 EnableResourceCaching 函數,這個函數主要是通過 FdCacheHelper 類來實現的,這個類主要是通過 FdCacheHelper::Cache 函數來實現進程的凍結, 這裡主要是對文件做一個緩存,這裡文件這塊的流程就不深入分解了。

void SetCgroupAction::EnableResourceCaching(ResourceCacheType cache_type) {
    std::lock_guard<std::mutex> lock(fd_mutex_);
    // Return early to prevent unnecessary calls to controller_.Get{Tasks|Procs}FilePath() which
    // include regex evaluations
    if (fd_[cache_type] != FdCacheHelper::FDS_NOT_CACHED) {
        return;
    }
    switch (cache_type) {
        case (ProfileAction::RCT_TASK):
            FdCacheHelper::Cache(controller_.GetTasksFilePath(path_), fd_[cache_type]);
            break;
        case (ProfileAction::RCT_PROCESS):
            // uid and pid don't matter because IsAppDependentPath ensures the path doesn't use them
            FdCacheHelper::Cache(controller_.GetProcsFilePath(path_, 0, 0), fd_[cache_type]);
            break;
        default:
            LOG(ERROR) << "Invalid cache type is specified!";
            break;
    }
}

/dev/src/system/core/libprocessgroup/task_profiles.cpp?#L339-L358

task_profiles.json

task_profiles.json 文件位於 <ANDROID_BUILD_TOP>/system/core/libprocessgroup/profiles/ 下。
該文件用於描述要應用於進程或線程的一組特定操作。這組操作與一個配置文件名稱相關聯,後者在 SetTaskProfilesSetProcessProfiles 調用中用於調用配置文件操作。

  • Attributes
    • Name 欄位 - 指定 Attribute 的名稱。
    • Controller 欄位 - 按名稱引用 cgroups.json 文件中的 cgroup 控制器(目前已有的控制器包含 cpuset memory cpu blkio 等,這些也可以新增自定義)
    • File 欄位 - 為相應控制器下的特定文件命名。

task_profiles.json 文件中,在 Attributes 屬性下, 對應凍結的屬性為:"FreezerState",對應的控制器為freezer,然後最終寫的節點為 cgroup.freeze.

    {
      "Name": "FreezerState",
      "Controller": "freezer",
      "File": "cgroup.freeze"
    }

/dev/src/system/core/libprocessgroup/profiles/task_profiles.json?#L75-L79

"Frozen" 關鍵詞最終定義的地方在:

    {
      "Name": "Frozen",
      "Actions": [
        {
          "Name": "SetAttribute",
          "Params":
          {
            "Name": "FreezerState",
            "Value": "1"
          }
        }
      ]
    },
    {
      "Name": "Unfrozen",
      "Actions": [
        {
          "Name": "SetAttribute",
          "Params":
          {
            "Name": "FreezerState",
            "Value": "0"
          }
        }
      ]
    },

/dev/src/system/core/libprocessgroup/profiles/task_profiles.json?#L96-L121

cgroup 節點

如上通過配置的最終狀態,實現對 cgroup 文件節點的控制。
最終體現到的節點狀態如下:

    0 12806   0 I<     39  fg [kworker/4:0H]              kworker/4:0H    worker_thread
10135 12821   0 S      19  bg com.duowan.kiwi             com.duowan.kiwi do_freezer_trap
    0 12869   0 I<     39  fg [kbase_event]               kbase_event     rescuer_thread

通過UID 找到對應的cgroup:

λ adb shell ls /sys/fs/cgroup/uid_10135
cgroup.controllers  cgroup.max.descendants  cgroup.threads  io.pressure
cgroup.events       cgroup.procs            cgroup.type     memory.pressure
cgroup.freeze       cgroup.stat             cpu.pressure    pid_12821
cgroup.max.depth    cgroup.subtree_control  cpu.stat

凍結跨進程行為

在Android中,跨進程交互是指不同進程之間進行通信和數據交換的方式。Android提供了多種方法來實現跨進程交互,以下是其中一些常見的方法:

  1. Binder:Binder是Android中最常用的跨進程通信(IPC)機制。它是一種高效的客戶端-伺服器架構,由底層Linux內核提供支持。通過Binder,一個進程可以註冊一個服務,並暴露介面供其他進程調用。其他進程可以通過Binder代理來調用服務的介面,從而實現進程間通信。
  2. AIDL(Android Interface Definition Language):AIDL是Android Interface Definition Language的縮寫。它是一種用於定義進程間介面的IDL語言。AIDL可以幫助開發人員定義介面,並生成對應的Java類和Binder代理,使得跨進程調用變得更加簡單。
  3. ContentProvider:ContentProvider是一種Android提供的數據共用機制,用於在不同應用程式之間共用數據。其他應用程式可以通過ContentResolver來訪問和修改ContentProvider中的數據,從而實現跨進程數據共用。
  4. Messenger:Messenger是一種基於Binder的輕量級通信機制。它允許在不同進程之間傳遞簡單的Message對象,實現進程間通信。
  5. BroadcastReceiver:BroadcastReceiver是一種用於接收廣播消息的組件。通過發送廣播消息,不同進程之間可以實現簡單的通信。
  6. Intent:Intent是用於在不同組件之間傳遞消息和數據的對象。通過Intent,可以在不同進程之間啟動組件、傳遞數據和執行特定操作。
  7. Socket:Socket是一種基於網路的跨進程通信方式。通過Socket,不同進程可以建立TCP或UDP連接,進行數據交換和通信。
  8. Messenger和AIDL的結合:有時候在應用中既需要支持跨進程的非同步通信,又需要傳輸複雜的數據結構。這時可以將Messenger和AIDL結合使用,通過AIDL定義複雜數據結構的介面,然後通過Messenger來傳遞帶有這些數據結構的Message對象。

不同的跨進程交互方法適用於不同的場景和需求,模塊的開發可能選擇不同的方式實現進程間通信,凍結進程如果發生在跨進程的交互行為上,那就可能導致進程間通信的阻塞,從而導致ANR。

ContentProvider 交互異常

如果是常規的一個非同步的調用,沒有強綁定,不會讓調用阻塞,這樣的調用不會有什麼問題,但是如果是一個同步調用,比如進程 A 調用進程 B 的 ContentProvider介面,進程 B 被凍結了,這個時候進程 A 就會被掛起等待,如果沒有處理這種場景,就會有如下類似的 trace:

"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x7209f5f8 self=0xb40000701e2cd010
  | sysTid=7766 nice=-10 cgrp=default sched=0/0 handle=0x71451a54f8
  | state=S schedstat=( 2529213544 46180857 3336 ) utm=208 stm=44 core=6 HZ=100
  | stack=0x7fdff81000-0x7fdff83000 stackSize=8192KB
  | held mutexes=
  native: #00 pc 000000000009a9d4  /apex/com.android.runtime/lib64/bionic/libc.so (__ioctl+4)
  native: #01 pc 0000000000057ac4  /apex/com.android.runtime/lib64/bionic/libc.so (ioctl+160)
  native: #02 pc 00000000000519d4  /system/lib64/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+296)
  native: #03 pc 0000000000052a08  /system/lib64/libbinder.so (android::IPCThreadState::waitForResponse(android::Parcel*, int*)+128)
  native: #04 pc 0000000000052734  /system/lib64/libbinder.so (android::IPCThreadState::transact(int, unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+184)
  native: #05 pc 000000000004b00c  /system/lib64/libbinder.so (android::BpBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+152)
  native: #06 pc 0000000000123d44  /system/lib64/libandroid_runtime.so (android_os_BinderProxy_transact(_JNIEnv*, _jobject*, int, _jobject*, _jobject*, int)+152)
  at android.os.BinderProxy.transactNative(Native method)
  at android.os.BinderProxy.transact(BinderProxy.java:540)
  at android.content.ContentProviderProxy.query(ContentProviderNative.java:470)
  at android.content.ContentResolver.query(ContentResolver.java:1183)
  at android.content.ContentResolver.query(ContentResolver.java:1115)
  at android.content.ContentResolver.query(ContentResolver.java:1071)
  at com.tencent.open.utils.h.c(ProGuard:5)
  at com.tencent.open.utils.h.b(ProGuard:1)
  at com.tencent.open.utils.h.a(ProGuard:2)
  at com.tencent.open.utils.k.a(ProGuard:3)
  at com.tencent.open.utils.k.c(ProGuard:1)
  at com.tencent.tauth.Tencent.isSupportSSOLogin(ProGuard:5)
  at com.umeng.socialize.handler.UMQQSsoHandler.isInstall(UMQQSsoHandler.java:1)
  at com.umeng.socialize.a.a.a(SocialRouter.java:34)
  at com.umeng.socialize.UMShareAPI.isInstall(UMShareAPI.java:2)
  at com.toscl.*.ui.common.switchplay.SwitchVideo.initViewAndListener(SwitchVideo.java:3)
  at com.toscl.*.ui.common.switchplay.SwitchVideo.init(SwitchVideo.java:3)

onServiceConnected 交互異常

在比如下述的 ANR 阻塞在 onServiceConnected, Service 綁定傳輸信息但是對方進程被凍結導致的ANR。

"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x72c702e0 self=0xdf185410
  | sysTid=8513 nice=-10 cgrp=default sched=0/0 handle=0xecb40470
  | state=S schedstat=( 3322230460 257625780 4174 ) utm=294 stm=37 core=4 HZ=100
  | stack=0xff5c4000-0xff5c6000 stackSize=8192KB
  | held mutexes=
  native: #00 pc 00070ac0  /apex/com.android.runtime/lib/bionic/libc.so (__ioctl+8)
  native: #01 pc 0003f02f  /apex/com.android.runtime/lib/bionic/libc.so (ioctl+26)
  native: #02 pc 00039a13  /system/lib/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+238)
  native: #03 pc 0003a635  /system/lib/libbinder.so (android::IPCThreadState::waitForResponse(android::Parcel*, int*)+32)
  native: #04 pc 0003a40f  /system/lib/libbinder.so (android::IPCThreadState::transact(int, unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+122)
  native: #05 pc 00035267  /system/lib/libbinder.so (android::BpBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+98)
  native: #06 pc 000c6ae3  /system/lib/libandroid_runtime.so (android_os_BinderProxy_transact(_JNIEnv*, _jobject*, int, _jobject*, _jobject*, int)+82)
  at android.os.BinderProxy.transactNative(Native method)
  at android.os.BinderProxy.transact(BinderProxy.java:540)
  at com.jht.engine.platsign.IKypcService$Stub$Proxy.registerListener(IKypcService.java:266)
  at com.*.*.util.AiSingSound$1.onServiceConnected(AiSingSound.java:52)
  - locked <0x03d7b64e> (a com.*.*.util.AiSingSound$1)
  at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:1983)
  at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:2015)
  at android.os.Handler.handleCallback(Handler.java:938)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:223)
  at android.app.ActivityThread.main(ActivityThread.java:7745)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:612)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1009)

這種例子很多,從應用開發的角度而言,如果瞭解更多的Linux的狀態,這樣才能更好的定位問題。

對於系統開發,需要找出這些問題的根源,從而解決問題, 比如跨進程會有哪些行為,跨進程的行為狀態,通用的特征,比如不管是 ContentProvider 還是 Service 的綁定,都是通過 binder 來實現跨進程傳輸。
如果通過 binder 直接處理好是不是能解決好問題,在Android 11 之前,內核版本上,binder 驅動還沒有直接 FREEZER 狀態,
到了 Android 13 之後,binder 驅動已經支持 FREEZER 狀態,當應用調用一個被 凍結 binder 進程後,會直接返回一個錯誤, 這就不會阻塞調用方的進程。

新的API BINDER_FREEZE

相比Android 11, Android 13上會首先會凍結與進程關聯的Binder介面,以刷新可能在排隊中的Binder事務,如果有 binder 的事務需要處理,將會重新延時(預設10分鐘)在執行凍結進程邏輯。
如果在凍結 binder 介面過程中異常就會直接將應用查殺。
如果凍結成功,就會繼續執行凍結進程的邏輯,因此不會阻塞 binder 調用方的進程,這樣就不會出現上述的 ANR 問題。

                // Freeze binder interface before the process, to flush any
                // transactions that might be pending.
                try {
                    if (freezeBinder(pid, true) != 0) {
                        rescheduleFreeze(proc, "outstanding txns"); // outstanding_txns 表示是否有需要處理的事務,在下麵內核的函數分析中會有涉及。
                        return;
                    }
                } catch (RuntimeException e) {
                    Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
                    mFreezeHandler.post(() -> {
                        synchronized (mAm) {
                            proc.killLocked("Unable to freeze binder interface",
                                    ApplicationExitInfo.REASON_FREEZER,
                                    ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
                        }
                    });
                }

                long unfreezeTime = opt.getFreezeUnfreezeTime();

                try {
                    Process.setProcessFrozen(pid, proc.uid, true);

                    opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
                    opt.setFrozen(true);
                    mFrozenProcesses.put(pid, proc);
                } catch (Exception e) {
                    Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name);
                }

/dev/src/frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java?#L1705-L1733

freezeBinder 是一個 native 函數,這個函數會調用 Framework上相容 BINDER_FREEZE 的介面上。

    /**
     * Informs binder that a process is about to be frozen. If freezer is enabled on a process via
     * this method, this method will synchronously dispatch all pending transactions to the
     * specified pid. This method will not add significant latencies when unfreezing.
     * After freezing binder calls, binder will block all transaction to the frozen pid, and return
     * an error to the sending process.
     *
     * @param pid the target pid for which binder transactions are to be frozen
     * @param freeze specifies whether to flush transactions and then freeze (true) or unfreeze
     * binder for the specificed pid.
     *
     * @throws RuntimeException in case a flush/freeze operation could not complete successfully.
     * @return 0 if success, or -EAGAIN indicating there's pending transaction.
     */
    private static native int freezeBinder(int pid, boolean freeze);

/dev/src/frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java?#L739-L753

Framework相容 BINDER_FREEZE api

Framework 通過 IPCThreadState::freeze 調用到 libbinder 內:

static jint com_android_server_am_CachedAppOptimizer_freezeBinder(
        JNIEnv *env, jobject clazz, jint pid, jboolean freeze) {

    jint retVal = IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */);
    if (retVal != 0 && retVal != -EAGAIN) {
        jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
    }

    return retVal;
}

/dev/src/frameworks/base/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp?#L475-L485

這部分 native API 的設計,在2020年提交,用來解決上述的問題,調用方調用被凍結的進程後,會直接給返回異常 , 而不是持續的阻塞。

這個提交的內容如下:
::: tip
binder: adopt BINDER_FREEZE api

binder: adopt BINDER_FREEZE api
The BINDER_FREEZE ioctl and related structures have been added to the
driver to notify binder of the frozen state of a process. This patch
introduces low-level user space handling for libbinder.
Bug: 143717177
Test: verified that binder transactions to a frozen app return ERR_FROZEN
Test: verified that transactions are correctly received by non-frozen apps
Co-developed-by: Todd Kjos [email protected]
Change-Id: I31fed5ecb040f5ba5b8e27ab6a20c441964f32b4
binder: adopt BINDER_FREEZE api
:::

這是一個新增的 ioctl 用來,標記為 BINDER_FREEZE 用來操作 binder 的凍結功能。

在電腦中,ioctl(input/output control)是一個專用於設備輸入輸出操作的系統調用,該調用傳入一個跟設備有關的請求碼,系統調用的功能完全取決於請求碼。

status_t IPCThreadState::freeze(pid_t pid, bool enable, uint32_t timeout_ms) {
    struct binder_freeze_info info;
    int ret = 0;

    info.pid = pid;
    info.enable = enable;
    info.timeout_ms = timeout_ms;


#if defined(__ANDROID__)
    if (ioctl(self()->mProcess->mDriverFD, BINDER_FREEZE, &info) < 0)
        ret = -errno;
#endif

    //
    // ret==-EAGAIN indicates that transactions have not drained.
    // Call again to poll for completion.
    //
    return ret;
}

/dev/src/frameworks/native/libs/binder/IPCThreadState.cpp?#L1425-L1445

最終這個 native api 通過 ioctrl 會走到內核 binder 的驅動上。

內核新增的 BINDER_FREEZE ioctl特性

::: tip
binder: BINDER_FREEZE ioctl

binder: BINDER_FREEZE ioctl
Frozen tasks can't process binder transactions, so a way is required to
inform transmitting ends of communication failures due to the frozen
state of their receiving counterparts. Additionally, races are possible
between transitions to frozen state and binder transactions enqueued to
a specific process.
Implement BINDER_FREEZE ioctl for user space to inform the binder driver
about the intention to freeze or unfreeze a process. When the ioctl is
called, block the caller until any pending binder transactions toward
the target process are flushed. Return an error to transactions to
processes marked as frozen.
binder: BINDER_FREEZE ioctl

:::

新增了三個欄位

image

為了實現 binder 的凍結,內核上 binder 的結構體,新增了三個欄位。

	int outstanding_txns;
	bool is_dead;
	bool is_frozen;
	bool sync_recv;
	bool async_recv;
	wait_queue_head_t freeze_wait;

/drivers/android/binder_internal.h?#L414-L419

對調用方的進程凍結

這段代碼是一個在 Linux 內核中的 binder 驅動的函數處理部分。這段代碼中的函數處理了 BINDER_FREEZE 的 ioctl 請求。

	case BINDER_FREEZE: {
		struct binder_freeze_info info;
		struct binder_proc **target_procs = NULL, *target_proc;
		int target_procs_count = 0, i = 0;

		ret = 0;

		if (copy_from_user(&info, ubuf, sizeof(info))) {
			ret = -EFAULT;
			goto err;
		}

		mutex_lock(&binder_procs_lock); // 使用互斥鎖 binder_procs_lock 鎖住進程列表 binder_procs,以保護其遍歷過程。
		hlist_for_each_entry(target_proc, &binder_procs, proc_node) { //  遍歷 binder_procs 鏈表,對每個進程進行操作。
			if (target_proc->pid == info.pid)  // 如果當前遍歷到的進程的 pid 等於傳入的 info 結構體中的 pid,則 target_procs_count 增加 1,用於計數符合條件的進程數量。
				target_procs_count++;
		}

		if (target_procs_count == 0) { // 如果沒有找到匹配的進程(target_procs_count == 0),則表示傳入的 pid 無效,解鎖互斥鎖並返回 -EINVAL 錯誤。
			mutex_unlock(&binder_procs_lock);
			ret = -EINVAL;
			goto err;
		}

		target_procs = kcalloc(target_procs_count,
				       sizeof(struct binder_proc *),
				       GFP_KERNEL);  // 動態分配一個指向 struct binder_proc 指針數組的記憶體,並將其賦值給 target_procs,用於保存找到的匹配進程的指針。

		if (!target_procs) {
			mutex_unlock(&binder_procs_lock);
			ret = -ENOMEM;
			goto err;
		}

		hlist_for_each_entry(target_proc, &binder_procs, proc_node) { // 第二次遍歷
			if (target_proc->pid != info.pid)
				continue;

			binder_inner_proc_lock(target_proc);
			target_proc->tmp_ref++;
			binder_inner_proc_unlock(target_proc);

			target_procs[i++] = target_proc; // 在第二次遍歷中,找到匹配進程後,對該進程執行一系列操作:增加 tmp_ref 計數,將進程指針添加到 target_procs 數組中,並對 i 計數器進行遞增。
		}
		mutex_unlock(&binder_procs_lock);

		for (i = 0; i < target_procs_count; i++) {
			if (ret >= 0)
				ret = binder_ioctl_freeze(&info,
							  target_procs[i]);  // 對 target_procs 數組中的每個進程執行 binder_ioctl_freeze 函數,並將函數的返回值保存在 ret 變數中。同時,對每個進程執行 binder_proc_dec_tmpref 函數,遞減其 tmp_ref 計數。

			binder_proc_dec_tmpref(target_procs[i]);
		}

		kfree(target_procs);

		if (ret < 0)
			goto err;
		break;
	}

/drivers/android/binder.c?#L5439-L5498
以上的核心邏輯,是通過遍歷進程,對所有 binder 請求 的target 是當前 pid的進程,進行一個批量的處理。

更新 is_frozen 欄位

static int binder_ioctl_freeze(struct binder_freeze_info *info,
			       struct binder_proc *target_proc)
{
	int ret = 0;

	if (!info->enable) { // 如果 info->enable 的值為 0(即禁用凍結),則將目標進程的 is_frozen 欄位設置為 false,表示解凍該進程,並直接返回 0。
		binder_inner_proc_lock(target_proc);
		target_proc->sync_recv = false;
		target_proc->async_recv = false;
		target_proc->is_frozen = false;
		binder_inner_proc_unlock(target_proc);
		return 0;
	}

	/*
	 * Freezing the target. Prevent new transactions by
	 * setting frozen state. If timeout specified, wait
	 * for transactions to drain.
	 */
	binder_inner_proc_lock(target_proc);
	target_proc->sync_recv = false;
	target_proc->async_recv = false;
	target_proc->is_frozen = true; //  將目標進程的 is_frozen 欄位設置為 true,表示凍結該進程。
	binder_inner_proc_unlock(target_proc);

	if (info->timeout_ms > 0) // 如果指定了超時時間,則等待直到該進程的所有事務(transactions)處理完成,或者等待超時。這裡使用了 wait_event_interruptible_timeout 函數進行等待
		ret = wait_event_interruptible_timeout(
			target_proc->freeze_wait,
			(!target_proc->outstanding_txns),
			msecs_to_jiffies(info->timeout_ms));

/drivers/android/binder.c?#L5232-L5261

最終實現將 binder 結構體中的 is_frozen 欄位設為 true, 後續的 binder 請求,將通過判斷這個欄位,實現給調用者返回凍結狀態。

凍結的場景

進程凍結是整機性能優化中的關鍵技術之一,正確應用該技術可以顯著提升整機性能。然而,不當使用會帶來問題,如應用切換到前臺時沒有及時解凍可能出現卡頓,影響用戶體驗,因此在優化時需要慎重考慮。

在國內,應用數量眾多,存在優先順序和類別的差異。除主流應用外,其他應用占比較小。由於缺乏足夠的政策監管,國內開發者採用多種技術手段相互競爭。因此,國內手機廠商針對凍結不僅局限於 CACHE APP,而是對所有應用進行狀態模塊的拆解。例如,應用在後臺需要被凍結,但狀態模塊的判斷會考慮一些特殊情況,如應用中使用桌面小組件、正在播放音頻或涉及跨進程行為等。這樣極致地使用 CPU 凍結,大幅提升系統性能。

在國內特定環境下,一些主要應用如微信和 QQ 通常被主流廠商直接加入白名單,避免被凍結。因此,國內手機廠商針對凍結場景做了許多優化。

確定何時凍結進程其實是一個複雜的問題,需要維護一個流程圖來管理不同的場景。在本文中,我們只是簡單介紹了原生的場景。後續我們將發佈另一篇文章,專門講解國內特定場景下的優化策略。

ADJ 更新

在Android Framework中,OomAdjuster OOM資源管理是Android系統中重要的資源管理機制之一,它負責計算和調整進程的OOM(Out Of Memory)優先順序。OOM優先順序用於確定在系統記憶體不足時哪些進程會被殺死以釋放記憶體資源。OomAdjuster考慮了進程的記憶體使用情況、可見性、重要性、運行狀態和系統角色等因素,計算出每個進程的合適OOM優先順序,並將其應用於系統的資源管理。

adj(Adjustment):在Linux內核中,進程的優先順序由adj值來表示,也稱為進程優先順序值。在Android系統中,OOM優先順序通過adj值來實現。Android系統定義了一系列的adj值範圍,代表不同的OOM優先順序。例如,adj值為-17代表前臺進程,adj值為0代表可見進程,adj值為6代表服務進程等。系統根據OomAdjuster計算出的OOM優先順序來設置進程的adj值,從而確定進程在記憶體不足時被殺死的概率。

調整過程:OomAdjuster計算出進程的OOM優先順序後,會將其映射到對應的adj值範圍。然後,系統根據進程的adj值來決定哪些進程優先保留,哪些進程應該被終止以釋放記憶體。adj值越小,表示進程優先順序越高,越不容易被殺死。OomAdjuster的目的就是通過合理的計算和調整,使得系統能夠更好地管理資源,提高系統性能和穩定性。

OomAdjuster 更新 adj 的時候,會調用 updateAppFreezeStateLSP 函數用來更新應用的凍結狀態:

    private void updateAppFreezeStateLSP(ProcessRecord app, String oomAdjReason) {
        if (!mCachedAppOptimizer.useFreezer()) {   // 是否使用凍結機制
            return;
        }

        if (app.mOptRecord.isFreezeExempt()) { // 是否免凍結, 這裡追溯過去的話,目前只有判斷是否擁有安裝器許可權
            return;
        }

        final ProcessCachedOptimizerRecord opt = app.mOptRecord;
        // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
        if (opt.isFrozen() && opt.shouldNotFreeze()) {  // 是否已經凍結並且不應該凍結
            mCachedAppOptimizer.unfreezeAppLSP(app, oomAdjReason);
            return;
        }

        final ProcessStateRecord state = app.mState;
        // Use current adjustment when freezing, set adjustment when unfreezing.
        if (state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ && !opt.isFrozen( )
                && !opt.shouldNotFreeze()) {  // 當前 adj 大於最小凍結 adj 並且沒有被凍結並且應該被凍結
            mCachedAppOptimizer.freezeAppAsyncLSP(app);
        } else if (state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
            mCachedAppOptimizer.unfreezeAppLSP(app, oomAdjReason);
        }
    }

/dev/src/frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java?#L3107-L3131

通過判斷凍結功能是否開啟、應用是否屬於豁免的應用、應用是否已經被凍結、應用是否不應該被凍結。
當做完基礎的判斷之後,主要通過判斷應用當前的 adj 是否大於等於 900 (CACHE_APP) 來決定是否凍結應用,直接然後執行 freezeAppAsyncLSP 走凍結流程。

flush binder transactions 之後

上述如果凍結過程有 binder 事務需要處理的時候哦。

                try {
                    if (freezeBinder(pid, true) != 0) {
                        rescheduleFreeze(proc, "outstanding txns");
                        return;
                    }
                } catch (RuntimeException e) {
                    Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
                    mFreezeHandler.post(() -> {
                        synchronized (mAm) {
                            proc.killLocked("Unable to freeze binder interface",
                                    ApplicationExitInfo.REASON_FREEZER,
                                    ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
                        }
                    });
                }

/dev/src/frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java?#L1707-L1721

解凍的場景

低記憶體時記憶體整理

    @GuardedBy({"mService", "mProcLock"})
    private void trimMemoryUiHiddenIfNecessaryLSP(ProcessRecord app) {
        if ((app.mState.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
                || app.mState.isSystemNoUi()) && app.mProfile.hasPendingUiClean()) {
            // If this application is now in the background and it
            // had done UI, then give it the special trim level to
            // have it free UI resources.
            scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN,
                    "Trimming memory of bg-ui ");
            app.mProfile.setPendingUiClean(false);
        }
    }

/dev/src/frameworks/base/services/core/java/com/android/server/am/AppProfiler.java?#L1095-L1106

AppProfiler 中,會在 trimMemoryUiHiddenIfNecessaryLSP 函數中,判斷應用是否需要進行記憶體整理,如果需要的話,會調用 scheduleTrimMemoryLSP 函數來進行記憶體整理:

    @GuardedBy({"mService", "mProcLock"})
    private void scheduleTrimMemoryLSP(ProcessRecord app, int level, String msg) {
        IApplicationThread thread;
        if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) {
            try {
                if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
                    Slog.v(TAG_OOM_ADJ, msg + app.processName + " to " + level);
                }
                mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
                        OOM_ADJ_REASON_NONE);
                thread.scheduleTrimMemory(level);
            } catch (RemoteException e) {
            }
        }
    }

/dev/src/frameworks/base/services/core/java/com/android/server/am/AppProfiler.java?#L1108-L1123

scheduleTrimMemoryLSP 函數中,會調用 unfreezeTemporarily 函數來解凍應用,然後調用 scheduleTrimMemory 函數來進行記憶體整理。

    // This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout.
    @GuardedBy("mAm")
    void unfreezeTemporarily(ProcessRecord app, String reason) {
        if (mUseFreezer) {
            synchronized (mProcLock) {
                if (app.mOptRecord.isFrozen() || app.mOptRecord.isPendingFreeze()) {
                    unfreezeAppLSP(app, reason);
                    freezeAppAsyncLSP(app);
                }
            }
        }
    }

/dev/src/frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java?#L1036-L1047

unfreezeTemporarily 函數中,會調用 unfreezeAppLSP 函數來解凍應用,然後調用 freezeAppAsyncLSP 函數來凍結應用, freezeAppAsyncLSP 是一個非同步調用,會延遲 10 分鐘才執行實際的凍結流程。

dump進程信息時

image

在 dump 進程信息的時候,會直接調用 enableFreezer 對整個 freezer 進行關閉,這部分邏輯Android11 上是直接調用 native 函數整個關閉,Android 13上就是持有了 mAm 嗯。

在Android 11 的時候,是直接調用 native 的 enableFreezer, 而在Android13上, 在CachedAppOptimizer內進行了重寫,對應用狀態的把控更準確,廢棄了原來的enableFreezer native 函數。

    public synchronized boolean enableFreezer(boolean enable) {
        if (!mUseFreezer) {
            return false;
        }

        if (enable) {
            mFreezerDisableCount--;

            if (mFreezerDisableCount > 0) {
                return true;
            } else if (mFreezerDisableCount < 0) {
                Slog.e(TAG_AM, "unbalanced call to enableFreezer, ignoring");
                mFreezerDisableCount = 0;
                return false;
            }
        } else {
            mFreezerDisableCount++;

            if (mFreezerDisableCount > 1) {
                return true;
            }
        }

        // Override is applied immediately, restore is delayed
        synchronized (mAm) {
            synchronized (mProcLock) {
                mFreezerOverride = !enable;
                Slog.d(TAG_AM, "freezer override set to " + mFreezerOverride);

                mAm.mProcessList.forEachLruProcessesLOSP(true, process -> {
                    if (process == null) {
                        return;
                    }

                    final ProcessCachedOptimizerRecord opt = process.mOptRecord;
                    if (enable && opt.hasFreezerOverride()) {
                        freezeAppAsyncLSP(process);
                        opt.setFreezerOverride(false);
                    }

                    if (!enable && opt.isFrozen()) {
                        unfreezeAppLSP(process, OomAdjuster.OOM_ADJ_REASON_NONE);

                        // Set freezerOverride *after* calling unfreezeAppLSP (it resets the flag)
                        opt.setFreezerOverride(true);
                    }
                });
            }
        }

        return true;
    }

/dev/src/frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java?#L686-L737

發送和接收廣播臨時解凍

接收廣播臨時解凍

        if (ordered) {
            r.receiver = filter.receiverList.receiver.asBinder();
            r.curFilter = filter;
            filter.receiverList.curBroadcast = r;
            r.state = BroadcastRecord.CALL_IN_RECEIVE;
            if (filter.receiverList.app != null) {
                // Bump hosting application to no longer be in background
                // scheduling class.  Note that we can't do that if there
                // isn't an app...  but we can only be in that case for
                // things that directly call the IActivityManager API, which
                // are already core system stuff so don't matter for this.
                r.curApp = filter.receiverList.app;
                filter.receiverList.app.mReceivers.addCurReceiver(r);
                mService.enqueueOomAdjTargetLocked(r.curApp);
                mService.updateOomAdjPendingTargetsLocked(
                        OOM_ADJ_REASON_START_RECEIVER);
            }
        } else if (filter.receiverList.app != null) {
            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app,
                    OOM_ADJ_REASON_START_RECEIVER);
        }

/dev/src/frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java?#L961-L981

發送廣播臨時解凍

                    if (sendResult) {
                        if (r.callerApp != null) {
                            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
                                    r.callerApp, OOM_ADJ_REASON_FINISH_RECEIVER);
                        }
                        try {
                            if (DEBUG_BROADCAST) {
                                Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] "
                                        + r.intent.getAction() + " app=" + r.callerApp);
                            }
                            if (r.dispatchTime == 0) {
                                // The dispatch time here could be 0, in case it's a parallel
                                // broadcast but it has a result receiver. Set it to now.
                                r.dispatchTime = now;
                            }

/dev/src/frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java?#L1343-L1357

持有文件鎖解凍

        @GuardedBy({"mAm"})
        @Override
        public void onBlockingFileLock(int pid) {
            if (DEBUG_FREEZER) {
                Slog.d(TAG_AM, "Process (pid=" + pid + ") holds blocking file lock");
            }
            synchronized (mProcLock) {
                ProcessRecord app = mFrozenProcesses.get(pid);
                if (app != null) {
                    Slog.i(TAG_AM, app.processName + " (" + pid + ") holds blocking file lock");
                    unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
                }
            }
        }

/dev/src/frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java?#L1835-L1848


本文只是從軟體開發的角度,簡單梳理一下Android freeer 涉及到的模塊以及基本概念,本文也在持續的更新中,如果你需要得到最新的更新或者有一些代碼高亮,請訪問: Android13凍結進程分析:如何提高設備性能和用戶體驗


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文分享自華為雲社區《MRS大企業ERP流程實時數據湖加工最佳實踐》,作者:晉紅輕 。 本文將以ERP流程實踐為例介紹MRS實時數據湖方案的演進 案例實踐需求解析: 業務描述 AE表:會計分錄表,主要記錄財務相關信息,可用於成本核算等業務計算。為業務最主要的表,稱驅動表。 四通道表:實際為四個門店業 ...
  • 執行查詢語句,使用 $nearSphere /** * 1千米 = 0.6213712英里 15千米 = 9.3205679英里 查詢通過除以地球的大約赤道半徑(3963.2英里)將距離轉換為弧度。 * ①:如果是第一頁,查詢50公裡內的老朋友店鋪, * ②:查詢15公裡內所以的置頂服務商家,然後根 ...
  • # SQL 性能分析 ## SQL 執行頻率 MySQL 客戶端連接成功後,通過 `show [session | global] status` 命令可以提供服務其狀態信息。通過下麵指令,可以查看當前資料庫 CRUD 的訪問頻次: `SHOW GLOBAL STATUS LIKE 'Com____ ...
  • [袋鼠雲產品團隊](https://www.dtstack.com/dtinsight?src=szsm)在幫助企業進行數字化轉型實踐的過程中,發現很多企業在[數據生產鏈路](https://www.dtstack.com/dtinsight?src=szsm)上都有著相同的問題。包括數據團隊聚焦於 ...
  • ## 背景 生產上有個導報表功能,工作了很長一段時間一直都很穩,沒出現過什麼問題,最近運營同學突然反饋導出來的數據和實際的對不上,經過排查發現導出的數據有重覆,也有的沒導出來。 由於我們提前生成好數據(每天會truncate重新生成),所以導出的邏輯非常簡單,不需要關聯很多表撈數據,只需要從一張表查 ...
  • # 一、前言 原有的業務系統跑在MySQL主從架構中,高可用通過腳本完成,但存在切換數據丟失和切換不及時風險,調研了高可用更穩定的MGR後,準備入手一試。本篇文章主要記錄GreatSQL從單機擴展到MGR的詳細過程,遇到的問題及解決方法。 # 二、基礎環境 伺服器角色如下 | IP | 埠 | 主 ...
  • ![](https://img2023.cnblogs.com/blog/3076680/202308/3076680-20230807132720267-1631745639.png) # 1. 計算一年有多少天 ## 1.1. Oracle sql語句實例 ```sql select 'Days ...
  • 一、升級webview版本 (1). 下載需要更新的Webview apk。如果不能翻牆可以用下載好的版本(相容32/64位):Webview-115.0.5790.138 (2). 在路徑\aosp\external\chromium-webview\prebuilt\下替換arm或arm64架構 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...