在flutter源码下面有一个foundation模块,模块之下有一文件是print.dart.
这个文件里面的源码就是常用的debugPrint函数的实现。
typedef DebugPrintCallback = void Function(String message, { int wrapWidth });
DebugPrintCallback debugPrint = debugPrintThrottled;
final Queue<String> _debugPrintBuffer = Queue<String>();
bool _debugPrintScheduled = false; //是否正在打印日志
void debugPrintThrottled(String message, { int wrapWidth }) {
final List<String> messageLines = message?.split('\n') ?? <String>['null'];
if (wrapWidth != null) {
_debugPrintBuffer.addAll(messageLines.expand<String>((String line) => debugWordWrap(line, wrapWidth)));
} else {
_debugPrintBuffer.addAll(messageLines);
}
if (!_debugPrintScheduled)
_debugPrintTask();
}
从上面代码就可以看出来debugPrint函数传入参数的定义:
参数message是必传的的,wrapWidth是规定一行有多少字符的。
以上代码应该除了debugWordWrap和_debugPrintTask方法都很好理解是什么意思。
int _debugPrintedCharacters = 0;
const int _kDebugPrintCapacity = 12 * 1024;
const Duration _kDebugPrintPauseTime = Duration(seconds: 1);
final Queue<String> _debugPrintBuffer = Queue<String>();
final Stopwatch _debugPrintStopwatch = Stopwatch();
Completer<void> _debugPrintCompleter; //这个print完成的回调
void _debugPrintTask() {
_debugPrintScheduled = false;
if (_debugPrintStopwatch.elapsed > _kDebugPrintPauseTime) {
_debugPrintStopwatch.stop();
_debugPrintStopwatch.reset();
_debugPrintedCharacters = 0;
}
while (_debugPrintedCharacters < _kDebugPrintCapacity && _debugPrintBuffer.isNotEmpty) {
final String line = _debugPrintBuffer.removeFirst();
_debugPrintedCharacters += line.length; // TODO(ianh): Use the UTF-8 byte length instead
print(line);
}
if (_debugPrintBuffer.isNotEmpty) {
_debugPrintScheduled = true;
_debugPrintedCharacters = 0;
Timer(_kDebugPrintPauseTime, _debugPrintTask);
_debugPrintCompleter ??= Completer<void>();
} else {
_debugPrintStopwatch.start();
_debugPrintCompleter?.complete();
_debugPrintCompleter = null;
}
}
以上的代码是_debugPrintTask这个方法,
- _debugPrintScheduled 主要是防止在打印过程中调用_debugPrintTask方法
- _debugPrintStopwatch主要是做一个延时的优化,和_debugPrintedCharacters相结合目的是防止在1s中之内打印超过12 * 1024字符,超过的部分设置一个定时器,过一秒再打印。
final RegExp _indentPattern = RegExp('^ *(?:[-+*] |[0-9]+[.):] )?');
enum _WordWrapParseMode { inSpace, inWord, atBreak }
Iterable<String> debugWordWrap(String message, int width, { String wrapIndent = '' }) sync* {
if (message.length < width || message.trimLeft()[0] == '#') {
yield message;
return;
}
final Match prefixMatch = _indentPattern.matchAsPrefix(message);
final String prefix = wrapIndent + ' ' * prefixMatch.group(0).length;
int start = 0;
int startForLengthCalculations = 0;
bool addPrefix = false;
int index = prefix.length;
_WordWrapParseMode mode = _WordWrapParseMode.inSpace;
int lastWordStart;
int lastWordEnd;
while (true) {
switch (mode) {
case _WordWrapParseMode.inSpace: // at start of break point (or start of line); can't break until next break
while ((index < message.length) && (message[index] == ' '))
index += 1;
lastWordStart = index;
mode = _WordWrapParseMode.inWord;
break;
case _WordWrapParseMode.inWord: // looking for a good break point
while ((index < message.length) && (message[index] != ' '))
index += 1;
mode = _WordWrapParseMode.atBreak;
break;
case _WordWrapParseMode.atBreak: // at start of break point
if ((index - startForLengthCalculations > width) || (index == message.length)) {
// we are over the width line, so break
if ((index - startForLengthCalculations <= width) || (lastWordEnd == null)) {
// we should use this point, because either it doesn't actually go over the
// end (last line), or it does, but there was no earlier break point
lastWordEnd = index;
}
if (addPrefix) {
yield prefix + message.substring(start, lastWordEnd);
} else {
yield message.substring(start, lastWordEnd);
addPrefix = true;
}
if (lastWordEnd >= message.length)
return;
// just yielded a line
if (lastWordEnd == index) {
// we broke at current position
// eat all the spaces, then set our start point
while ((index < message.length) && (message[index] == ' '))
index += 1;
start = index;
mode = _WordWrapParseMode.inWord;
} else {
// we broke at the previous break point, and we're at the start of a new one
assert(lastWordStart > lastWordEnd);
start = lastWordStart;
mode = _WordWrapParseMode.atBreak;
}
startForLengthCalculations = start - prefix.length;
assert(addPrefix);
lastWordEnd = null;
} else {
// save this break point, we're not yet over the line width
lastWordEnd = index;
// skip to the end of this break point
mode = _WordWrapParseMode.inSpace;
}
break;
}
}
}
- 这个方法就是实现每行的字符数的限制
- wrapIndent默认是“”(空字符串),可以改一下,那这个方法处理后才换行的每行日志前面都会有这个前缀。
- 这个方法可能对于我们中文使用者来说有两个坑:a. 这个换行是基于英文单词间的空格而分割的;b. 我们中文每个词不用空格分割。举个例子说明这两点吧:如果我们设置的wrapWidth是20,一个句子有三个词组成,第一个单词长度是30个字符,第二个15个字符,第三个词是2个字符。结果是打印出来的会是第一个词虽然长度超过20字符,但是它会完整展示出30个字符作为一行。第二个词加第三个词,再加上中间的空格分割字符,一起是18个字符会在第二行显示。
最后我们来说一下flutter原来版本的debugPrint实现:
void debugPrintSynchronously(String message, { int wrapWidth }) {
if (wrapWidth != null) {
print(message.split('\n').expand<String>((String line) => debugWordWrap(line, wrapWidth)).join('\n'));
} else {
print(message);
}
}
这个处理就简单很多了,那为什么要增加限制日志输出速率的处理呢?那是因为避免在一些会限制日志输出速率的平台会发生日志丢失的情况,比如Android.
这是flutter框架源码分析的其中一篇,因能力有限,有诸多不足之处,还请斧正。
网友评论