前言
這得從我在群裡時不時冒泡說起。
此刻樓上大佬裝成萌新,所以我就來了。
也就是看了群裡一位老哥寫的 Flutter 元件,可以檢視當前列印的日誌,還是挺厲害,但是列印的時候就只能呼叫他給定的方法來 print ,在寫安卓原生的時候有過這一需求,就是需要在 app 內部拿到 System.out.print 方法列印的字元。
java 中則是開放了這一方法。
System.setErr(new PrintStream(***));
System.setOut(new PrintStream(***));
複製程式碼
這樣所有的標準輸出與標準錯誤輸出都定向到了自己設定的流中,這個流可以指向檔案、管道、或是檔案描述符。
大致相關的一些需求:
- 需要主動獲取 dart 的 print 內容,方便分析,因為過長的內容 print 是會被截斷的。
- 新增少量程式碼的情況下讓自己的上線產品的輸出不能隨意被看見。
因為在平時的開發中,經常有被除錯的手機執行著其他的 Flutter 程式,自己的電腦終端就能拿到它的輸出。
初步分析
從 dart 內部
在 dart 的 stdio.dart 檔案中有以下幾個變數。
int _stdinFD = 0;
int _stdoutFD = 1;
int _stderrFD = 2;
複製程式碼
還有一個重定向輸出的方法。
void _setStdioFDs(int stdin, int stdout, int stderr) {
_stdinFD = stdin;
_stdoutFD = stdout;
_stderrFD = stderr;
}
複製程式碼
幾乎這個檔案所有的東西都加上了 _ ,而我們能拿到的只有 stdin/stdout/stderr,並且能夠操作的方法也比較少。
從程式
一個程式至少有 0,1,2 三個檔案描述符,它們分別對應 stdin/stdout/stderr。
舉個例子:
我們在 c 語言用呼叫以下程式碼:
printf('hi');
複製程式碼
它其實等價於:
char tmp = "hi";
write(1, tmp, strlen(tmp));
複製程式碼
所以我考慮拷貝這三個檔案描述符,讓他們指向其他的地方。
確保在父程式執行拷貝函式,fork 後的子程式的檔案描述符是分開。
dup2 函式
dup2 函式用來拷貝檔案描述符。
函式原型:
int dup2 (int oldfd,int newfd)
複製程式碼
若引數 newfd 已經被程式使用,則系統就會將 newfd 所指的檔案關閉,若 newfd 等於 oldfd ,則返回 newfd ,而不關閉 newfd 所指的檔案。dup2 所複製的檔案描述符與原來的檔案描述符共享各種檔案狀態。共享所有的鎖定,讀寫位置和各項許可權或 flags 等。
例子
我們呼叫
int filefd = open("./tmp.txt", O_RDWR);
dup2(filefd,0);
複製程式碼
O_RDWR 表示可讀,可寫。
此時我們再次呼叫:
printf('hi');
複製程式碼
螢幕上不會再出現任何字元,輸出就直接寫入到了檔案 tmp.txt 中。
所以它其實等價於:
char tmp = "hi";
write(filefd, tmp, strlen(tmp));
複製程式碼
這也是偽終端實現的主要原理之一。
Flutter 實現
我們使用 dart:ffi 來完成這個操作。
ffi 相關本文不做具體解釋。
流程就是 dart -> dart:ffi -> c -> dup2
所以我們只需要簡單的幾行程式碼。
int filefd = open("./tmp.txt", O_RDWR);
dup2(filefd, 1);
dup2(filefd, 2);
複製程式碼
如此一來,開發者呼叫 print 函式,亦或者 dart 內部的一些列印,都等價於:
write(filefd, '***', strlen("***"));
複製程式碼
所有的內容都會被定向到我們指定的檔案中
測試截圖:
此時我的 vs code 除錯終端拿不到任何輸出了,但我能從我設定到的檔案中讀取輸出。平臺差異
在我反覆測試 Linux 桌面版與 Android 平臺的時候,發現在 Linux 的 Flutter 桌面 app 上,dart 的 print 其實就是寫入字元到檔案描述符 1,2,但在 Android 裝置上,print 寫入到的檔案描述符是 3 ,
也就是
dup2(filefd, 3);
複製程式碼
over。有問題留言。有錯誤指出。
最後歡迎各位加入Flutter Candies,一起製造可愛好用的? 。 (QQ群:181398081)