Android 記錄系統的目標是提供普遍適用的易用功能,並假設所有記錄資料都能以字元序列表示。這項假設符合大多數用途,特別是在沒有專用工具的情況下,記錄可讀性至關重要時。不過,在需要高記錄效能和限制記錄大小的環境中,以文字為基礎的記錄可能不是最佳選擇。其中一個例子是 WindowManager,這需要強大的記錄系統,能夠處理即時視窗轉換記錄,且對系統的影響極小。
ProtoLog 是替代方案,可滿足 WindowManager 和類似服務的記錄需求。與 logcat 相比,ProtoLog 的主要優點如下:
- 記錄使用的資源量較少。
- 從開發人員的角度來看,這與使用預設的 Android 記錄架構相同。
- 支援在執行階段啟用或停用記錄陳述式。
- 如有需要,仍可記錄到 logcat。
為最佳化記憶體用量,ProtoLog 採用字串實習機制,包括計算及儲存訊息的已編譯雜湊。為提升效能,ProtoLog 會在編譯期間執行字串實習 (適用於系統服務),並在執行階段只記錄訊息 ID 和引數。此外,產生 ProtoLog 追蹤記錄或取得錯誤報告時,ProtoLog 會自動納入編譯時建立的訊息字典,因此可解碼任何建構版本的訊息。
使用 ProtoLog 時,訊息會以二進位格式 (proto) 儲存在 Perfetto 追蹤記錄中。訊息解碼作業會在 Perfetto 的 trace_processor
內部進行。這個程序包括解碼二進位 proto 訊息、使用內嵌訊息字典將訊息 ID 轉換為字串,以及使用動態引數格式化字串。
ProtoLog 支援與 android.utils.Log
相同的記錄層級,包括:d
、v
、i
、w
、e
、wtf
。
用戶端 ProtoLog
最初,ProtoLog 僅適用於 WindowManager 的伺服器端,在單一程序和元件中運作。隨後,這項功能擴大涵蓋系統 UI 程序中的 WindowManager Shell 程式碼,但 ProtoLog 使用需要複雜的樣板設定程式碼。此外,Proto 記錄僅限於系統伺服器和系統 UI 程序,因此難以納入其他程序,且每個程序都需要設定個別的記憶體緩衝區。不過,ProtoLog 現在已適用於用戶端程式碼,因此不必使用額外的樣板程式碼。
與系統服務程式碼不同,用戶端程式碼通常會略過編譯時間字串實習。而是會在背景執行緒中動態發生字串實習。因此,雖然用戶端 ProtoLog 提供的記憶體用量優勢與系統服務上的 ProtoLog 相當,但效能負擔略高,且不像伺服器端對應項目那樣具有釘選記憶體的記憶體減少優勢。
ProtoLog 群組
ProtoLog 訊息會歸類到名為 ProtoLogGroups
的群組,與 Logcat 訊息的分類方式 (依據 TAG
) 類似。這些 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());
以與
android.util.Log
相同的方式使用Protolog
: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 陳述式所在位置) 和訊息內容篩選追蹤記錄。所有資料欄都可以篩選。 按一下第一欄中的時間戳記,時間軸就會跳到該訊息的時間戳記。此外,按一下「前往目前時間」,ProtoLog 表格就會捲動回時間軸中選取的時間戳記:
圖 1. ProtoLog 檢視器