Android event日誌列印原理
基於android 7.0程式碼,分析Event日誌的列印原理
近期準備重新梳理一遍Activity的啟動流程,其中通過Event日誌分析是一個重要的工具。
我們在除錯分析Android的過程中,經常會檢視EventLog,它非常簡潔明瞭地展現當前Activity各種狀態,當然不至於此,比如還有window、surfaceFlinger、battery等其他的資訊。
如果在終端中輸入:logcat -b events
,就能夠輸出大量類似下面的日誌資訊:
07-02 01:38:27.718 3623 3636 I am_home_stack_moved: [0,0,1,1,startingNewTask]
07 -02 01:38:27.719 3623 3636 I wm_task_moved: [6,1,0]
07-02 01:38:27.722 3623 3636 I am_create_task: [0,7]
07-02 01:38:27.722 3623 3636 I am_create_activity: [0,224793551,7,com.android.settings/.Settings,android.intent.action
.MAIN,NULL,NULL,807403520]
07-02 01:38:27.723 3623 3636 I wm_task_created: [7,1]
07-02 01 :38:27.723 3623 3636 I wm_task_moved: [7,1,1]
07-02 01:38:27.724 3623 3636 I am_pause_activity: [0,59336324,com.bbk.launcher2/.Launcher]
07-02 01:38:27.728 3623 3636 I am_home_stack_moved: [0,0,1,1,startedActivity setFocusedActivity]
07-02 01:38:27.728 3623 3636 I wm_task_moved: [7,1,1]
07-02 01:38:27.735 3623 3636 I am_focused_activity: [0,com.android.settings/.Settings]
07-02 01:38:27.746 4621 4621 I am_on_paused_called: [0,com.bbk.launcher2.Launcher]
07-02 01:38:27.751 3623 4576 I am_restart_activity: [0,224793551,7,com.android.settings/.Settings]
07-02 01:38:28.062 3623 3666 I am_activity_launch_time: [0,224793551,com.android.settings/.Settings,315,315]
通過字面意思,就能得到不少資訊量,比如am_create_activity,建立activity,但是後面括號中內容的具體含義,其實有很高的價值。既然Event日誌辣麼辣麼重要,辣麼Event日誌是如何列印的呢?
在Android原始碼中,是通過如下語句打印出event日誌的:(這裡我以ActivityStackSupervisor.java檔案說明,其它檔案方法一樣)
...
import android.util.EventLog;
...
public final class ActivityStackSupervisor implements DisplayListener {
...
void moveHomeStack(boolean toFront, String reason, ActivityStack lastFocusedStack) {
...
EventLog.writeEvent(EventLogTags.AM_HOME_STACK_MOVED,
mCurrentUser, toFront ? 1 : 0, stacks.get(topNdx).getStackId(),
mFocusedStack == null ? -1 : mFocusedStack.getStackId(), reason);
...
}
...
}
EventLog.writeEvent
android_util_EventLog_writeEvent_Array //android_util_EventLog.cpp
android_bWriteLog // system/core/include/log/Log.h
在使用上很簡單,但是其具體的實現的話,那就需要研究一下原始碼了。這裡先來看看event tag吧。
1. Event TAG
[===>frameworks/base/core/java/android/util/EventLog.java]
public class EventLog { //代替frameworks/base/core/java/android/util/EventLogTags.java檔案
/** @hide */ public EventLog() {}
private static final String TAG = "EventLog";
private static final String TAGS_FILE = "/system/etc/event-log-tags";
private static final String COMMENT_PATTERN = "^\\s*(#.*)?$";
private static final String TAG_PATTERN = "^\\s*(\\d+)\\s+(\\w+)\\s*(\\(.*\\))?\\s*$";
private static HashMap<String, Integer> sTagCodes = null;
private static HashMap<Integer, String> sTagNames = null;
/** A previously logged event read from the logs. Instances are thread safe. */
public static final class Event {
...
}
public static native int writeEvent(int tag, int value);
public static native int writeEvent(int tag, long value);
public static native int writeEvent(int tag, float value);
public static native int writeEvent(int tag, String str);
public static native int writeEvent(int tag, Object... list);
public static native void readEvents(int[] tags, Collection<Event> output)
throws IOException;
public static String getTagName(int tag) {
readTagsFile();
return sTagNames.get(tag);
}
/**
* Get the event type tag code associated with an event name.
* @param name of event to look up
* @return the tag code, or -1 if no tag has that name
*/
public static int getTagCode(String name) {
readTagsFile();
...
}
/**
* Read TAGS_FILE, populating sTagCodes and sTagNames, if not already done.
*/
private static synchronized void readTagsFile() {
if (sTagCodes != null && sTagNames != null) return;
sTagCodes = new HashMap<String, Integer>();
sTagNames = new HashMap<Integer, String>();
Pattern comment = Pattern.compile(COMMENT_PATTERN);
Pattern tag = Pattern.compile(TAG_PATTERN);
BufferedReader reader = null;
String line;
try {
reader = new BufferedReader(new FileReader(TAGS_FILE), 256);
while ((line = reader.readLine()) != null) {
if (comment.matcher(line).matches()) continue;
Matcher m = tag.matcher(line);
if (!m.matches()) {
Log.wtf(TAG, "Bad entry in " + TAGS_FILE + ": " + line);
continue;
}
try {
int num = Integer.parseInt(m.group(1));
String name = m.group(2);
sTagCodes.put(name, num);
sTagNames.put(num, name);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Error in " + TAGS_FILE + ": " + line, e);
}
}
} catch (IOException e) {
Log.wtf(TAG, "Error reading " + TAGS_FILE, e);
// Leave the maps existing but unpopulated
} finally {
try { if (reader != null) reader.close(); } catch (IOException e) {}
}
}
}
注意到public類EventLog中的所有變數、介面、內部類及內部類的所有變數、介面都是static靜態的,因此,在構造EventLog類的時候,這些static介面都會被執行一遍。
Event Tag是通過readTagsFile讀取解析/system/etc/event-log-tags
檔案內容,並將相關資訊儲存在sTagCodes、sTagNames兩個HashMap中,其中sTagNames儲存的是Tag Name。
在程式碼中,有多處地方都可以有EventLogTags.logtags檔案:
******@******:~/Google_7.0.0$ find -name "EventLogTags.logtags"
./frameworks/native/services/surfaceflinger/EventLog/EventLogTags.logtags
./frameworks/base/core/java/android/speech/tts/EventLogTags.logtags
./frameworks/base/core/java/android/content/EventLogTags.logtags
./frameworks/base/core/java/android/webkit/EventLogTags.logtags
./frameworks/base/core/java/com/android/internal/logging/EventLogTags.logtags
./frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/EventLogTags.logtags
./frameworks/base/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
./frameworks/base/services/core/java/com/android/server/EventLogTags.logtags
./frameworks/base/services/core/java/com/android/server/am/EventLogTags.logtags
./frameworks/opt/telephony/src/java/com/android/internal/telephony/EventLogTags.logtags
./system/core/libsysutils/EventLogTags.logtags
./packages/apps/Settings/src/com/android/settings/EventLogTags.logtags
./packages/apps/QuickSearchBox/src/com/android/quicksearchbox/EventLogTags.logtags
./packages/providers/CalendarProvider/src/com/android/providers/calendar/EventLogTags.logtags
./packages/providers/ContactsProvider/src/com/android/providers/contacts/EventLogTags.logtags
./packages/services/Telephony/src/com/android/phone/EventLogTags.logtags
python指令碼build/tools/java-event-log-tags.py
則負責將EventLogTags.logtags以及呼叫轉化為java檔案,或者是將java檔案中的writeEvent呼叫轉為標準的java呼叫,以及生成system/etc/event-log-tags檔案(對於使用來說,分析這個python檔案沒有多大意義)。
以frameworks/base/services/core/java/com/android/server/am/EventLogTags.logtags檔案為例,該檔案編譯過程中通過python指令碼生成的對應java檔案在out目錄中:
out/target/common/obj/JAVA_LIBRARIES/services.core_intermediates/src/com/android/server/am/EventLogTags.java
下面看看logcat -b events是怎麼讀取並顯示event log的:
int main(int argc, char **argv)
{
using namespace android;
...
for (;;) {
int ret;
...
ret = getopt_long(argc, argv, ":cdDLt:T:gG:sQf:r:n:v:b:BSpP:m:e:",
long_options, &option_index);
if (ret < 0) {
break;
}
switch (ret) {
...
case 'b': {
if (strcmp(optarg, "default") == 0) {
...
break;
}
if (strcmp(optarg, "all") == 0) {
...
break;
}
bool binary = !(strcmp(optarg, "events") &&
strcmp(optarg, "security"));
if (devices) {
dev = devices;
while (dev->next) {
if (!strcmp(optarg, dev->device)) {
dev = NULL;
break;
}
dev = dev->next;
}
if (dev) {
dev->next = new log_device_t(optarg, binary);
}
} else {
devices = new log_device_t(optarg, binary);
}
g_devCount++;
}
break;
case 'B':
g_printBinary = 1;
break;
...
default:
logcat_panic(true, "Unrecognized Option %c\n", optopt);
break;
}
}
...
if (g_logRotateSizeKBytes != 0 && g_outputFileName == NULL) {
logcat_panic(true, "-r requires -f as well\n");
}
setupOutput();
if (hasSetLogFormat == 0) {
const char* logFormat = getenv("ANDROID_PRINTF_LOG");
if (logFormat != NULL) {
err = setLogFormat(logFormat);
if (err < 0) {
fprintf(stderr, "invalid format in ANDROID_PRINTF_LOG '%s'\n",
logFormat);
}
} else {
setLogFormat("threadtime");
}
}
if (forceFilters) {
err = android_log_addFilterString(g_logformat, forceFilters);
if (err < 0) {
logcat_panic(false, "Invalid filter expression in logcat args\n");
}
} else if (argc == optind) {
// Add from environment variable
char *env_tags_orig = getenv("ANDROID_LOG_TAGS");
if (env_tags_orig != NULL) {
err = android_log_addFilterString(g_logformat, env_tags_orig);
if (err < 0) {
logcat_panic(true,
"Invalid filter expression in ANDROID_LOG_TAGS\n");
}
}
} else {
// Add from commandline
for (int i = optind ; i < argc ; i++) {
err = android_log_addFilterString(g_logformat, argv[i]);
if (err < 0) {
logcat_panic(true, "Invalid filter expression '%s'\n", argv[i]);
}
}
}
dev = devices;
if (tail_time != log_time::EPOCH) {
logger_list = android_logger_list_alloc_time(mode, tail_time, pid);
} else {
logger_list = android_logger_list_alloc(mode, tail_lines, pid);
}
const char *openDeviceFail = NULL;
const char *clearFail = NULL;
const char *setSizeFail = NULL;
const char *getSizeFail = NULL;
// We have three orthogonal actions below to clear, set log size and
// get log size. All sharing the same iteration loop.
while (dev) {
...
dev = dev->next;
}
// report any errors in the above loop and exit
if (printStatistics || getPruneList) {
size_t len = 8192;
char *buf;
for (int retry = 32;
(retry >= 0) && ((buf = new char [len]));
delete [] buf, buf = NULL, --retry) {
if (getPruneList) {
android_logger_get_prune_list(logger_list, buf, len);
} else {
android_logger_get_statistics(logger_list, buf, len);
}
buf[len-1] = '\0';
if (atol(buf) < 3) {
delete [] buf;
buf = NULL;
break;
}
size_t ret = atol(buf) + 1;
if (ret <= len) {
len = ret;
break;
}
len = ret;
}
if (!buf) {
logcat_panic(false, "failed to read data");
}
// remove trailing FF
char *cp = buf + len - 1;
*cp = '\0';
bool truncated = *--cp != '\f';
if (!truncated) {
*cp = '\0';
}
// squash out the byte count
cp = buf;
if (!truncated) {
while (isdigit(*cp)) {
++cp;
}
if (*cp == '\n') {
++cp;
}
}
printf("%s", cp);
delete [] buf;
return EXIT_SUCCESS;
}
dev = NULL;
log_device_t unexpected("unexpected", false);
while (!g_maxCount || (g_printCount < g_maxCount)) {
...
if (g_printBinary) {
...
} else {
processBuffer(dev, &log_msg);
}
}
android_logger_list_free(logger_list);
return EXIT_SUCCESS;
}
在system/core/logcat/logcat.cpp的main函式裡頭,如果引數帶-b events的話,g_printBinary為false,binary會是true,然後回去讀取event tag maps:
android::g_eventTagMap = android_openEventTagMap(EVENT_TAG_MAP_FILE);
其中是EVENT_TAG_MAP_FILE就是system/etc/event-log-tags。
ActivityManager
[===>frameworks/base/services/core/java/com/android/server/am/EventLogTags.logtags]
# See system/core/logcat/event.logtags for a description of the format of this file.
option java_package com.android.server.am
2719 configuration_changed (config mask|1|5)
2721 cpu (total|1|6),(user|1|6),(system|1|6),(iowait|1|6),(irq|1|6),(softirq|1|6)
# ActivityManagerService.systemReady() starts:
3040 boot_progress_ams_ready (time|2|3)
# ActivityManagerService calls enableScreenAfterBoot():
3050 boot_progress_enable_screen (time|2|3)
# Do not change these names without updating the checkin_events setting in
# google3/googledata/wireless/android/provisioning/gservices.config !!
#
# An activity is being finished:
30001 am_finish_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3)
# A task is being brought to the front of the screen:
30002 am_task_to_front (User|1|5),(Task|1|5)
# An existing activity is being given a new intent:
30003 am_new_intent (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
# A new task is being created:
30004 am_create_task (User|1|5),(Task ID|1|5)
# A new activity is being created in an existing task:
30005 am_create_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
# An activity has been resumed into the foreground but was not already running:
30006 am_restart_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
# An activity has been resumed and is now in the foreground:
30007 am_resume_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
# Application Not Responding
30008 am_anr (User|1|5),(pid|1|5),(Package Name|3),(Flags|1|5),(reason|3)
# Activity launch time
30009 am_activity_launch_time (User|1|5),(Token|1|5),(Component Name|3),(time|2|3)
# Application process bound to work
30010 am_proc_bound (User|1|5),(PID|1|5),(Process Name|3)
# Application process died
30011 am_proc_died (User|1|5),(PID|1|5),(Process Name|3)
# The Activity Manager failed to pause the given activity.
30012 am_failed_to_pause (User|1|5),(Token|1|5),(Wanting to pause|3),(Currently pausing|3)
# Attempting to pause the current activity
30013 am_pause_activity (User|1|5),(Token|1|5),(Component Name|3)
# Application process has been started
30014 am_proc_start (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3),(Type|3),(Component|3)
# An application process has been marked as bad
30015 am_proc_bad (User|1|5),(UID|1|5),(Process Name|3)
# An application process that was bad is now marked as good
30016 am_proc_good (User|1|5),(UID|1|5),(Process Name|3)
# Reporting to applications that memory is low
30017 am_low_memory (Num Processes|1|1)
# An activity is being destroyed:
30018 am_destroy_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3)
# An activity has been relaunched, resumed, and is now in the foreground:
30019 am_relaunch_resume_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
# An activity has been relaunched:
30020 am_relaunch_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
# The activity's onPause has been called.
30021 am_on_paused_called (User|1|5),(Component Name|3)
# The activity's onResume has been called.
30022 am_on_resume_called (User|1|5),(Component Name|3)
# Kill a process to reclaim memory.
30023 am_kill (User|1|5),(PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3)
# Discard an undelivered serialized broadcast (timeout/ANR/crash)
30024 am_broadcast_discard_filter (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(BroadcastFilter|1|5)
30025 am_broadcast_discard_app (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(App|3)
# A service is being created
30030 am_create_service (User|1|5),(Service Record|1|5),(Name|3),(UID|1|5),(PID|1|5)
# A service is being destroyed
30031 am_destroy_service (User|1|5),(Service Record|1|5),(PID|1|5)
# A process has crashed too many times, it is being cleared
30032 am_process_crashed_too_much (User|1|5),(Name|3),(PID|1|5)
# An unknown process is trying to attach to the activity manager
30033 am_drop_process (PID|1|5)
# A service has crashed too many times, it is being stopped
30034 am_service_crashed_too_much (User|1|5),(Crash Count|1|1),(Component Name|3),(PID|1|5)
# A service is going to be restarted after its process went away
30035 am_schedule_service_restart (User|1|5),(Component Name|3),(Time|2|3)
# A client was waiting for a content provider, but its process was lost
30036 am_provider_lost_process (User|1|5),(Package Name|3),(UID|1|5),(Name|3)
# The activity manager gave up on a new process taking too long to start
30037 am_process_start_timeout (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3)
# Unhandled exception
30039 am_crash (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Exception|3),(Message|3),(File|3),(Line|1|5)
# Log.wtf() called
30040 am_wtf (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Tag|3),(Message|3)
# User switched
30041 am_switch_user (id|1|5)
# Activity fully drawn time
30042 am_activity_fully_drawn_time (User|1|5),(Token|1|5),(Component Name|3),(time|2|3)
# Activity focused
30043 am_focused_activity (User|1|5),(Component Name|3)
# Home Stack brought to front or rear
30044 am_home_stack_moved (User|1|5),(To Front|1|5),(Top Stack Id|1|5),(Focused Stack Id|1|5),(Reason|3)
# Running pre boot receiver
30045 am_pre_boot (User|1|5),(Package|3)
# Report collection of global memory state
30046 am_meminfo (CachedKb|2|2),(FreeKb|2|2),(ZramKb|2|2),(KernelKb|2|2),(NativeKb|2|2)
# Report collection of memory used by a process
30047 am_pss (Pid|1|5),(UID|1|5),(Process Name|3),(PssKb|2|2),(UssKb|2|2)
system/core/logcat/event.logtags部分檔案
# The entries in this file map a sparse set of log tag numbers to tag names.
# This is installed on the device, in /system/etc, and parsed by logcat.
#
# Tag numbers are decimal integers, from 0 to 2^31. (Let's leave the
# negative values alone for now.)
#
# Tag names are one or more ASCII letters and numbers or underscores, i.e.
# "[A-Z][a-z][0-9]_". Do not include spaces or punctuation (the former
# impacts log readability, the latter makes regex searches more annoying).
#
# Tag numbers and names are separated by whitespace. Blank lines and lines
# starting with '#' are ignored.
#
# Optionally, after the tag names can be put a description for the value(s)
# of the tag. Description are in the format
# (<name>|data type[|data unit])
# Multiple values are separated by commas.
#
# The data type is a number from the following values:
# 1: int
# 2: long
# 3: string
# 4: list
# 5: float
#
# The data unit is a number taken from the following list:
# 1: Number of objects
# 2: Number of bytes
# 3: Number of milliseconds
# 4: Number of allocations
# 5: Id
# 6: Percent
# Default value for data of type int/long is 2 (bytes).
#
# TODO: generate ".java" and ".h" files with integer constants from this file.
(<name>|data type[|data unit])
data type | 資料型別 |
---|---|
1 | int |
2 | long |
3 | string |
4 | list |
5 | float |
data unit | 資料單位 |
---|---|
1 | Number of objects |
2 | Number of bytes |
3 | Number of milliseconds |
4 | Number of allocations |
5 | Id |
6 | Percent |
光這麼說,可能比較模糊,已具體的event日誌來說明。
07-02 11:23:09.712 933 1870 I am_pause_activity: [0,123715054,com.XXX.launcher2/.Launcher]
結合EventLogTags.logtags檔案中關於am_pause_activity的資訊
# Attempting to pause the current activity
30013 am_pause_activity (User|1|5),(Token|1|5),(Component Name|3)
我們可以知道該event日誌描述的是:com.XXX.launcher2/.Launcher被pause,User Id為0(int型別),Token Id為123715054(int型別),Component name為com.XXX.launcher2/.Launcher。