在JNI中将标准输出重定向到Android日志中

762 阅读3分钟

重定向到 Android Log 中

在Android开发中,使用第三方c或c++库时,有一些库使用了 printf,cout 等方式输出日志。我们在开发中,需要根据输出的内容做一些监控或调整。这时我们首先想到的就是手动去改第三方库,但是有两点不好:

  1. 改动的地方多,工作量大;

  2. 需要手动改第三方库,引入依赖,不便于第三方库的后续更新。

由于以上问题,我们可以换个思路,将标准输出重定向到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系统中,默认情况下,每个进程都会打开三个标准文件描述符:

  1. 标准输入(stdin):用于输入操作,文件描述符值为0
  2. 标准输出(stdout):用于输出操作,文件描述符值为1
  3. 标准错误(stderr):用于输出错误信息,文件描述符值为2
STDOUT_FILENOSTDERR_FILENO
  • STDOUT_FILENO(标准输出文件描述符):通常用于输出正常信息,例如程序的结果、提示信息等。标准输出默认连接到终端或控制台。
  • STDERR_FILENO(标准错误文件描述符):通常用于输出错误信息或诊断信息。标准错误默认也连接到终端或控制台,它与标准输出分开管理,便于独立处理错误信息。

我们在程序中,使用 cout 或 printf 输出的内容就被输出到了标准输出中了。