重定向到 Android Log 中
在Android开发中,使用第三方c或c++库时,有一些库使用了 printf,cout 等方式输出日志。我们在开发中,需要根据输出的内容做一些监控或调整。这时我们首先想到的就是手动去改第三方库,但是有两点不好:
-
改动的地方多,工作量大;
-
需要手动改第三方库,引入依赖,不便于第三方库的后续更新。
由于以上问题,我们可以换个思路,将标准输出重定向到Android Log中。
思路是:创建管道,将标准输出重定向到管道中。读取管道中内容,使用Android Log Api 输出内容。
static int pfd[2];
static pthread_t thr;
static const char *tag = "stdout";
static void *thread_func(void *) {
ssize_t rdsz;
char buf[128];
while ((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) {
if (buf[rdsz - 1] == '\n') --rdsz;
buf[rdsz] = 0; /* add null-terminator */
__android_log_write(ANDROID_LOG_INFO, tag, buf);
}
return 0;
}
int start_logger() {
/* make stdout line-buffered and stderr unbuffered */
setvbuf(stdout, 0, _IOLBF, 0);
setvbuf(stderr, 0, _IONBF, 0);
/* create the pipe and redirect stdout and stderr */
pipe(pfd);
dup2(pfd[1], STDOUT_FILENO);
dup2(pfd[1], STDERR_FILENO);
/* spawn the logging thread */
if (pthread_create(&thr, 0, thread_func, 0) == -1) {
return -1;
}
pthread_detach(thr);
return 0;
}
其中,如何实现重定向呢?关键在于dup2函数。dup2 函数是一个用于复制文件描述符的系统调用,它可以将一个文件描述符复制到另一个文件描述符上,并且可以指定目标文件描述符的值。
关于 dup2
函数原型
int dup2(int oldfd, int newfd);
参数说明
oldfd:要复制的文件描述符,即源文件描述符。newfd:目标文件描述符。如果newfd已经被打开,则会先关闭newfd,然后将oldfd复制到newfd上。
返回值
- 成功时,返回
newfd。 - 失败时,返回
-1,并设置errno以指示错误原因。
工作原理
-
如果
oldfd是一个无效的文件描述符,dup2会返回-1并设置errno。 -
如果
newfd等于oldfd,即目标文件描述符和源文件描述符相同,dup2什么也不做,只是返回newfd。 -
如果
newfd是一个已经打开的文件描述符,dup2会先关闭它,然后将oldfd复制到newfd。 -
dup2保证newfd是新复制的文件描述符,因此即使newfd已经被打开,它也会被关闭并重新分配。
举一个简单的栗子
下面的例子,创建了一个打开output.txt文件的文件描述符,然后,将这个描述符复制到 STDOUT_FILENO中。那么,接下来的所有标准输出都将会写入到output.txt文件中。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("open");
return 1;
}
if (dup2(fd, STDOUT_FILENO) < 0) {
perror("dup2");
return 1;
}
close(fd);
printf("This will be written to the file 'output.txt'\n");
return 0;
}
关于标准输出
在Linux系统中,一切资源都被描述成文件。文件描述符就是用于标识这些资源的。
文件描述符
文件描述符(File Descriptor, FD)是操作系统内核分配给每个打开的文件的一个非负整数。标识对应资源。
标准文件描述符
在Unix-like系统中,默认情况下,每个进程都会打开三个标准文件描述符:
- 标准输入(stdin):用于输入操作,文件描述符值为
0。 - 标准输出(stdout):用于输出操作,文件描述符值为
1。 - 标准错误(stderr):用于输出错误信息,文件描述符值为
2。
STDOUT_FILENO 与 STDERR_FILENO
STDOUT_FILENO(标准输出文件描述符):通常用于输出正常信息,例如程序的结果、提示信息等。标准输出默认连接到终端或控制台。STDERR_FILENO(标准错误文件描述符):通常用于输出错误信息或诊断信息。标准错误默认也连接到终端或控制台,它与标准输出分开管理,便于独立处理错误信息。
我们在程序中,使用 cout 或 printf 输出的内容就被输出到了标准输出中了。