Logcat工具的初始化过程是从文件logcat.cpp中的函数main开始的,它会打开日志设备和解析命令行参数。这个函数的实现代码比较长,我们分段来阅读。
**system/core/logcat/logcat.cpp**
~~~
static AndroidLogFormat * g_logformat;
static bool g_nonblock = false;
static int g_tail_lines = 0;
static const char * g_outputFileName = NULL;
static int g_logRotateSizeKBytes = 0; // 0 means "no log rotation"
static int g_maxRotatedLogs = DEFAULT_MAX_ROTATED_LOGS; // 0 means "unbounded"
static int g_outFD = -1;
static off_t g_outByteCount = 0;
static int g_printBinary = 0;
static int g_devCount = 0;
static EventTagMap* g_eventTagMap = NULL;
int main(int argc, char **argv)
{
int err;
int hasSetLogFormat = 0;
......
const char *forceFilters = NULL;
log_device_t* devices = NULL;
log_device_t* dev;
bool needBinary = false;
g_logformat = android_log_format_new();
~~~
第25行调用函数android_log_format_new来创建一个全局的日志记录输出格式和输出过滤器对象g_logformat。
**system/core/liblog/logprint.c**
~~~
AndroidLogFormat *android_log_format_new()
{
AndroidLogFormat *p_ret;
p_ret = calloc(1, sizeof(AndroidLogFormat));
p_ret->global_pri = ANDROID_LOG_VERBOSE;
p_ret->format = FORMAT_BRIEF;
return p_ret;
}
~~~
从函数android_log_format_new的实现就可以看出,全局变量g_logformat指定的日志记录输出格式为FORMAT_BRIEF,而指定的全局默认日志记录过滤优先级为ANDROID_LOG_VERBOSE。
**system/core/logcat/logcat.cpp**
~~~
for (;;) {
int ret;
ret = getopt(argc, argv, "cdt:gsQf:r::n:v:b:B");
......
switch(ret) {
......
case 'd':
g_nonblock = true;
break;
case 't':
g_nonblock = true;
g_tail_lines = atoi(optarg);
break;
......
case 'b': {
char* buf = (char*) malloc(strlen(LOG_FILE_DIR) + strlen(optarg) + 1);
strcpy(buf, LOG_FILE_DIR);
strcat(buf, optarg);
bool binary = strcmp(optarg, "events") == 0;
if (binary) {
needBinary = true;
}
if (devices) {
dev = devices;
while (dev->next) {
dev = dev->next;
}
dev->next = new log_device_t(buf, binary, optarg[0]);
} else {
devices = new log_device_t(buf, binary, optarg[0]);
}
android::g_devCount++;
}
break;
case 'B':
android::g_printBinary = 1;
break;
case 'f':
// redirect output to a file
android::g_outputFileName = optarg;
break;
case 'r':
if (optarg == NULL) {
android::g_logRotateSizeKBytes
= DEFAULT_LOG_ROTATE_SIZE_KBYTES;
} else {
long logRotateSize;
char *lastDigit;
if (!isdigit(optarg[0])) {
......
}
android::g_logRotateSizeKBytes = atoi(optarg);
}
break;
case 'n':
if (!isdigit(optarg[0])) {
......
}
android::g_maxRotatedLogs = atoi(optarg);
break;
case 'v':
err = setLogFormat(optarg);
......
hasSetLogFormat = 1;
break;
......
}
}
~~~
函数第26行到第102行的for循环依次对命令行参数进行解析。命令行参数的字符串解析是通过调用函数getopt来实现的,它的返回值ret表示命令行中的一个选项,而选项对应的值保存在变量optarg中。接下来,我们就分别对命令行中的选项d、t、b、B、f、r、n和v进行介绍。
如果使用选项d来启动Logcat工具,那么函数第35行就会把全局变量g_nonblock的值设置为true,表示当Logger日志驱动程序中没有日志记录可读时,Logcat工具就直接退出。
如果使用选项t来启动Logcat工具,那么函数第39行就会将选项后面的数字保存在全局变量g_tail_lines中,同时第38行会将全局变量g_nonblock的值设置为true。其中,全局变量g_tail_lines表示Logcat工具每次在输出日志记录时,只输出最新的日志记录条数。
如果使用选项b来启动Logcat工具,那么函数第52行到第60行代码就会将选项后面的字符串取出来,并且为它创建一个log_device_t结构体,表示Logcat工具要打开的日志设备。选项后面的字符串的取值为“main”、“radio”或者 “events”,表示要打开的日志设备分别为/dev/log/main、/dev/log/radio或者/dev/log/events。当选项后面的字符串为“events”时,函数第49行会将变量needBinary的值设置为true,表示Logcat工具要解析目标设备上的/system/etc/event-log-tags文件的内容,以便可以将日志设备 /dev/log/events的内容从二进制格式转换为文本格式。每次创建一个log_device_t结构体时,全局变量g_devCount的值就会加1,表示Logcat工具所打开的日志设备个数。
> 如果在启动Logcat工具时,没有使用选项b,那么Logcat工具默认打开的日志设备就为/dev/log/main。
如果使用选项B来启动Logcat工具,那么函数第65行就会将全局变量g_printBinary的值设置为1,表示要以二进制格式来输出日志记录。这时候就不需要对日志记录的内容进行解析了。
如果使用选项f来启动Logcat工具,那么函数第70行就会将选项后面的字符串取出来,并且保存在全局变量g_outputFileName中,用作日志记录的输出文件名称。当输入到文件g_outputFileName的日志记录的大小(单位是字节)达到设定的量时,Logcat工具就会将接下来的日志记录输出到另外一个文件中,直到这个文件的日志记录的大小也达到设定的量为止。用作日志记录输出的文件的命名方式是遵循一定的规律的。例如,假设选项f后面的字符串为 “logfile”,那么第一个日志记录输出文件的名称就为“logfile”,接下来的日志记录输出文件的名称依次为“logfile.1”、“logfile.2”、“logfile.3”等。日志记录输出文件的大小由选项r来指定,而日志记录输出文件的个数由选项n来指定。当日志记录输出文件的个数达到设定的值时,并且每一个日志记录输出文件的大小也达到设定的值时,Logcat工具就会循环使用已有的日志记录输出文件,即将原来的日志记录输出文件的内容擦掉,然后将新的日志记录写入到这些文件中。
如果使用选项r来启动Logcat工具,那么函数第84行就会将选项后面的数字保存在全局变量g_logRotateSizeKBytes中,用来表示每一个日志记录输出文件的最大容量。如果该选项后面没有指定数字,那么全局变量g_logRotateSizeKBytes的值就默认设置为DEFAULT_LOG_ROTATE_SIZE_KBYTES。
**system/core/logcat/logcat.cpp**
~~~
#define DEFAULT_LOG_ROTATE_SIZE_KBYTES 16
~~~
全局变量g_logRotateSizeKBytes的值被初始化为0,表示所有的日志记录输出文件的容量没有限制,这会导致所有的日志记录都输出到文件g_outputFileName中。
如果使用选项n来启动Logcat工具,那么函数第92行就会将选项后面的数字保存在全局变量g_maxRotatedLogs中,表示日志记录输出文件的最大个数。全局变量g_maxRotateLogs的值被初始化为DEFAULT_MAX_ROTATED_LOGS。
**system/core/logcat/logcat.cpp**
~~~
#define DEFAULT_MAX_ROTATED_LOGS 16
~~~
如果使用选项v来启动Logcat工具,那么函数第95行就会调用函数setLogFormat将选项后面的字符串转换为相应的AndroidLogPrintFormat枚举值。该选项后面的字符串取值为“brief”、“process”、“tag”、“thread”、“raw”、“time”、“threadtime”或者“long”,分别对应于枚举AndroidLogPrintFormat中的每一个值。
函数setLogFormat的实现如下所示。
**system/core/logcat/logcat.cpp**
~~~
static int setLogFormat(const char * formatString)
{
static AndroidLogPrintFormat format;
format = android_log_formatFromString(formatString);
if (format == FORMAT_OFF) {
// FORMAT_OFF means invalid string
return -1;
}
android_log_setPrintFormat(g_logformat, format);
return 0;
}
~~~
第5行调用函数android_log_formatFromString将字符串formatString转换为一个AndroidLogPrintFormat枚举值。
**system/core/liblog/logprint.c**、
~~~
AndroidLogPrintFormat android_log_formatFromString(const char * formatString)
{
static AndroidLogPrintFormat format;
if (strcmp(formatString, "brief") == 0) format = FORMAT_BRIEF;
else if (strcmp(formatString, "process") == 0) format = FORMAT_PROCESS;
else if (strcmp(formatString, "tag") == 0) format = FORMAT_TAG;
else if (strcmp(formatString, "thread") == 0) format = FORMAT_THREAD;
else if (strcmp(formatString, "raw") == 0) format = FORMAT_RAW;
else if (strcmp(formatString, "time") == 0) format = FORMAT_TIME;
else if (strcmp(formatString, "threadtime") == 0) format = FORMAT_THREADTIME;
else if (strcmp(formatString, "long") == 0) format = FORMAT_LONG;
else format = FORMAT_OFF;
return format;
}
~~~
如果参数formatString是一个非法字符串,那么函数android_log_formatFromString就会将它转换为一个FORMAT_OFF值;否则,就通过第5行到第12行代码来得到正确的AndroidLogPrintFormat枚举值。
回到函数setLogFormat中,将前面得到的日志记录输出格式保存在变量format中,接着第12行继续调用函数android_log_setPrintFormat将变量format的值设置到全局变量g_logformat的成员变量format中,用来描述Logcat工具的日志记录输出格式。
**system/core/liblog/logprint.c**
~~~
void android_log_setPrintFormat(AndroidLogFormat *p_format,
AndroidLogPrintFormat format)
{
p_format->format=format;
}
~~~
回到main函数中,如果指定了选项v,并且成功地对它进行了解析,那么函数第98行就会将变量hasSetLogFormat的值设置为1,表示设置了Logcat工具的日志记录输出格式。
函数main解析完成命令行参数后,继续往下执行。
**system/core/logcat/logcat.cpp**
~~~
if (!devices) {
devices = new log_device_t(strdup("/dev/"LOGGER_LOG_MAIN), false, 'm');
android::g_devCount = 1;
int accessmode =
(mode & O_RDONLY) ? R_OK : 0
| (mode & O_WRONLY) ? W_OK : 0;
// only add this if it's available
if (0 == access("/dev/"LOGGER_LOG_SYSTEM, accessmode)) {
devices->next = new log_device_t(strdup("/dev/"LOGGER_LOG_SYSTEM), false, 's');
android::g_devCount++;
}
}
~~~
如果启动Logcat工具时,没有指定选项b,那么变量devices的值就会等于NULL。这时候Logcat工具就会默认打开日志设备/dev/log/main来读取它的日志记录。如果日志设备/dev/log/system也存在,那么Logcat工具也会一起打开它来读取日志记录。
打开要读取日志记录的日志设备之后,函数main继续往下执行。
**system/core/logcat/logcat.cpp**
~~~
android::setupOutput();
~~~
第115行调用函数setupOutput来设置日志记录是输出到文件中还是打印到标准输出中,它的实现如下所示。
**system/core/logcat/logcat.cpp**
~~~
static void setupOutput()
{
if (g_outputFileName == NULL) {
g_outFD = STDOUT_FILENO;
} else {
struct stat statbuf;
g_outFD = openLogFile(g_outputFileName);
if (g_outFD < 0) {
perror ("couldn't open output file");
exit(-1);
}
fstat(g_outFD, &statbuf);
g_outByteCount = statbuf.st_size;
}
}
~~~
第4行判断全局变量g_outputFileName的值是否等于NULL。如果是,则说明要把日志记录打印到标准输出中,因此就将全局变量g_outFD指向标准输出文件描述符STDOUT_FILENO;否则,就要将日志记录输出到文件g_outputFileName中。如果是将日志记录输出到文件中,那么函数第10行调用openLogFile函数以APPEND方式打开日志记录输出文件g_outputFileName。
**system/core/logcat/logcat.cpp**
~~~
static int openLogFile (const char *pathname)
{
return open(g_outputFileName, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
}
~~~
回到函数setupOutput中,第17行获得当前日志记录输出文件g_outputFileName的大小,并且将它保存在全局变量g_outByteCount中。接下来Logcat工具往文件g_outputFileName输出日志记录时,就会相应地增加全局变量g_outByteCount的值。当全局变量g_outByteCount的值达到g_logRotateSizeKBytes时,Logcat工具就会将后面的日志记录输出到另外一个文件中。
回到函数main中,继续往下执行。
**system/core/logcat/logcat.cpp**
~~~
if (hasSetLogFormat == 0) {
const char* logFormat = getenv("ANDROID_PRINTF_LOG");
if (logFormat != NULL) {
err = setLogFormat(logFormat);
......
}
}
~~~
如果启动Logcat工具时,没有指定选项v,那么变量hasSetLogFormat的值就会等于0,表示没有指定日志记录的输出格式。这时候Logcat工具就会检查环境变量ANDROID_PRINTF_LOG的值。如果环境变量ANDROID_PRINTF_LOG存在,那么就会调用函数setLogFormat将它的值作为Logcat工具的日志记录输出格式。
设置好日志记录的输出格式之后,函数main继续往下执行。
**system/core/logcat/logcat.cpp**
~~~
if (forceFilters) {
err = android_log_addFilterString(g_logformat, forceFilters);
......
} 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);
......
}
} else {
// Add from commandline
for (int i = optind ; i < argc ; i++) {
err = android_log_addFilterString(g_logformat, argv[i]);
......
}
}
~~~
在启动Logcat工具时,我们可以指定一个不公开的选项Q,这时候Logcat工具就会去读取目标设备上的/proc/cmdline文件,检查里面有没有设置日志记录输出过滤器。如果设置了,那么变量forceFilters就指向这个日志记录输出过滤器,因此,函数第125行就会调用函数android_log_addFilterString将它增加到Logcat工具的日志记录输出过滤器列表中。
如果启动Logcat工具时,没有指定选项Q,那么Logcat工具就会继续检查命令行是否还带有其他额外的参数。如果没有,即第127行的if语句为true,那么Logcat工具就会将环境变量ANDROID_LOG_TAGS的值作为日志记录输出的一个过滤器来使用。
如果启动Logcat工具时,没有指定选项Q,但是命令行带有额外的参数,那么函数第137行到第140行的for循环就会检查这些额外的参数是否是一个日志记录输出过滤表达式。例如,当使用下面的命令来启动Logcat工具时:
`USER@MACHINE:~/Android$ adb logcat LOGTAG:I`
最后一个参数“LOGTAG:I”就称为日志记录输出过滤表达式。日志记录输出过滤表达式的格式为“[:priority]”,其中,tag为任意字符串,表示一个日志记录标签;priority是一个字符,表示一个日志记录优先级。合法的priority字符为数字‘0’到‘9’,或者字母‘v’、‘d’、‘i’、‘w’、‘f’、‘s’和‘*’,它们的具体含义在后面会进一步解释。这时候函数第138行就会调用函数android_log_addFilterString来解析这些日志记录输出过滤表达式,并且将它增加到Logcat工具的日志记录输出过滤器列表中。
函数android_log_addFilterString的实现如下所示。
**system/core/liblog/logprint.c**
~~~
/**
* filterString: a comma/whitespace-separated set of filter expressions
*
* eg "AT:d *:i"
*
* returns 0 on success and -1 on invalid expression
*
* Assumes single threaded execution
*
*/
int android_log_addFilterString(AndroidLogFormat *p_format,
const char *filterString)
{
char *filterStringCopy = strdup (filterString);
char *p_cur = filterStringCopy;
char *p_ret;
int err;
// Yes, I'm using strsep
while (NULL != (p_ret = strsep(&p_cur, " \t,"))) {
// ignore whitespace-only entries
if(p_ret[0] != '\0') {
err = android_log_addFilterRule(p_format, p_ret);
if (err < 0) {
goto error;
}
}
}
free (filterStringCopy);
return 0;
error:
free (filterStringCopy);
return -1;
}
~~~
第二个参数filterString可以同时包括若干个日志记录输出过滤表达式,它们以空格、制表符或者逗号分割。每一个日志记录输出过滤表达式都是使用函数android_log_addFilterRule来解析的,它的实现如下所示。
**system/core/liblog/logprint.c**
~~~
/**
* filterExpression: a single filter expression
* eg "AT:d"
*
* returns 0 on success and -1 on invalid expression
*
* Assumes single threaded execution
*/
int android_log_addFilterRule(AndroidLogFormat *p_format,
const char *filterExpression)
{
size_t i=0;
size_t tagNameLength;
android_LogPriority pri = ANDROID_LOG_DEFAULT;
tagNameLength = strcspn(filterExpression, ":");
if (tagNameLength == 0) {
goto error;
}
if(filterExpression[tagNameLength] == ':') {
pri = filterCharToPri(filterExpression[tagNameLength+1]);
if (pri == ANDROID_LOG_UNKNOWN) {
goto error;
}
}
if(0 == strncmp("*", filterExpression, tagNameLength)) {
// This filter expression refers to the global filter
// The default level for this is DEBUG if the priority
// is unspecified
if (pri == ANDROID_LOG_DEFAULT) {
pri = ANDROID_LOG_DEBUG;
}
p_format->global_pri = pri;
} else {
// for filter expressions that don't refer to the global
// filter, the default is verbose if the priority is unspecified
if (pri == ANDROID_LOG_DEFAULT) {
pri = ANDROID_LOG_VERBOSE;
}
char *tagName;
// Presently HAVE_STRNDUP is never defined, so the second case is always taken
// Darwin doesn't have strnup, everything else does
#ifdef HAVE_STRNDUP
tagName = strndup(filterExpression, tagNameLength);
#else
//a few extra bytes copied...
tagName = strdup(filterExpression);
tagName[tagNameLength] = '\0';
#endif /*HAVE_STRNDUP*/
FilterInfo *p_fi = filterinfo_new(tagName, pri);
free(tagName);
p_fi->p_next = p_format->filters;
p_format->filters = p_fi;
}
return 0;
error:
return -1;
}
~~~
第17行取得日志记录输出过滤表达式中冒号的位置,接着第24行就调用函数filterCharToPri将冒号后面的字符转换为android_LogPriority枚举值。
**system/core/liblog/logprint.c**
~~~
/*
* Note: also accepts 0-9 priorities
* returns ANDROID_LOG_UNKNOWN if the character is unrecognized
*/
static android_LogPriority filterCharToPri (char c)
{
android_LogPriority pri;
c = tolower(c);
if (c >= '0' && c <= '9') {
if (c >= ('0'+ANDROID_LOG_SILENT)) {
pri = ANDROID_LOG_VERBOSE;
} else {
pri = (android_LogPriority)(c - '0');
}
} else if (c == 'v') {
pri = ANDROID_LOG_VERBOSE;
} else if (c == 'd') {
pri = ANDROID_LOG_DEBUG;
} else if (c == 'i') {
pri = ANDROID_LOG_INFO;
} else if (c == 'w') {
pri = ANDROID_LOG_WARN;
} else if (c == 'e') {
pri = ANDROID_LOG_ERROR;
} else if (c == 'f') {
pri = ANDROID_LOG_FATAL;
} else if (c == 's') {
pri = ANDROID_LOG_SILENT;
} else if (c == '*') {
pri = ANDROID_LOG_DEFAULT;
} else {
pri = ANDROID_LOG_UNKNOWN;
}
return pri;
}
~~~
从函数filterCharToPri的实现就可以看出,字符“v”、“d”、“i”、“w”、“e”、“f”、“s”和“*”对应的日志记录优先级分别为ANDROID_LOG_VERBOSE、ANDROID_LOG_DEBUG、ANDROID_LOG_INFO、ANDROID_LOG_WARN、ANDROID_LOG_ERROR、ANDROID_LOG_FATAL、ANDROID_LOG_SILENT和ANDROID_LOG_DEFAULT。此外,字符“0”到“7”对应的日志记录优先级分别为ANDROID_LOG_UNKNOWN、ANDROID_LOG_DEFAULT、ANDROID_LOG_VERBOSE、ANDROID_LOG_DEBUG、ANDROID_LOG_INFO、ANDROID_LOG_WARN、ANDROID_LOG_ERROR和ANDROID_LOG_FATAL,而字符“8”和“9”对应的日志记录优先级均为ANDROID_LOG_VERBOSE。
回到函数android_log_addFilterRule中,如果日志记录输出过滤表达式中的日志记录标签值为“*”,即第31行的if语句为true,就表示要将它的日志记录优先级设置为全局的日志记录过滤优先级;否则,就为该日志记录输出过滤表达式创建一个日志记录输出过滤器,并且增加到Logcat工具的日志记录输出过滤器列表中,如第59行到第63行代码所示。
设置好日志记录输出过滤器列表之后,回到函数main中,继续往下执行。
**system/core/logcat/logcat.cpp**
~~~
dev = devices;
while (dev) {
dev->fd = open(dev->device, mode);
......
dev = dev->next;
}
~~~
函数第143行到148行的while循环依次调用函数open来打开保存在devices列表中的各个日志设备,并且将得到的文件描述符保存在对应的日志设备的成员变量fd中。
日志设备打开之后,函数main继续往下执行。
**system/core/logcat/logcat.cpp**
~~~
if (needBinary)
android::g_eventTagMap = android_openEventTagMap(EVENT_TAG_MAP_FILE);
~~~
函数第149行判断打开的日志设备的日志记录格式是否包含二进制格式,即是否打开了日志设备/dev/log/events。如果是,即第149行的if语句为true,那么第150行就会调用函数android_openEventTagMap来解析目标设备上的/system/etc/event-log-tags文件,因为Logcat工具需要根据它的内容来解析类型为events的日志记录。函数android_openEventTagMap的返回值是一个EventTagMap结构体,保存在全局变量g_eventTagMap中。
EVENT_TAG_MAP_FILE是一个宏定义,它的值为“/system/etc/event-log-tags”,如下所示。
**system/core/include/cutils/event_tag_map.h**
`#define EVENT_TAG_MAP_FILE "/system/etc/event-log-tags"`
函数android_openEventTagMap的实现如下所示。
**system/core/liblog/event_tag_map.c**
~~~
/*
* Open the map file and allocate a structure to manage it.
*
* We create a private mapping because we want to terminate the log tag
* strings with '\0'.
*/
EventTagMap* android_openEventTagMap(const char* fileName)
{
EventTagMap* newTagMap;
off_t end;
int fd = -1;
newTagMap = calloc(1, sizeof(EventTagMap));
if (newTagMap == NULL)
return NULL;
fd = open(fileName, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "%s: unable to open map '%s': %s\n",
OUT_TAG, fileName, strerror(errno));
goto fail;
}
end = lseek(fd, 0L, SEEK_END);
(void) lseek(fd, 0L, SEEK_SET);
if (end < 0) {
fprintf(stderr, "%s: unable to seek map '%s'\n", OUT_TAG, fileName);
goto fail;
}
newTagMap->mapAddr = mmap(NULL, end, PROT_READ | PROT_WRITE, MAP_PRIVATE,
fd, 0);
if (newTagMap->mapAddr == MAP_FAILED) {
fprintf(stderr, "%s: mmap(%s) failed: %s\n",
OUT_TAG, fileName, strerror(errno));
goto fail;
}
newTagMap->mapLen = end;
if (processFile(newTagMap) != 0)
goto fail;
return newTagMap;
fail:
android_closeEventTagMap(newTagMap);
if (fd >= 0)
close(fd);
return NULL;
}
~~~
第13行首先创建一个EventTagMap结构体对象,接着第17行打开文件/system/etc/event-log-tags,第24行得到该文件的大小,第31行将该文件的内容映射到内存中,最后第41行调用函数processFile来解析文件/system/etc/event-log-tags中的内容。
**system/core/liblog/event_tag_map.c**
~~~
/*
* Crunch through the file, parsing the contents and creating a tag index.
*/
static int processFile(EventTagMap* map)
{
EventTag* tagArray = NULL;
/* get a tag count */
map->numTags = countMapLines(map);
if (map->numTags < 0)
return -1;
//printf("+++ found %d tags\n", map->numTags);
/* allocate storage for the tag index array */
map->tagArray = calloc(1, sizeof(EventTag) * map->numTags);
if (map->tagArray == NULL)
return -1;
/* parse the file, null-terminating tag strings */
if (parseMapLines(map) != 0) {
fprintf(stderr, "%s: file parse failed\n", OUT_TAG);
return -1;
}
/* sort the tags and check for duplicates */
if (sortTags(map) != 0)
return -1;
return 0;
}
~~~
第9行调用函数countMapLines来计算文件/system/etc/event-log-tags的行数,接着第16行根据这个行数来分配一个EventTag结构体数组,其中,每一行的内容都对应一个EventTag结构体。第21行调用函数parseMapLines来解析文件/system/etc/event-log-tags的内容。函数countMapLines主要是根据文件中的换行符‘\n’来计算行数,而且会忽略文件中的空白行和注释行。函数parseMapLines对文件中的每一行内容进行解析,从而得到类型为events的日志记录标签描述表。下面我们就通过一个例子来说明函数parseMapLines的工作原理。
当目标设备上的文件/system/etc/event-log-tags包含以下一行内容时:
`2722 battery_level (level|1|6),(voltage|1|1),(temperature|1|1)`
函数parseMapLines就会将字符串“2722”转换为数字2722,并且将它保存在一个EventTag结构体的成员变量tagIndex中,然后再将字符“battery_level”在内存中的地址保存在同一个EventTag结构体的成员变量tagStr中,从而将日志记录标签号2722与描述字符串“battery_level”关联起来。
函数parseMapLines执行完成之后,我们就得到了类型为events的日志记录的日志标签描述表,它保存在一个EventTagMap结构体内部的数组tagArray中。为了使后面能够根据日志标签号快速找到对应的日志标签描述字符串,函数processFile的第27行调用函数sortTags对这个数组进行排序。
**system/core/liblog/event_tag_map.c**
~~~
/*
* Sort the EventTag array so we can do fast lookups by tag index.
* the sort we do a quick check for duplicate tag indices.
*
* Returns 0 on success.
*/
static int sortTags(EventTagMap* map)
{
int i;
qsort(map->tagArray, map->numTags, sizeof(EventTag), compareEventTags);
for (i = 1; i < map->numTags; i++) {
if (map->tagArray[i].tagIndex == map->tagArray[i-1].tagIndex) {
fprintf(stderr, "%s: duplicate tag entries (%d:%s and %d:%s)\n",
OUT_TAG,
map->tagArray[i].tagIndex, map->tagArray[i].tagStr,
map->tagArray[i-1].tagIndex, map->tagArray[i-1].tagStr);
return -1;
}
}
return 0;
}
~~~
第11行调用快排函数qsort对该日志标签描述数组进行排序,指定的比较函数为compareEventTags,它的实现如下所示。
**system/core/liblog/event_tag_map.c**
~~~
/*
* Compare two EventTags.
*/
static int compareEventTags(const void* v1, const void* v2)
{
const EventTag* tag1 = (const EventTag*) v1;
const EventTag* tag2 = (const EventTag*) v2;
return tag1->tagIndex - tag2->tagIndex;
}
~~~
从这里就可以看出,日志标签描述数组的元素是按照日志标签号从小到大排列的。
回到函数sortTags中,第13行到第21行的for循环检查前面获得的日志标签描述数组的合法性,即检查它里面是否有重复的日志标签号。如果有,就会返回错误码-1,表示目标设备上的文件/system/etc/event-log-tags的内容有问题。
解析完成目标设备上的文件/system/etc/event-log-tags之后,回到函数main中,继续往下执行。
**system/core/logcat/logcat.cpp**
~~~
android::readLogLines(devices);
return 0;
}
~~~
第151行调用函数readLogLines开始读取前面打开的日志设备的日志记录。接下来,我们就分析这些日志记录的读取过程。
- 文章概述
- 下载Android源码以及查看源码
- win10 平台通过VMware Workstation安装Ubuntu
- Linux系统安装Ubuntu编译Android源码
- Eclipse快捷键大全
- 前言
- 第一篇 初识Android系统
- 第一章 准备知识
- 1.1 Linux内核参考书籍
- 1.2 Android应用程序参考书籍
- 1.3 下载、编译和运行Android源代码
- 1.3.1 下载Android源代码
- 1.3.2 编译Android源代码
- 1.3.3 运行Android模拟器
- 1.4 下载、编译和运行Android内核源代码
- 1.4.1 下载Android内核源代码
- 1.4.2 编译Android内核源代码
- 1.4.3 运行Android模拟器
- 1.5 开发第一个Android应用程序
- 1.6 单独编译和打包Android应用程序模块
- 1.6.1 导入单独编译模块的mmm命令
- 1.6.2 单独编译Android应用程序模块
- 1.6.3 重新打包Android系统镜像文件
- 第二章 硬件抽象层
- 2.1 开发Android硬件驱动程序
- 2.1.1 实现内核驱动程序模块
- 2.1.2 修改内核Kconfig文件
- 2.1.3 修改内核Makefile文件
- 2.1.4 编译内核驱动程序模块
- 2.1.5 验证内核驱动程序模块
- 2.2 开发C可执行程序验证Android硬件驱动程序
- 2.3 开发Android硬件抽象层模块
- 2.3.1 硬件抽象层模块编写规范
- 2.3.1.1 硬件抽象层模块文件命名规范
- 2.3.1.2 硬件抽象层模块结构体定义规范
- 2.3.2 编写硬件抽象层模块接口
- 2.3.3 硬件抽象层模块的加载过程
- 2.3.4 处理硬件设备访问权限问题
- 2.4 开发Android硬件访问服务
- 2.4.1 定义硬件访问服务接口
- 2.4.2 实现硬件访问服务
- 2.4.3 实现硬件访问服务的JNI方法
- 2.4.4 启动硬件访问服务
- 2.5 开发Android应用程序来使用硬件访问服务
- 第三章 智能指针
- 3.1 轻量级指针
- 3.1.1 实现原理分析
- 3.1.2 使用实例分析
- 3.2 强指针和弱指针
- 3.2.1 强指针的实现原理分析
- 3.2.2 弱指针的实现原理分析
- 3.2.3 应用实例分析
- 第二篇 Android专用驱动系统
- 第四章 Logger日志系统
- 4.1 Logger日志格式
- 4.2 Logger日志驱动程序
- 4.2.1 基础数据结构
- 4.2.2 日志设备的初始化过程
- 4.2.3 日志设备文件的打开过程
- 4.2.4 日志记录的读取过程
- 4.2.5 日志记录的写入过程
- 4.3 运行时库层日志库
- 4.4 C/C++日志写入接口
- 4.5 Java日志写入接口
- 4.6 Logcat工具分析
- 4.6.1 基础数据结构
- 4.6.2 初始化过程
- 4.6.3 日志记录的读取过程
- 4.6.4 日志记录的输出过程
- 第五章 Binder进程间通信系统
- 5.1 Binder驱动程序
- 5.1.1 基础数据结构
- 5.1.2 Binder设备的初始化过程
- 5.1.3 Binder设备文件的打开过程
- 5.1.4 设备文件内存映射过程
- 5.1.5 内核缓冲区管理
- 5.1.5.1 分配内核缓冲区
- 5.1.5.2 释放内核缓冲区
- 5.1.5.3 查询内核缓冲区
- 5.2 Binder进程间通信库
- 5.3 Binder进程间通信应用实例
- 5.4 Binder对象引用计数技术
- 5.4.1 Binder本地对象的生命周期
- 5.4.2 Binder实体对象的生命周期
- 5.4.3 Binder引用对象的生命周期
- 5.4.4 Binder代理对象的生命周期
- 5.5 Binder对象死亡通知机制
- 5.5.1 注册死亡接收通知
- 5.5.2 发送死亡接收通知
- 5.5.3 注销死亡接收通知
- 5.6 Service Manager的启动过程
- 5.6.1 打开和映射Binder设备文件
- 5.6.2 注册成为Binder上下文管理者
- 5.6.3 循环等待Client进程请求
- 5.7 Service Manager代理对象接口的获取过程
- 5.8 Service的启动过程
- 5.8.1 注册Service组件
- 5.8.1.1 封装通信数据为Parcel对象
- 5.8.1.2 发送和处理BC_TRANSACTION命令协议
- 5.8.1.3 发送和处理BR_TRANSACTION返回协议
- 5.8.1.4 发送和处理BC_REPLY命令协议
- 5.8.1.5 发送和处理BR_REPLY返回协议
- 5.8.2 循环等待Client进程请求
- 5.9 Service代理对象接口的获取过程
- 5.10 Binder进程间通信机制的Java实现接口
- 5.10.1 获取Service Manager的Java代理对象接口
- 5.10.2 AIDL服务接口解析
- 5.10.3 Java服务的启动过程
- 5.10.4 获取Java服务的代理对象接口
- 5.10.5 Java服务的调用过程
- 第六章 Ashmem匿名共享内存系统
- 6.1 Ashmem驱动程序
- 6.1.1 相关数据结构
- 6.1.2 设备初始化过程
- 6.1.3 设备文件打开过程
- 6.1.4 设备文件内存映射过程
- 6.1.5 内存块的锁定和解锁过程
- 6.1.6 解锁状态内存块的回收过程
- 6.2 运行时库cutils的匿名共享内存接口
- 6.3 匿名共享内存的C++访问接口
- 6.3.1 MemoryHeapBase
- 6.3.1.1 Server端的实现
- 6.3.1.2 Client端的实现
- 6.3.2 MemoryBase
- 6.3.2.1 Server端的实现
- 6.3.2.2 Client端的实现
- 6.3.3 应用实例
- 6.4 匿名共享内存的Java访问接口
- 6.4.1 MemoryFile
- 6.4.2 应用实例
- 6.5 匿名共享内存的共享原理分析
- 第三篇 Android应用程序框架篇
- 第七章 Activity组件的启动过程
- 7.1 Activity组件应用实例
- 7.2 根Activity的启动过程
- 7.3 Activity在进程内的启动过程
- 7.4 Activity在新进程中的启动过程
- 第八章 Service组件的启动过程
- 8.1 Service组件应用实例
- 8.2 Service在新进程中的启动过程
- 8.3 Service在进程内的绑定过程
- 第九章 Android系统广播机制
- 9.1 广播应用实例
- 9.2 广播接收者的注册过程
- 9.3 广播的发送过程
- 第十章 Content Provider组件的实现原理
- 10.1 Content Provider组件应用实例
- 10.1.1 ArticlesProvider
- 10.1.2 Article
- 10.2 Content Provider组件的启动过程
- 10.3 Content Provider组件的数据共享原理
- 10.4 Content Provider组件的数据更新通知机制
- 10.4.1 内容观察者的注册过程
- 10.4.2 数据更新的通知过程
- 第十一章 Zygote和System进程的启动过程
- 11.1 Zygote进程的启动脚本
- 11.2 Zygote进程的启动过程
- 11.3 System进程的启动过程
- 第十二章 Android应用程序进程的启动过程
- 12.1 应用程序进程的创建过程
- 12.2 Binder线程池的启动过程
- 12.3 消息循环的创建过程
- 第十三章 Android应用程序的消息处理机制
- 13.1 创建线程消息队列
- 13.2 线程消息循环过程
- 13.3 线程消息发送过程
- 13.4 线程消息处理过程
- 第十四章 Android应用程序的键盘消息处理机制
- 14.1 InputManager的启动过程
- 14.1.1 创建InputManager
- 14.1.2 启动InputManager
- 14.1.3 启动InputDispatcher
- 14.1.4 启动InputReader
- 14.2 InputChannel的注册过程
- 14.2.1 创建InputChannel
- 14.2.2 注册Server端InputChannel
- 14.2.3 注册当前激活窗口
- 14.2.4 注册Client端InputChannel
- 14.3 键盘消息的分发过程
- 14.3.1 InputReader处理键盘事件
- 14.3.2 InputDispatcher分发键盘事件
- 14.3.3 当前激活的窗口获得键盘消息
- 14.3.4 InputDispatcher获得键盘事件处理完成通知
- 14.4 InputChannel的注销过程
- 14.4.1 销毁应用程序窗口
- 14.4.2 注销Client端InputChannel
- 14.4.3 注销Server端InputChannel
- 第十五章 Android应用程序线程的消息循环模型
- 15.1 应用程序主线程消息循环模型
- 15.2 界面无关的应用程序子线程消息循环模型
- 15.3 界面相关的应用程序子线程消息循环模型
- 第十六章 Android应用程序的安装和显示过程
- 16.1 应用程序的安装过程
- 16.2 应用程序的显示过程