編輯:關於Android編程
Logcat工具內置在Android系統中,可以在主機上通過adb logcat命令來查看模擬機上日志信息。Logcat工具的用法很豐富,因此,源代碼也比較多,本文並不打算完整地介紹整個Logcat工具的源代碼,主要是介紹Logcat讀取日志的主線,即從打開日志設備文件到讀取日志設備文件的日志記錄到輸出日志記錄的主要過程,希望能起到一個拋磚引玉的作用。
Logcat工具源代碼位於system/core/logcat目錄下,只有一個源代碼文件logcat.cpp,編譯後生成的可執行文件位於out/target/product/generic/system/bin目錄下,在模擬機中,可以在/system/bin目錄下看到logcat工具。下面我們就分段來閱讀logcat.cpp源代碼文件。
一. Logcat工具的相關數據結構。
這些數據結構是用來保存從日志設備文件讀出來的日志記錄:
[color=amily:Consolas,'Courier]
[cpp]
struct queued_entry_t {
union {
unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1] __attribute__((aligned(4)));
struct logger_entry entry __attribute__((aligned(4)));
};
queued_entry_t* next;
queued_entry_t() {
next = NULL;
}
};
struct log_device_t {
char* device;
bool binary;
int fd;
bool printed;
char label;
queued_entry_t* queue;
log_device_t* next;
log_device_t(char* d, bool b, char l) {
device = d;
binary = b;
label = l;
queue = NULL;
next = NULL;
printed = false;
}
void enqueue(queued_entry_t* entry) {
if (this->queue == NULL) {
this->queue = entry;
} else {
queued_entry_t** e = &this->queue;
while (*e && cmp(entry, *e) >= 0) {
e = &((*e)->next);
}
entry->next = *e;
*e = entry;
}
}
};
其中,宏LOGGER_ENTRY_MAX_LEN和struct logger_entry定義在system/core/include/cutils/logger.h文件中,在Android應用程序框架層和系統運行庫層日志系統源代碼分析一文有提到,為了方便描述,這裡列出這個宏和結構體的定義:
[color=amily:Consolas,'Courier]
[cpp]
struct logger_entry {
__u16 len; /* length of the payload */
__u16 __pad; /* no matter what, we get 2 bytes of padding */
__s32 pid; /* generating process's pid */
__s32 tid; /* generating process's tid */
__s32 sec; /* seconds since Epoch */
__s32 nsec; /* nanoseconds */
char msg[0]; /* the entry's payload */
};
#define LOGGER_ENTRY_MAX_LEN (4*1024)
從結構體struct queued_entry_t和struct log_device_t的定義可以看出,每一個log_device_t都包含有一個queued_entry_t隊列,queued_entry_t就是對應從日志設備文件讀取出來的一條日志記錄了,而log_device_t則是對應一個日志設備文件上下文。在Android日志系統驅動程序Logger源代碼分析一文中,我們曾提到,Android日志系統有三個日志設備文件,分別是/dev/log/main、/dev/log/events和/dev/log/radio。
每個日志設備上下文通過其next成員指針連接起來,每個設備文件上下文的日志記錄也是通過next指針連接起來。日志記錄隊例是按時間戳從小到大排列的,這個log_device_t::enqueue函數可以看出,當要插入一條日志記錄的時候,先隊列頭開始查找,直到找到一個時間戳比當前要插入的日志記錄的時間戳大的日志記錄的位置,然後插入當前日志記錄。比較函數cmp的定義如下:
[color=amily:Consolas,'Courier]
[cpp]
static int cmp(queued_entry_t* a, queued_entry_t* b) {
int n = a->entry.sec - b->entry.sec;
if (n != 0) {
return n;
}
return a->entry.nsec - b->entry.nsec;
}
為什麼日志記錄要按照時間戳從小到大排序呢?原來,Logcat在使用時,可以指定一個參數-t <count>,可以指定只顯示最新count條記錄,超過count的記錄將被丟棄,在這裡的實現中,就是要把排在隊列前面的多余日記記錄丟棄了,因為排在前面的日志記錄是最舊的,默認是顯示所有的日志記錄。在下面的代碼中,我們還會繼續分析這個過程。
二. 打開日志設備文件。
Logcat工具的入口函數main,打開日志設備文件和一些初始化的工作也是在這裡進行。main函數的內容也比較多,前面的邏輯都是解析命令行參數。這裡假設我們使用logcat工具時,不帶任何參數。這不會影響我們分析logcat讀取日志的主線,有興趣的讀取可以自行分析解析命令行參數的邏輯。
分析完命令行參數以後,就開始要創建日志設備文件上下文結構體struct log_device_t了:
[color=amily:Consolas,'Courier]
[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時,不帶任何命令行參數,這裡的devices變量為NULL,因此,就會默認創建/dev/log/main設備上下文結構體,如果存在/dev/log/system設備文件,也會一並創建。宏LOGGER_LOG_MAIN和LOGGER_LOG_SYSTEM也是定義在system/core/include/cutils/logger.h文件中:
[color=amily:Consolas,'Courier]
[cpp]
#define LOGGER_LOG_MAIN "log/main"
#define LOGGER_LOG_SYSTEM "log/system"
我們在Android日志系統驅動程序Logger源代碼分析一文中看到,在Android日志系統驅動程序Logger中,默認是不創建/dev/log/system設備文件的。
往下看,調用setupOutput()函數來初始化輸出文件:
[color=amily:Consolas,'Courier]
[cpp]
android::setupOutput();
setupOutput()函數定義如下:
[color=amily:Consolas,'Courier]
[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;
}
}
如果我們在執行logcat命令時,指定了-f <filename>選項,日志內容就輸出到filename文件中,否則,就輸出到標准輸出控制台去了。
再接下來,就是打開日志設備文件了:
[color=amily:Consolas,'Courier]
[cpp]
dev = devices;
while (dev) {
dev->fd = open(dev->device, mode);
if (dev->fd < 0) {
fprintf(stderr, "Unable to open log device '%s': %s\n",
dev->device, strerror(errno));
exit(EXIT_FAILURE);
}
if (clearLog) {
int ret;
ret = android::clearLog(dev->fd);
if (ret) {
perror("ioctl");
exit(EXIT_FAILURE);
}
}
if (getLogSize) {
int size, readable;
size = android::getLogSize(dev->fd);
if (size < 0) {
perror("ioctl" );
exit(EXIT_FAILURE);
}
readable = android::getLogReadableSize(dev->fd);
if (readable < 0) {
perror("ioctl");
exit(EXIT_FAILURE);
}
printf("%s: ring buffer is %dKb (%dKb consumed), "
"max entry is %db, max payload is %db\n", dev->device,
size / 1024, readable / 1024,
(int) LOGGER_ENTRY_MAX_LEN, (int) LOGGER_ENTRY_MAX_PAYLOAD);
}
dev = dev->next; }
如果執行logcat命令的目的是清空日志,即clearLog為true,則調用android::clearLog函數來執行清空日志操作:
[color=amily:Consolas,'Courier]
[cpp]
static int clearLog(int logfd)
{
return ioctl(logfd, LOGGER_FLUSH_LOG);
}
這裡是通過標准的文件函數ioctl函數來執行日志清空操作,具體可以參考logger驅動程序的實現。
如果執行logcat命令的目的是獲取日志內存緩沖區的大小,即getLogSize為true,通過調用android::getLogSize函數實現:
[color=amily:Consolas,'Courier]
[cpp]
/* returns the total size of the log's ring buffer */
static int getLogSize(int logfd)
{
return ioctl(logfd, LOGGER_GET_LOG_BUF_SIZE);
}
如果為負數,即size < 0,就表示出錯了,退出程序。
接著驗證日志緩沖區可讀內容的大小,即調用android::getLogReadableSize函數:
[color=amily:Consolas,'Courier]
[cpp]
/* returns the readable size of the log's ring buffer (that is, amount of the log consumed) */
static int getLogReadableSize(int logfd)
{
return ioctl(logfd, LOGGER_GET_LOG_LEN);
}
如果返回負數,即readable < 0,也表示出錯了,退出程序。
接下去的printf語句,就是輸出日志緩沖區的大小以及可讀日志的大小到控制台去了。
繼續看下看代碼,如果執行logcat命令的目的是清空日志或者獲取日志的大小信息,則現在就完成使命了,可以退出程序了:
[color=amily:Consolas,'Courier]
[cpp]
if (getLogSize) {
return 0;
}
if (clearLog) {
return 0;
}
否則,就要開始讀取設備文件的日志記錄了:
[color=amily:Consolas,'Courier]
[html]
android::readLogLines(devices);
至此日志設備文件就打開並且初始化好了,下面,我們繼續分析從日志設備文件讀取日志記錄的操作,即readLogLines函數。
三. 讀取日志設備文件。
讀取日志設備文件內容的函數是readLogLines函數:
[color=amily:Consolas,'Courier]
[cpp]
static void readLogLines(log_device_t* devices)
{
log_device_t* dev;
int max = 0;
int ret;
int queued_lines = 0;
bool sleep = true;
int result;
fd_set readset;
for (dev=devices; dev; dev = dev->next) {
if (dev->fd > max) {
max = dev->fd;
}
}
while (1) {
do {
timeval timeout = { 0, 5000 /* 5ms */ }; // If we oversleep it's ok, i.e. ignore EINTR.
FD_ZERO(&readset);
for (dev=devices; dev; dev = dev->next) {
FD_SET(dev->fd, &readset);
}
result = select(max + 1, &readset, NULL, NULL, sleep ? NULL : &timeout);
} while (result == -1 && errno == EINTR);
if (result >= 0) {
for (dev=devices; dev; dev = dev->next) {
if (FD_ISSET(dev->fd, &readset)) {
queued_entry_t* entry = new queued_entry_t();
/* NOTE: driver guarantees we read exactly one full entry */
ret = read(dev->fd, entry->buf, LOGGER_ENTRY_MAX_LEN);
if (ret < 0) {
if (errno == EINTR) {
delete entry;
goto next;
}
if (errno == EAGAIN) {
delete entry;
break;
}
perror("logcat read");
exit(EXIT_FAILURE);
}
else if (!ret) {
fprintf(stderr, "read: Unexpected EOF!\n");
exit(EXIT_FAILURE);
}
entry->entry.msg[entry->entry.len] = '\0';
dev->enqueue(entry);
++queued_lines;
}
}
if (result == 0) {
// we did our short timeout trick and there's nothing new
// print everything we have and wait for more data
sleep = true;
while (true) {
chooseFirst(devices, &dev);
if (dev == NULL) {
break;
}
if (g_tail_lines == 0 || queued_lines <= g_tail_lines) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
--queued_lines;
}
// the caller requested to just dump the log and exit
if (g_nonblock) {
exit(0);
}
} else {
// print all that aren't the last in their list
sleep = false;
while (g_tail_lines == 0 || queued_lines > g_tail_lines) {
chooseFirst(devices, &dev);
if (dev == NULL || dev->queue->next == NULL) {
break;
}
if (g_tail_lines == 0) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
--queued_lines;
}
}
}
next:
;
}
}
由於可能同時打開了多個日志設備文件,這裡使用select函數來同時監控哪個文件當前可讀:
[color=amily:Consolas,'Courier]
[cpp]
do {
timeval timeout = { 0, 5000 /* 5ms */ }; // If we oversleep it's ok, i.e. ignore EINTR.
FD_ZERO(&readset);
for (dev=devices; dev; dev = dev->next) {
FD_SET(dev->fd, &readset);
}
result = select(max + 1, &readset, NULL, NULL, sleep ? NULL : &timeout);
} while (result == -1 && errno == EINTR);
如果result >= 0,就表示有日志設備文件可讀或者超時。接著,用一個for語句檢查哪個設備文件可讀,即FD_ISSET(dev->fd, &readset)是否為true,如果為true,表明可讀,就要進一步通過read函數將日志讀出,注意,每次只讀出一條日志記錄:
[color=amily:Consolas,'Courier]
[cpp]
for (dev=devices; dev; dev = dev->next) {
if (FD_ISSET(dev->fd, &readset)) {
queued_entry_t* entry = new queued_entry_t();
/* NOTE: driver guarantees we read exactly one full entry */
ret = read(dev->fd, entry->buf, LOGGER_ENTRY_MAX_LEN);
if (ret < 0) {
if (errno == EINTR) {
delete entry;
goto next;
}
if (errno == EAGAIN) {
delete entry;
break;
}
perror("logcat read");
exit(EXIT_FAILURE);
}
else if (!ret) {
fprintf(stderr, "read: Unexpected EOF!\n");
exit(EXIT_FAILURE);
}
entry->entry.msg[entry->entry.len] = '\0';
dev->enqueue(entry);
++queued_lines;
}
}
調用read函數之前,先創建一個日志記錄項entry,接著調用read函數將日志讀到entry->buf中,最後調用dev->enqueue(entry)將日志記錄加入到日志隊例中去。同時,把當前的日志記錄數保存在queued_lines變量中。
繼續進一步處理日志:
[color=amily:Consolas,'Courier]
[cpp]
if (result == 0) {
// we did our short timeout trick and there's nothing new
// print everything we have and wait for more data
sleep = true;
while (true) {
chooseFirst(devices, &dev);
if (dev == NULL) {
break;
}
if (g_tail_lines == 0 || queued_lines <= g_tail_lines) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
--queued_lines;
}
// the caller requested to just dump the log and exit
if (g_nonblock) {
exit(0);
}
} else {
// print all that aren't the last in their list
sleep = false;
while (g_tail_lines == 0 || queued_lines > g_tail_lines) {
chooseFirst(devices, &dev);
if (dev == NULL || dev->queue->next == NULL) {
break;
}
if (g_tail_lines == 0) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
--queued_lines;
}
}
如果result == 0,表明是等待超時了,目前沒有新的日志可讀,這時候就要先處理之前已經讀出來的日志。調用chooseFirst選擇日志隊列不為空,且日志隊列中的第一個日志記錄的時間戳為最小的設備,即先輸出最舊的日志:
[color=amily:Consolas,'Courier]
[cpp]
static void chooseFirst(log_device_t* dev, log_device_t** firstdev) {
for (*firstdev = NULL; dev != NULL; dev = dev->next) {
if (dev->queue != NULL && (*firstdev == NULL || cmp(dev->queue, (*firstdev)->queue) < 0)) {
*firstdev = dev;
}
}
}
如果存在這樣的日志設備,接著判斷日志記錄是應該丟棄還是輸出。前面我們說過,如果執行logcat命令時,指定了參數-t <count>,那麼就會只顯示最新的count條記錄,其它的舊記錄將被丟棄:
[color=amily:Consolas,'Courier]
[cpp]
if (g_tail_lines == 0 || queued_lines <= g_tail_lines) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
g_tail_lines表示顯示最新記錄的條數,如果為0,就表示全部顯示。如果g_tail_lines == 0或者queued_lines <= g_tail_lines,就表示這條日志記錄應該輸出,否則就要丟棄了。每處理完一條日志記錄,queued_lines就減1,這樣,最新的g_tail_lines就可以輸出出來了。
如果result > 0,表明有新的日志可讀,這時候的處理方式與result == 0的情況不同,因為這時候還有新的日志可讀,所以就不能先急著處理之前已經讀出來的日志。這裡,分兩種情況考慮,如果能設置了只顯示最新的g_tail_lines條記錄,並且當前已經讀出來的日志記錄條數已經超過g_tail_lines,就要丟棄,剩下的先不處理,等到下次再來處理;如果沒有設備顯示最新的g_tail_lines條記錄,即g_tail_lines == 0,這種情況就和result == 0的情況處理方式一樣,先處理所有已經讀出的日志記錄,再進入下一次循環。希望讀者可以好好體會這段代碼:
[color=amily:Consolas,'Courier]
[cpp]
while (g_tail_lines == 0 || queued_lines > g_tail_lines) {
chooseFirst(devices, &dev);
if (dev == NULL || dev->queue->next == NULL) {
break;
}
if (g_tail_lines == 0) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
--queued_lines;
}
丟棄日志記錄的函數skipNextEntry實現如下:
[color=amily:Consolas,'Courier]
[cpp]
static void skipNextEntry(log_device_t* dev) {
maybePrintStart(dev);
queued_entry_t* entry = dev->queue;
dev->queue = entry->next;
delete entry;
}
這裡只是簡單地跳過日志隊列頭,這樣就把最舊的日志丟棄了。
printNextEntry函數處理日志輸出,下一節中繼續分析。
四. 輸出日志設備文件的內容。
從前面的分析中看出,最終日志設備文件內容的輸出是通過printNextEntry函數進行的:
[color=amily:Consolas,'Courier]
[cpp]
static void printNextEntry(log_device_t* dev) {
maybePrintStart(dev);
if (g_printBinary) {
printBinary(&dev->queue->entry);
} else {
processBuffer(dev, &dev->queue->entry);
}
skipNextEntry(dev);
}
g_printBinary為true時,以二進制方式輸出日志內容到指定的文件中:
[color=amily:Consolas,'Courier]
[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);
}
我們關注g_printBinary為false的情況,調用processBuffer進一步處理:
[color=amily:Consolas,'Courier]
[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;
}
當dev->binary為true,日志記錄項是二進制形式,不同於我們在Android日志系統驅動程序Logger源代碼分析一文中提到的常規格式:
struct logger_entry |
priority | tag | msg
這裡我們不關注這種情況,有興趣的讀者可以自已分析,android_log_processBinaryLogBuffer函數定義在system/core/liblog/logprint.c文件中,它的作用是將一條二進制形式的日志記錄轉換為ASCII形式,並保存在entry參數中,它的原型為:
[color=amily:Consolas,'Courier]
[cpp]
/**
* 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);
通常情況下,dev->binary為false,調用android_log_processLogBuffer函數將日志記錄由logger_entry格式轉換為AndroidLogEntry格式。logger_entry格式在在Android日志系統驅動程序Logger源代碼分析一文中已經有詳細描述,這裡不述;AndroidLogEntry結構體定義在system/core/include/cutils/logprint.h中:
[color=amily:Consolas,'Courier]
[cpp]
typedef struct AndroidLogEntry_t {
time_t tv_sec;
long tv_nsec;
android_LogPriority priority;
pid_t pid;
pthread_t tid;
const char * tag;
size_t messageLen;
const char * message;
} AndroidLogEntry;
android_LogPriority是一個枚舉類型,定義在system/core/include/android/log.h文件中:
[color=amily:Consolas,'Courier]
[cpp]
/*
* Android log priority values, in ascending priority order.
*/
typedef enum android_LogPriority {
ANDROID_LOG_UNKNOWN = 0,
ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
ANDROID_LOG_VERBOSE,
ANDROID_LOG_DEBUG,
ANDROID_LOG_INFO,
ANDROID_LOG_WARN,
ANDROID_LOG_ERROR,
ANDROID_LOG_FATAL,
ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
} android_LogPriority;
android_log_processLogBuffer定義在system/core/liblog/logprint.c文件中:
[color=amily:Consolas,'Courier]
[cpp]
/**
* 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;
}
結合logger_entry結構體中日志項的格式定義(struct logger_entry | priority | tag | msg),這個函數很直觀,不再累述。
調用完android_log_processLogBuffer函數後,日志記錄的具體信息就保存在本地變量entry中了,接著調用android_log_shouldPrintLine函數來判斷這條日志記錄是否應該輸出。
在分析android_log_shouldPrintLine函數之前,我們先了解數據結構AndroidLogFormat,這個結構體定義在system/core/liblog/logprint.c文件中:
[color=amily:Consolas,'Courier]
[cpp]
struct AndroidLogFormat_t {
android_LogPriority global_pri;
FilterInfo *filters;
AndroidLogPrintFormat format;
};
AndroidLogPrintFormat也是定義在system/core/liblog/logprint.c文件中:
[color=amily:Consolas,'Courier]
[cpp]
typedef struct FilterInfo_t {
char *mTag;
android_LogPriority mPri;
struct FilterInfo_t *p_next;
} FilterInfo;
因此,可以看出,AndroidLogFormat結構體定義了日志過濾規范。在logcat.c文件中,定義了變量
[color=amily:Consolas,'Courier]
[cpp]
static AndroidLogFormat * g_logformat;
這個變量是在main函數裡面進行分配的:
[color=amily:Consolas,'Courier]
[cpp]
g_logformat = android_log_format_new();
在main函數裡面,在分析logcat命令行參數時,會將g_logformat進行初始化,有興趣的讀者可以自行分析。
回到android_log_shouldPrintLine函數中,它定義在system/core/liblog/logprint.c文件中:
[color=amily:Consolas,'Courier]
[cpp]
/**
* 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);
}
這個函數判斷在p_format中根據tag值,找到對應的pri值,如果返回來的pri值小於等於參數傳進來的pri值,那麼就表示這條日志記錄可以輸出。我們來看filterPriForTag函數的實現:
[color=amily:Consolas,'Courier]
[cpp]
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;
}
如果在p_format中找到與tag值對應的filter,並且該filter的mPri不等於ANDROID_LOG_DEFAULT,那麼就返回該filter的成員變量mPri的值;其它情況下,返回p_format->global_pri的值。
回到processBuffer函數中,如果執行完android_log_shouldPrintLine函數後,表明當前日志記錄應當輸出,則調用android_log_printLogLine函數來輸出日志記錄到文件fd中, 這個函數也是定義在system/core/liblog/logprint.c文件中:
[color=amily:Consolas,'Courier]
[cpp]
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;
}
這個函數的作用就是把AndroidLogEntry格式的日志記錄按照指定的格式AndroidLogFormat進行輸出了,這裡,不再進一步分析這個函數。
processBuffer函數的最後,還有一個rotateLogs的操作:
[color=amily:Consolas,'Courier]
[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;
}
一、概述在Android的開發中,經常聽到“內存洩漏”這個詞。“內存洩漏”就是一個對象已經不需要再使用了,但是因為其它的對象持有該對象的引用,導致它的內存不能被回收。“內
一、 RemoteViews的兩種應用1. 桌面widget小部件(http://blog.csdn.net/qq_28261343/article/details/5
留守公司就剩下幾個人了。我沒有年假故還在堅守。廢話不多說,閒來無事。想練習一下自定義控件的應用以及學習圖片類操作以及處理等等。所以我在網上找了大神文章,鴻洋大神的博客。找
QQ5.0側滑效果實現方案有很多方式,今天我們使用ViewDragHelper來實現一下。先上效果圖:①自定義控件SlidingMenu繼承FrameLayout,放在F