从前面4.6.3小节的内容可以知道,Logcat工具是通过调用源代码文件logcat.cpp中的函数printNextEntry来输出日志记录的。
在前面的4.6.2小节中还提到,如果全局变量g_printBinary的值等于1,那么就说明以二进制格式来输出日志记录。这时候函数printNextEntry就调用函数printBinary来处理。
**system/core/logcat/logcat.cpp**
~~~
void printBinary(struct logger_entry *buf)
{
size_t size = sizeof(logger_entry) + buf->len;
int ret;
do {
ret = write(g_outFD, buf, size);
} while (ret < 0 && errno == EINTR);
}
~~~
由于不需要对日志记录进行解析,即不用将它的优先级、标签以及内容解析出来,因此,函数printBinary的实现很简单,可直接调用函数write将它输出到文件或者打印到标准输出中。
如果全局变量g_printBinary的值等于0,那么就要以文本格式来输出日志记录。这时候函数printNextEntry就调用函数processBuffer来处理。
**system/core/logcat/logcat.cpp**
~~~
static void processBuffer(log_device_t* dev, struct logger_entry *buf)
{
int bytesWritten = 0;
int err;
AndroidLogEntry entry;
char binaryMsgBuf[1024];
if (dev->binary) {
err = android_log_processBinaryLogBuffer(buf, &entry, g_eventTagMap,
binaryMsgBuf, sizeof(binaryMsgBuf));
//printf(">>> pri=%d len=%d msg='%s'\n",
// entry.priority, entry.messageLen, entry.message);
} else {
err = android_log_processLogBuffer(buf, &entry);
}
if (err < 0) {
goto error;
}
if (android_log_shouldPrintLine(g_logformat, entry.tag, entry.priority)) {
if (false && g_devCount > 1) {
binaryMsgBuf[0] = dev->label;
binaryMsgBuf[1] = ' ';
bytesWritten = write(g_outFD, binaryMsgBuf, 2);
if (bytesWritten < 0) {
perror("output error");
exit(-1);
}
}
bytesWritten = android_log_printLogLine(g_logformat, g_outFD, &entry);
if (bytesWritten < 0) {
perror("output error");
exit(-1);
}
}
g_outByteCount += bytesWritten;
if (g_logRotateSizeKBytes > 0
&& (g_outByteCount / 1024) >= g_logRotateSizeKBytes
) {
rotateLogs();
}
error:
//fprintf (stderr, "Error processing record\n");
return;
}
~~~
前面得到的日志记录只是一块二进制数据,保存在一个logger_entry结构体中,因此,函数processBuffer在将它输出之前,首先要将它的内容转换为一个AndroidLogEntry结构体。AndroidLogEntry结构体描述了一条日志记录的写入时间、优先级、标签、内容以及写入进程ID。如果日志记录的类 型是二进制格式的,即是类型为events的日志记录,那么函数processBuffer就会调用函数android_log_processBinaryLogBuffer对它进行解析;否则,就调用函数android_log_processLogBuffer对它进行解析。日志记录解析完成之后,函数processBuffer最后就调用函数android_log_printLogLine将它输出到文件或者打印到标准输出中。这三个函数的实现我们在后面再详细分析,现在先完成对函数processBuffer的分析。
一条日志记录解析完成之后,Logcat工具就得到了它的优先级和标签。由于在启动Logcat工具时,可能设置了日志记录输出过滤器,因此,函数processBuffer就需要调用函数android_log_shouldPrintLine判断一条日志记录是否能够输出。在前面的4.6.2小节中提到,Logcat工具的日志记录输出过滤器列表保存在全局变量g_logformat中,因此,函数processBuffer就以它作为参数来调用函数android_log_shouldPrintLine,判断一条日志记录是否能够输出,它的实现如下所示。
**system/core/liblog/logprint.c**
~~~
/**
* returns 1 if this log line should be printed based on its priority
* and tag, and 0 if it should not
*/
int android_log_shouldPrintLine (
AndroidLogFormat *p_format, const char *tag, android_LogPriority pri)
{
return pri >= filterPriForTag(p_format, tag);
}
~~~
函数filterPriForTag首先在参数p_format中检查是否对日志记录标签tag设置了输出过滤器。如果设置了,那么函数filterPriForTag就会返回它的过滤优先级。只有这个过滤优先级低于即将要输出的日志记录的优先级时,该日志记录才可以输出。
函数filterPriForTag的实现如下所示。
**system/core/liblog/logprint.c**
~~~
static android_LogPriority filterPriForTag(
AndroidLogFormat *p_format, const char *tag)
{
FilterInfo *p_curFilter;
for (p_curFilter = p_format->filters
; p_curFilter != NULL
; p_curFilter = p_curFilter->p_next
) {
if (0 == strcmp(tag, p_curFilter->mTag)) {
if (p_curFilter->mPri == ANDROID_LOG_DEFAULT) {
return p_format->global_pri;
} else {
return p_curFilter->mPri;
}
}
}
return p_format->global_pri;
}
~~~
第6行到第17行的for循环检查在参数p_format的日志记录输出过滤器列表中是否为日志记录标签tag设置了输出过滤器。如果设置了,就返回该日志记录标签所对应的优先级;否则,就返回全局设置的日志记录输出优先级,即返回参数p_format的成员变量global_pri。另外,如果我们为日志记录标签tag设置了输出过滤器,但是将该输出过滤器的优先级设置为ANDROID_LOG_DEFAULT,那么函数filterPriForTag实际上返回的是全局设置的日志记录输出优先级,如第12行代码所示。
回到函数processBuffer中,如果函数android_log_shouldPrintLine的返回值为true,那么它就会先调用函数android_log_printLogLine输出日志记录,然后再将输出的日志记录的字节数bytesWritten增加到全局变量g_outByteCount中,表示到目前为止,一共往文件g_outFD中输出了多少个字节的日志记录。一旦输出到文件g_outFD中的日志记录的字节数大于全局变量g_logRotateSizeKBytes的值时,那么Logcat就会将接下来的其他日志记录输出到另外一个文件中。全局变量g_logRotateSizeKBytes的值是由Logcat工具的启动选项r来指定的,如果没有指定该选项,那么全局变量g_logRotateSizeKBytes的值就会等于0,表示将所有的日志记录都输出到同一个文件中。如果全局变量g_logRotateSizeKBytes的值大于0,那么总共可以用来作为日志记录输出文件的个数就由Logcat工具的选项n来指定。如果没有指定选项n,那么默认就有四个日志记录输出文件。第44行调用函数rotateLogs来设置下一个日志记录输出文件,它的实现如下所示。
**system/core/logcat/logcat.cpp**
~~~
static void rotateLogs()
{
int err;
// Can't rotate logs if we're not outputting to a file
if (g_outputFileName == NULL) {
return;
}
close(g_outFD);
for (int i = g_maxRotatedLogs ; i > 0 ; i--) {
char *file0, *file1;
asprintf(&file1, "%s.%d", g_outputFileName, i);
if (i - 1 == 0) {
asprintf(&file0, "%s", g_outputFileName);
} else {
asprintf(&file0, "%s.%d", g_outputFileName, i - 1);
}
err = rename (file0, file1);
if (err < 0 && errno != ENOENT) {
perror("while rotating log files");
}
free(file1);
free(file0);
}
g_outFD = openLogFile (g_outputFileName);
if (g_outFD < 0) {
perror ("couldn't open output file");
exit(-1);
}
g_outByteCount = 0;
}
~~~
假设在启动Logcat工具时,通过选项f来指定日志记录输出文件为“logfile”,并且通过选项n来指定日志记录输出文件个数为3,那么函数rotateLogs就分别将日志记录输出文件设置为logfile、logfile.1、logfile.2和logfile.3。
> 这四个文件是循环使用的,即当文件logfile.3的大小达到限定的值之后,Logcat工具就将接下来的日志记录重新输出到文件logfile、logfile.1和logfile.2中。依此类推,当文件logfile.2的大小再次达到限定的值之后,最后又将新的日志记录输出到文件logfile.3中。
分析完成函数processBuffer的实现之后,接下来我们开始分析函数android_log_processLogBuffer、android_log_processBinaryLogBuffer和android_log_printLogLine的实现。
首先分析函数android_log_processLogBuffer的实现,它是用来解析类型为main、system和radio的日志记录的,如下所示。
**system/core/liblog/logprint.c**
~~~
/**
* Splits a wire-format buffer into an AndroidLogEntry
* entry allocated by caller. Pointers will point directly into buf
*
* Returns 0 on success and -1 on invalid wire format (entry will be
* in unspecified state)
*/
int android_log_processLogBuffer(struct logger_entry *buf,
AndroidLogEntry *entry)
{
size_t tag_len;
entry->tv_sec = buf->sec;
entry->tv_nsec = buf->nsec;
entry->priority = buf->msg[0];
entry->pid = buf->pid;
entry->tid = buf->tid;
entry->tag = buf->msg + 1;
tag_len = strlen(entry->tag);
entry->messageLen = buf->len - tag_len - 3;
entry->message = entry->tag + tag_len + 1;
return 0;
}
~~~
日志记录的优先级字段的长度为1个字节,保存在logger_entry 结构体buf内部的缓冲区msg的第一个字节中。日志记录的标签字段从logger_entry 结构体buf内部的缓冲区msg的第二个字节开始,一直到后面的‘\0’字符为止,因此,第19行可以通过调用函数strlen来计算它的长度。logger_entry 结构体buf内部的缓冲区msg 剩余的字节即为日志记录的内容字段,它的长度等于缓冲区的总长度减去日志记录标签的长度,再减去1个字节的日志记录优先级,以及2个字符串结束字符‘\0’,其中一个是日志记录标签字段的,另一个是日志记录内容字段的。
接下来,我们继续分析函数android_log_processBinaryLogBuffer的实现,它是用来解析类型为events的日志记录的,我们分段来阅读。
**system/core/liblog/logprint.c**
~~~
/**
* Convert a binary log entry to ASCII form.
*
* For convenience we mimic the processLogBuffer API. There is no
* pre-defined output length for the binary data, since we're free to format
* it however we choose, which means we can't really use a fixed-size buffer
* here.
*/
int android_log_processBinaryLogBuffer(struct logger_entry *buf,
AndroidLogEntry *entry, const EventTagMap* map, char* messageBuf,
int messageBufLen)
{
size_t inCount;
unsigned int tagIndex;
const unsigned char* eventData;
entry->tv_sec = buf->sec;
entry->tv_nsec = buf->nsec;
entry->priority = ANDROID_LOG_INFO;
entry->pid = buf->pid;
entry->tid = buf->tid;
/*
* Pull the tag out.
*/
eventData = (const unsigned char*) buf->msg;
inCount = buf->len;
if (inCount < 4)
return -1;
tagIndex = get4LE(eventData);
eventData += 4;
inCount -= 4;
if (map != NULL) {
entry->tag = android_lookupEventTag(map, tagIndex);
} else {
entry->tag = NULL;
}
~~~
第17行到21行代码用来初始化AndroidLogEntry 结构体entry中的日志记录写入时间、写入进程ID以及优先级等信息。从前面4.1小节中的图4-3可以知道,类型为events的日志记录是没有优先级这个字段的,但是为了和其他类型的日志记录统一处理,Logcat工具将它们的优先级设置为ANDROID_LOG_INFO。
第26行得到logger_entry 结构体buf内部的缓冲区msg的地址,并且保存在变量eventData中。由于类型为events的日志记录的标签是使用一个整数值来描述的,并且保存在logger_entry 结构体buf内部的缓冲区msg的前面4个字节中,因此第30行就调用函数get4LE将它获取回来,并且保存在变量tagIndex中。
函数get4LE的实现如下所示。
**system/core/liblog/logprint.c**
~~~
/*
* Extract a 4-byte value from a byte stream.
*/
static inline uint32_t get4LE(const uint8_t* src)
{
return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
}
~~~
它只是简单地将缓冲区src前面4个字节的内容组合在一起形成一个整数,然后返回给调用者。
回到函数android_log_processBinaryLogBuffer中,接下来第34行检查参数map的值是否为NULL。如果是,就直接将AndroidLogEntry 结构体entry的成员变量tag设置为NULL,表示当前正在处理的一个日志记录的标签值没有对应的描述字符串;否则,第35行就会调用函数android_lookupEventTag在参数map中找到与日志记录标签值tagIndex对应的描述字符串。
> 参数map指向一个EventTagMap结构体,它是Logcat工具在启动时,通过解析目标设备上的/system/etc/event-log-tags文件得到的。因此,Logcat工具就可以通过它来找到与日志记录标签值tagIndex对应的描述字符串。
数android_lookupEventTag的实现如下所示。
**system/core/liblog/event_tag_map.c**
~~~
/*
* Look up an entry in the map.
*
* The entries are sorted by tag number, so we can do a binary search.
*/
const char* android_lookupEventTag(const EventTagMap* map, int tag)
{
int hi, lo, mid;
lo = 0;
hi = map->numTags-1;
while (lo <= hi) {
int cmp;
mid = (lo+hi)/2;
cmp = map->tagArray[mid].tagIndex - tag;
if (cmp < 0) {
/* tag is bigger */
lo = mid + 1;
} else if (cmp > 0) {
/* tag is smaller */
hi = mid - 1;
} else {
/* found */
return map->tagArray[mid].tagStr;
}
}
return NULL;
}
~~~
在前面的4.6.2小节中介绍Logcat工具的初始化过程时提到,Logcat工具解析完成目标设备上的/system/etc/event-log-tags文之后,就得到了一个日志记录标签描述表,表中描述了与每一个日志标签所对应的描述字符串,最后会按照标签值从小到大的顺序保存在EventTagMap结构体内部EventTag结构体数组tagArray中。因此,函数android_lookupEventTag就可以使用二分法从这个数组中得到与日志记录标签值tag对应的描述字符串,并且将它返回给调用者。
回到函数android_log_processBinaryLogBuffer中,继续往下执行。
**system/core/liblog/logprint.c**
~~~
/*
* If we don't have a map, or didn't find the tag number in the map,
* stuff a generated tag value into the start of the output buffer and
* shift the buffer pointers down.
*/
if (entry->tag == NULL) {
int tagLen;
tagLen = snprintf(messageBuf, messageBufLen, "[%d]", tagIndex);
entry->tag = messageBuf;
messageBuf += tagLen+1;
messageBufLen -= tagLen+1;
}
/*
* Format the event log data into the buffer.
*/
char* outBuf = messageBuf;
size_t outRemaining = messageBufLen-1; /* leave one for nul byte */
int result;
result = android_log_printBinaryEvent(&eventData, &inCount, &outBuf,
&outRemaining);
~~~
如果前面没有在EventTagMap结构体map中找到即将要输出的日志记录的标签值描述字符串,即第44行的if语句为true,那么第47行就将该标签值作为要输出的日志记录的标签值描述字符串。接着第59行调用函数android_log_printBinaryEvent继续解析日志记录的内容字段。
要输出的日志记录解析完成之后,输出结果就保存在缓冲区messageBuf中。在调用函数android_log_printBinaryEvent来解析要输出的日志记录的内容字段之前,第57行会在缓冲区messageBuf的后面保留一个字节,用来保存一个字符串结束字符‘\0’。这样,Logcat工具就可以将缓冲区messageBuf的内容作为一个字符串输出到文件或者打印到标准输出中。
函数android_log_printBinaryEvent的实现如下所示。
**system/core/liblog/logprint.c**
~~~
/*
* Recursively convert binary log data to printable form.
*
* This needs to be recursive because you can have lists of lists.
*
* If we run out of room, we stop processing immediately. It's important
* for us to check for space on every output element to avoid producing
* garbled output.
*
* Returns 0 on success, 1 on buffer full, -1 on failure.
*/
static int android_log_printBinaryEvent(const unsigned char** pEventData,
size_t* pEventDataLen, char** pOutBuf, size_t* pOutBufLen)
{
const unsigned char* eventData = *pEventData;
size_t eventDataLen = *pEventDataLen;
char* outBuf = *pOutBuf;
size_t outBufLen = *pOutBufLen;
unsigned char type;
size_t outCount;
int result = 0;
if (eventDataLen < 1)
return -1;
type = *eventData++;
eventDataLen--;
//fprintf(stderr, "--- type=%d (rem len=%d)\n", type, eventDataLen);
switch (type) {
case EVENT_TYPE_INT:
/* 32-bit signed int */
{
int ival;
if (eventDataLen < 4)
return -1;
ival = get4LE(eventData);
eventData += 4;
eventDataLen -= 4;
outCount = snprintf(outBuf, outBufLen, "%d", ival);
if (outCount < outBufLen) {
outBuf += outCount;
outBufLen -= outCount;
} else {
/* halt output */
goto no_room;
}
}
break;
case EVENT_TYPE_LONG:
/* 64-bit signed long */
{
long long lval;
if (eventDataLen < 8)
return -1;
lval = get8LE(eventData);
eventData += 8;
eventDataLen -= 8;
outCount = snprintf(outBuf, outBufLen, "%lld", lval);
if (outCount < outBufLen) {
outBuf += outCount;
outBufLen -= outCount;
} else {
/* halt output */
goto no_room;
}
}
break;
case EVENT_TYPE_STRING:
/* UTF-8 chars, not NULL-terminated */
{
unsigned int strLen;
if (eventDataLen < 4)
return -1;
strLen = get4LE(eventData);
eventData += 4;
eventDataLen -= 4;
if (eventDataLen < strLen)
return -1;
if (strLen < outBufLen) {
memcpy(outBuf, eventData, strLen);
outBuf += strLen;
outBufLen -= strLen;
} else if (outBufLen > 0) {
/* copy what we can */
memcpy(outBuf, eventData, outBufLen);
outBuf += outBufLen;
outBufLen -= outBufLen;
goto no_room;
}
eventData += strLen;
eventDataLen -= strLen;
break;
}
case EVENT_TYPE_LIST:
/* N items, all different types */
{
unsigned char count;
int i;
if (eventDataLen < 1)
return -1;
count = *eventData++;
eventDataLen--;
if (outBufLen > 0) {
*outBuf++ = '[';
outBufLen--;
} else {
goto no_room;
}
for (i = 0; i < count; i++) {
result = android_log_printBinaryEvent(&eventData, &eventDataLen,
&outBuf, &outBufLen);
if (result != 0)
goto bail;
if (i < count-1) {
if (outBufLen > 0) {
*outBuf++ = ',';
outBufLen--;
} else {
goto no_room;
}
}
}
if (outBufLen > 0) {
*outBuf++ = ']';
outBufLen--;
} else {
goto no_room;
}
}
break;
default:
fprintf(stderr, "Unknown binary event type %d\n", type);
return -1;
}
bail:
*pEventData = eventData;
*pEventDataLen = eventDataLen;
*pOutBuf = outBuf;
*pOutBufLen = outBufLen;
return result;
no_room:
result = 1;
goto bail;
}
~~~
函数android_log_printBinaryEvent正好是在前面4.5小节中介绍的四个日志记录写入函数android_util_EventLog_writeEvent_Integer、android_util_EventLog_writeEvent_Long、android_util_EventLog_writeEvent_String和android_util_EventLog_writeEvent_Array的逆操作,因此,读者可以对照这四个函数来分析函数android_log_printBinaryEvent的实现。简单来说,如果日志记录内容只有一个值,那么函数android_log_printBinaryEvent就会根据它的类型(整数、长整数和字符串)将它从输入缓冲区pEventData取回来,并且保存到输出缓冲区outBuf中。当日志记录的内容是一个列表时,函数android_log_printBinaryEvent就会通过递归调用自己来依次遍历列表中的数据。如果得到的数据是一个整数、长整数或者字符串,那么处理方式就与前面一样;否则,当得到的数据又是一个列表时,函数android_log_printBinaryEvent就会继续对这个子列表执行同样的遍历操作。
函数android_log_printBinaryEvent执行完成之后,如果返回值等于0,那么就说明日志记录解析成功;否则,就说明解析过程中出现了问题,其中,返回值等于-1表示日志记录缓冲区有误,返回值等于1表示输出缓冲区outBuf没有足够的空间来容纳日志记录的内容。
回到函数android_log_processBinaryLogBuffer中,继续往下执行。
**system/core/liblog/logprint.c**
~~~
if (result < 0) {
fprintf(stderr, "Binary log entry conversion failed\n");
return -1;
} else if (result == 1) {
if (outBuf > messageBuf) {
/* leave an indicator */
*(outBuf-1) = '!';
} else {
/* no room to output anything at all */
*outBuf++ = '!';
outRemaining--;
}
/* pretend we ate all the data */
inCount = 0;
}
/* eat the silly terminating '\n' */
if (inCount == 1 && *eventData == '\n') {
eventData++;
inCount--;
}
if (inCount != 0) {
fprintf(stderr,
"Warning: leftover binary log data (%d bytes)\n", inCount);
}
/*
* Terminate the buffer. The NUL byte does not count as part of
* entry->messageLen.
*/
*outBuf = '\0';
entry->messageLen = outBuf - messageBuf;
assert(entry->messageLen == (messageBufLen-1) - outRemaining);
entry->message = messageBuf;
return 0;
}
~~~
变量result的值是调用函数android_log_printBinaryEvent得到的返回值。如果它的值小于0,即第61行的if语句为true,就说明日志记录内容有误,因此,函数android_log_processBinaryLogBuffer就不用进一步对它进行处理了,第63行直接返回错误码-1;如果它的值等于1,即第64行的if语句为true,那么就说明日志记录输出缓冲区messageBuf空间不足,这时候函数android_log_processBinaryLogBuffer就在缓冲区messageBuf的最后一个字节上写入一个号‘!’来说明此种情况。
变量inCount表示原始的日志记录缓冲区中还有多少个字节未处理。如果它的值等于1,并且剩余未处理的字符为‘\n’,那么就说明要输出的是一条正常的日志记录,因为每一条类型为events的日志记录总是以‘\n’字符结束的。如果要输出的日志记录不是这种情况,那么第84行和第85行就会输出一条警告信息来说明此种情况。
前面提到,函数android_log_processBinaryLogBuffer在调用函数android_log_printBinaryEvent来解析要输出的日志记录的内容字段之前,会在日志记录输出缓冲区messageBuf的后面保留一个字节,这个字节就是用来写入一个字符串结束符号‘\0’的,如第92行代码所示。这样,Logcat工具就可以把缓冲区messageBuf的内容当作一个字符串来使用,即可以直接将它以文本的形式输出到文件或者打印到标准输出中。
最后,我们分析函数android_log_printLogLine的实现,它是日志记录输出过程中的最后一个步骤,它的实现如下所示。
**system/core/liblog/logprint.c**
~~~
/**
* Either print or do not print log line, based on filter
*
* Returns count bytes written
*/
int android_log_printLogLine(
AndroidLogFormat *p_format,
int fd,
const AndroidLogEntry *entry)
{
int ret;
char defaultBuffer[512];
char *outBuffer = NULL;
size_t totalLen;
outBuffer = android_log_formatLogLine(p_format, defaultBuffer,
sizeof(defaultBuffer), entry, &totalLen);
if (!outBuffer)
return -1;
do {
ret = write(fd, outBuffer, totalLen);
} while (ret < 0 && errno == EINTR);
if (ret < 0) {
fprintf(stderr, "+++ LOG: write failed (errno=%d)\n", errno);
ret = 0;
goto done;
}
if (((size_t)ret) < totalLen) {
fprintf(stderr, "+++ LOG: write partial (%d of %d)\n", ret,
(int)totalLen);
goto done;
}
done:
if (outBuffer != defaultBuffer) {
free(outBuffer);
}
return ret;
}
~~~
第17行调用函数android_log_formatLogLine来格式化要输出的日志记录,并且将最后结果保存在缓冲区defaultBuffer中。函数android_log_formatLogLine的实现比较简单,它将要输出的日志记录的内容格式化为“<PREFIX> +MESSAGE+<SUFFIX>”的形式。其中,<PREFIX>和<SUFFIX>的内容取决于Logcat工具在初始化时所设置的日志记录输出格式,即参数p_format的内容;而MESSAGE描述了日志记录的内容字段。有了输出结果缓冲区defaultBuffer之后,第23行到第25行的while循环就调用函数write将它输出到文件描述符fd所描述的目标文件中。
在前面的4.6.2小节中介绍Logcat工具的初始化过程时提到,如果在启动Logcat时,指定了选项v,那么就可以指定日志记录的输出格式。选项v后面所跟的参数可以为“brief”、“process”、“tag”、“thread”、“raw”、“time”、“threadtime”或者“long”。每一个参数都对应一个AndroidLogPrintFormat枚举值,分别为FORMAT_BRIEF、FORMAT_PROCESS、FORMAT_TAG、FORMAT_THREAD、FORMAT_RAW、FORMAT_TIME、FORMAT_THREADTIME和FORMAT_LONG。
下面我们就介绍每一个AndroidLogPrintFormat枚举值所对应的日志记录输出格式。
(1)FORMAT_BRIEF:<PREFIX>和<SUFFIX>的内容分别为“<priority>/<tag>(<pid>): ”和“\n”。
(2)FORMAT_PROCESS:<PREFIX>和<SUFFIX>的内容分别为“<priority>(<pid>) ”和“ (<tag>)\n”。
(3)FORMAT_TAG:<PREFIX>和<SUFFIX>的内容分别为“<priority>/(<tag>): ”和“\n”。
(4)FORMAT_THREAD:<PREFIX>和<SUFFIX>的内容分别为“<priority>(<pid>:<tid>) ”和“\n”。
(5)FORMAT_RAW:<PREFIX>和<SUFFIX>的内容分别为空值和“\n”。
(6)FORMAT_TIME:<PREFIX>和<SUFFIX>的内容分别为“<sec>.<nsec> <priority>/<tag>(<pid>): ”和“\n”。
(7)FORMAT_THREADTIME:<PREFIX>和<SUFFIX>的内容分别为 “<sec>.<nsec> <pid> <tid><priority> <tag>: ”和“\n”。
(8)FORMAT_LONG:<PREFIX>和<SUFFIX>的内容分别为“[ <sec>.<nsec> <pid>:<tid><priority>/<tag> ]”和“\n\n”。
例如,假设应用程序(进程ID为396)中调用Java接口android.util.Log来往Logger日志驱动程序中写入了以下一条日志记录:
`android.util.Log.i("LogTag", "Log Content.");`
并且假设Logcat工具在启动时,被指定以FORMAT_BRIEF格式来输出该日志记录的内容,那么Logcat工具从Logger日志驱动程序中读出这条日志记录之后,就会打印出以下的输出:
`I/LogTag(396): Log Content.`
至此,日志记录的输出过程就分析完成了。
- 文章概述
- 下载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 应用程序的显示过程