Flutter冷知识 | 获取dart的print内容
前言
这得从我在群里时不时冒泡说起。
此刻楼上大佬装成萌新,所以我就来了。
也就是看了群里一位老哥写的 Flutter 组件,可以查看当前打印的日志,还是挺厉害,但是打印的时候就只能调用他给定的方法来 print ,在写安卓原生的时候有过这一需求,就是需要在 app 内部拿到 System.out.print 方法打印的字符。
java 中则是开放了这一方法。
1 | System.setErr(new PrintStream(***)); |
这样所有的标准输出与标准错误输出都定向到了自己设置的流中,这个流可以指向文件、管道、或是文件描述符。
大致相关的一些需求:
- 需要主动获取 dart 的 print 内容,方便分析,因为过长的内容 print 是会被截断的。
- 添加少量代码的情况下让自己的上线产品的输出不能随意被看见。
因为在平时的开发中,经常有被调试的手机运行着其他的 Flutter 程序,自己的电脑终端就能拿到它的输出。
初步分析
从 dart 内部
在 dart 的 stdio.dart 文件中有以下几个变量。
1 | int _stdinFD = 0; |
还有一个重定向输出的方法。
1 | void _setStdioFDs(int stdin, int stdout, int stderr) { |
几乎这个文件所有的东西都加上了 _ ,而我们能拿到的只有 stdin/stdout/stderr,并且能够操作的方法也比较少。
从进程
一个进程至少有 0,1,2 三个文件描述符,它们分别对应 stdin/stdout/stderr。
举个例子:
我们在 c 语言用调用以下代码:
1 | printf('hi'); |
它其实等价于:
1 | char tmp = "hi"; |
所以我考虑拷贝这三个文件描述符,让他们指向其他的地方。
确保在父进程执行拷贝函数,fork 后的子进程的文件描述符是分开。
dup2 函数
dup2 函数用来拷贝文件描述符。
函数原型:
1 | int dup2 (int oldfd,int newfd) |
若参数 newfd 已经被程序使用,则系统就会将 newfd 所指的文件关闭,若 newfd 等于 oldfd ,则返回 newfd ,而不关闭 newfd 所指的文件。dup2 所复制的文件描述符与原来的文件描述符共享各种文件状态。共享所有的锁定,读写位置和各项权限或 flags 等。
例子
我们调用
1 | int filefd = open("./tmp.txt", O_RDWR); |
O_RDWR 表示可读,可写。
此时我们再次调用:
1 | printf('hi'); |
屏幕上不会再出现任何字符,输出就直接写入到了文件 tmp.txt 中。
所以它其实等价于:
1 | char tmp = "hi"; |
这也是伪终端实现的主要原理之一。
Flutter 实现
我们使用 dart:ffi 来完成这个操作。
ffi 相关本文不做具体解释。
流程就是 dart -> dart:ffi -> c -> dup2
所以我们只需要简单的几行代码。
1 | int filefd = open("./tmp.txt", O_RDWR); |
如此一来,开发者调用 print 函数,亦或者 dart 内部的一些打印,都等价于:
1 | write(filefd, '***', strlen("***")); |
所有的内容都会被定向到我们指定的文件中
测试截图:
此时我的 vs code 调试终端拿不到任何输出了,但我能从我设置到的文件中读取输出。
平台差异
在我反复测试 Linux 桌面版与 Android 平台的时候,发现在 Linux 的 Flutter 桌面 app 上,dart 的 print 其实就是写入字符到文件描述符 1,2,但在 Android 设备上,print 写入到的文件描述符是 3 ,
也就是
1 | dup2(filefd, 3); |
over。有问题留言。有错误指出。
最后欢迎各位加入 Flutter Candies,一起制造可爱好用的 🍬 。 (QQ 群:181398081)
Flutter冷知识 | 获取dart的print内容
http://blog.nightmare.press/2020/07/10/Flutter冷知识-获取dart的print内容/