企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
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开始读取前面打开的日志设备的日志记录。接下来,我们就分析这些日志记录的读取过程。