從前面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 應用程序的顯示過程