Android 記錄系統旨在提供通用的無障礙和易用性,假設所有記錄資料都能以字元序列表示。這項假設與大多數用途相符,尤其是在沒有專用工具的情況下,記錄檔的可讀性就顯得格外重要。不過,在需要高記錄效能和限制記錄大小的環境中,以文字為基礎的記錄功能可能不是最佳選擇。其中一個情境是 WindowManager,因為它需要可處理即時視窗轉換記錄的強大記錄系統,且對系統的影響要降到最低。
ProtoLog 是解決 WindowManager 和類似服務記錄需求的替代方案。ProtoLog 相較於 logcat 的主要優點如下:
- 用於記錄的資源較少。
- 從開發人員的角度來看,這與使用預設的 Android 記錄架構相同。
- 支援在執行階段啟用或停用記錄陳述式。
- 如有需要,仍可記錄至 Logcat。
為最佳化記憶體用量,ProtoLog 採用字串內部化機制,用於計算及儲存訊息的編譯雜湊。為提升效能,ProtoLog 會在編譯期間 (針對系統服務) 執行字串內部化,只在執行階段記錄訊息 ID 和引數。此外,在產生 ProtoLog 追蹤記錄或取得錯誤報告時,ProtoLog 會自動整合在編譯時建立的訊息字典,讓您從任何版本中解碼訊息。
使用 ProtoLog 時,訊息會以二進位格式 (proto) 儲存在 Perfeto 追蹤記錄中。訊息解碼作業會在 Perfetto 的 trace_processor
中進行。這個程序包括解碼二進位元 Proto 訊息、使用內嵌訊息字典將訊息 ID 轉譯為字串,以及使用動態引數設定字串格式。
ProtoLog 支援與 android.utils.Log
相同的記錄層級,也就是:d
、v
、i
、w
、e
、wtf
。
用戶端 ProtoLog
最初,ProtoLog 僅適用於 WindowManager 的伺服器端,在單一程序和元件中運作。後來,我們將其擴充至涵蓋系統 UI 處理程序中的 WindowManager 殼層程式碼,但 ProtoLog 的使用方式需要複雜的重複不變設定程式碼。此外,Proto 記錄功能僅限於系統伺服器和系統 UI 程序,因此要將其整合至其他程序相當費力,而且需要為每個程序設定個別的記憶體緩衝區。不過,ProtoLog 現在已可用於用戶端程式碼,因此不需要額外的標準程式碼。
與系統服務程式碼不同,用戶端程式碼通常會略過編譯時間字串內部化。而是在背景執行緒中動態執行字串內部化。因此,雖然用於用戶端的 ProtoLog 可提供與系統服務上的 ProtoLog 相近的記憶體用量優勢,但其效能開銷會略高,且缺乏用於伺服器端的 ProtoLog 所提供的固定記憶體縮減優勢。
ProtoLog 群組
ProtoLog 訊息會分組為 ProtoLogGroups
,類似 TAG
將 Logcat 訊息分組的方式。這些 ProtoLogGroups
是訊息的叢集,可在執行階段一併啟用或停用。此外,它們還會控制是否應在編譯期間移除訊息,以及應記錄訊息的位置 (proto、logcat 或兩者皆是)。每個 ProtoLogGroup
都包含下列屬性:
enabled
:如果設為false
,系統會在編譯期間排除這個群組中的訊息,且無法在執行階段使用。logToProto
:定義這個群組是否以二進位格式記錄。logToLogcat
:定義這個群組是否記錄至 Logcat。tag
:記錄訊息的來源名稱。
每個使用 ProtoLog 的程序都必須設定 ProtoLogGroup
例項。
支援的引數類型
在內部,ProtoLog 會使用 android.text.TextUtils#formatSimple(String, Object...)
格式化字串,因此語法相同。
ProtoLog 支援下列引數類型:
%b
- 布林值%d
、%x
- 整數型別 (short、integer 或 long)%f
- 浮點類型 (float 或 double)%s
- 字串%%
- 常值百分比字元
系統支援寬度和精確度修飾符 (例如 %04d
和 %10b
),但不支援 argument_index
和 flags
。
在新服務中使用 ProtoLog
如要在新程序中使用 ProtoLog:
為這項服務建立
ProtoLogGroup
定義。在首次使用前初始化定義 (例如在建立程序時):
Protolog.init(ProtologGroup.values());
使用
Protolog
的方式與android.util.Log
相同:ProtoLog.v(WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId);
啟用編譯時最佳化
如要在程序中啟用編譯時間 ProtoLog,您必須變更其建構規則並叫用 protologtool
二進位檔。
ProtoLogTool
是執行字串內部化並更新 ProtoLog 叫用作業的程式碼轉換二進位檔。這個二進位檔會轉換每個 ProtoLog
記錄呼叫,如以下範例所示:
ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);
轉換為:
if (ProtoLogImpl.isEnabled(GROUP_NAME)) {
int protoLogParam0 = value1;
String protoLogParam1 = String.valueOf(value2);
ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 1234560b0100, protoLogParam0, protoLogParam1);
}
在這個範例中,ProtoLog
、ProtoLogImpl
和 ProtoLogGroup
是做為引數提供的類別 (可匯入、靜態匯入或完整路徑,不允許萬用字元匯入),而 x
則是記錄方法。
轉換作業是在來源層級完成。系統會根據格式字串、記錄層級和記錄群組名稱產生雜湊碼,並插入 ProtoLogGroup 引數之後。實際產生的程式碼會內嵌,並新增多個行字元,以便保留檔案中的行號。
例子:
genrule {
name: "wm_shell_protolog_src",
srcs: [
":protolog-impl", // protolog lib
":wm_shell_protolog-groups", // protolog groups declaration
":wm_shell-sources", // source code
],
tools: ["protologtool"],
cmd: "$(location protologtool) transform-protolog-calls " +
"--protolog-class com.android.internal.protolog.ProtoLog " +
"--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
"--loggroups-jar $(location :wm_shell_protolog-groups) " +
"--viewer-config-file-path /system_ext/etc/wmshell.protolog.pb " +
"--legacy-viewer-config-file-path /system_ext/etc/wmshell.protolog.json.gz " +
"--legacy-output-file-path /data/misc/wmtrace/shell_log.winscope " +
"--output-srcjar $(out) " +
"$(locations :wm_shell-sources)",
out: ["wm_shell_protolog.srcjar"],
}
指令列選項
ProtoLog 的主要優點之一,就是您可以在執行階段啟用或停用它。舉例來說,您可以在建構中加入更多詳細的記錄資訊 (預設為停用),並在本機開發期間啟用這些資訊,以便對特定問題進行偵錯。例如,在 WindowManager 中使用此模式時,可透過群組 WM_DEBUG_WINDOW_TRANSITIONS
和 WM_DEBUG_WINDOW_TRANSITIONS_MIN
啟用不同類型的轉換記錄,其中前者會在預設情況下啟用。
您可以在啟動追蹤時,使用 Perfetto 設定 ProtoLog。您也可以使用 adb
指令列在本機設定 ProtoLog。
adb shell cmd protolog_configuration
指令支援下列引數:
help
Print this help text.
groups (list | status)
list - lists all ProtoLog groups registered with ProtoLog service"
status <group> - print the status of a ProtoLog group"
logcat (enable | disable) <group>"
enable or disable ProtoLog to logcat
有效使用訣竅
ProtoLog 會為傳遞的訊息和任何字串引數使用字串內部化。也就是說,為了讓 ProtoLog 發揮更大效益,訊息應將重複值分隔為變數。
例如,請看以下敘述:
Protolog.v(MY_GROUP, "%s", "The argument value is " + argument);
在編譯期間進行最佳化後,會轉換為以下內容:
ProtologImpl.v(MY_GROUP, 0x123, "The argument value is " + argument);
如果在程式碼中使用 ProtoLog,並搭配引數 A,B,C
:
Protolog.v(MY_GROUP, "%s", "The argument value is A");
Protolog.v(MY_GROUP, "%s", "The argument value is B");
Protolog.v(MY_GROUP, "%s", "The argument value is C");
Protolog.v(MY_GROUP, "%s", "The argument value is A");
這會導致記憶體中出現以下訊息:
Dict:
0x123: "%s"
0x111: "The argument value is A"
0x222: "The argument value is B"
0x333: "The argument value is C"
Message1 (Hash: 0x123, Arg1: 0x111)
Message2 (Hash: 0x123, Arg2: 0x222)
Message3 (Hash: 0x123, Arg3: 0x333)
Message4 (Hash: 0x123, Arg1: 0x111)
如果 ProtoLog 陳述式寫成以下形式:
Protolog.v(MY_GROUP, "The argument value is %s", argument);
記憶體緩衝區最終會變成:
Dict:
0x123: "The argument value is %s" (24 b)
0x111: "A" (1 b)
0x222: "B" (1 b)
0x333: "C" (1 b)
Message1 (Hash: 0x123, Arg1: 0x111)
Message2 (Hash: 0x123, Arg2: 0x222)
Message3 (Hash: 0x123, Arg3: 0x333)
Message4 (Hash: 0x123, Arg1: 0x111)
這個序列可讓記憶體用量減少 35%。
Winscope 檢視器
Winscope 的 ProtoLog 檢視器分頁會以表格格式顯示 ProtoLog 追蹤記錄。您可以依記錄層級、標記、來源檔案 (ProtoLog 陳述式所在位置) 和訊息內容篩選追蹤記錄。所有資料欄皆可篩選。點選第一欄中的時間戳記,即可將時間軸轉移至訊息時間戳記。此外,按一下「Go to Current Time」,ProtoLog 表格會捲動至時間軸中選取的時間戳記:
圖 1. ProtoLog 檢視器