本篇文章有2个目的:
- 介绍Dart单线程的运行模式
- Flutter项目中如何收集崩溃信息和日志
单线程模型:
Dart单线程queue分类
Dart 在单线程中是以消息循环机制来运行的,其中包含两个任务队列,
一个是“微任务队列” microtask queue,
另一个叫做“事件队列” event queue。
使用Future类,可以将任务加入到Event Queue的队尾
使用scheduleMicrotask函数,将任务加入到Microtask Queue队尾
Microtask Queue存在的意义是:希望通过这个Queue来处理稍晚一些的事情,但是在下一个消息到来之前需要处理完的事情。(画重点!)
当Event Looper正在处理Microtask Queue中的Event时候,Event Queue中的Event就停止了处理了,此时App不能绘制任何图形,不能处理任何鼠标点击,不能处理文件IO等等
从图中可以发现,微任务队列的执行优先级高于事件队列
Dart线程运行过程
现在我们来介绍一下Dart线程运行过程,
如上图中所示,入口函数 main() 执行完后,
消息循环机制便启动了。
首先会按照先进先出的顺序逐个执行微任务队列中的任务,
事件任务执行完毕后程序便会退出,
但是,在事件任务执行的过程中也可以插入新的微任务和事件任务,
在这种情况下,整个线程的执行过程便是一直在循环,不会退出,
而Flutter中,主线程的执行过程正是如此,永不终止。
在Dart中,所有的外部事件任务都在事件队列中,
如IO、计时器、点击、以及绘制事件等,
而微任务通常来源于Dart内部,并且微任务非常少,
之所以如此,是因为微任务队列优先级高,
如果微任务太多,执行时间总和就越久,事件队列任务的延迟也就越久,
对于GUI应用来说最直观的表现就是比较卡,所以必须得保证微任务队列不会太长。
值得注意的是,我们可以通过Future.microtask(…)方法向微任务队列插入一个任务。
在事件循环中,当某个任务发生异常并没有被捕获时,程序并不会退出,
而直接导致的结果是当前任务的后续代码就不会被执行了,也就是说一个任务中的异常是不会影响其它任务执行的。
关于单线程中Future的一些使用:
- Future中的then并没有创建新的Event丢到Event Queue中,而只是一个普通的Function Call,在FutureTask执行完后,立即开始执行
- 当Future在then函数先已经执行完成了,则会创建一个task,将该task的添加到microtask queue中,并且该任务将会执行通过then传入的函数
- Future只是创建了一个Event,将Event插入到了Event Queue的队尾
- 使用Future.value构造函数的时候,就会和第二条一样,创建Task丢到microtask Queue中执行then传入的函数
- Future.sync构造函数执行了它传入的函数之后,也会立即创建Task丢到microtask Queue中执行(这个很明显,不加入解释)
就是说Future.then方法里面并没有创建一个新的Event,只是一个简单的方法回调,Future(该方法只是创建一个Evnet并入队)在FutureTask(这个需要去Dart的文档去看了解一下),就会占用eventloop立即执行;
一般Future会带有很多then处理函数,then都视为任务的子任务。所以会创建新的task。而这个task一般会加入到microtask中(希望通过这个Queue来处理稍晚一些的事情,但是在下一个消息到来之前需要处理完的事情)
和上面的解析这个相呼应!就是解决下一个Future进入Event之前处理好这些事情。
Flutter异常捕获及上报
捕获Flutter框架抛出的异常
在我们日常开发中,也许写代码时候会写一些比较低级的bug
Flutter 框架为我们在很多关键的方法进行了异常捕获
通常这个时候调试机器上面就会红屏,弹出错误信息,那么这个错误信息是如何弹出来的呢?
下面这一段是弹出错误视图的源码
class _MyAppState extends State<MyApp> {
@overridevoid initState() {
super.initState();
// 注册页面,每个页面的名称在native端和flutter端需要一致
FlutterBoost.singleton.registerPageBuilders({
'embeded': (pageName, params, _)=>EmbededFirstRouteWidget(),
'first': (pageName, params, _) => FirstRouteWidget(),
'firstFirst': (pageName, params, _) => FirstFirstRouteWidget(),
'second': (pageName, params, _) => SecondRouteWidget(),
'secondStateful': (pageName, params, _) => SecondStatefulRouteWidget(),
'tab': (pageName, params, _) => TabRouteWidget(),
'platformView': (pageName, params, _) => PlatformRouteWidget(),
'flutterFragment': (pageName, params, _) => FragmentRouteWidget(params),
///可以在native层通过 getContainerParams 来传递参数
'flutterPage': (pageName, params, _) {
print("flutterPage params:$params");
return FlutterRouteWidget(params:params);
},
});
FlutterBoost.singleton.addBoostNavigatorObserver(TestBoostNavigatorObserver());
}
@overrideWidget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Boost example',
builder: FlutterBoost.init(postPush: _onRoutePushed),
home: Container(color:Colors.white));
}
void _onRoutePushed(String pageName, String uniqueId, Map params, Route route, Future _) {}}
综上所述,想捕获Flutter抛出的异常,那么我们只需要在void mian(){}内实现FlutterError提供的onError方法
即可捕获Flutter为我们包装好的一些错误收集
void main() {
FlutterError.onError = (FlutterErrorDetails details) {
reportError(details);
};
...
}
捕获其它异常
在Flutter中,还有一些Flutter没有为我们捕获的异常,如调用空对象方法异常、Future中的异常
在Dart中,异常分两类:同步异常和异步异常,同步异常可以通过try/catch捕获,而异步异常则比较麻烦,如下面的代码是捕获不了Future的异常的:
即Future中的Error使用try catch的方式是捕获不到的!!!
try{
Future.delayed(Duration(seconds: 1)).then((e) => Future.error("xxx"));
} catch (e){
print(e)
}
那么我们就捕获不到这些错误了吗?其实不然,
Dart语法中提供了runZoned()方法
Dart中有一个runZoned(...) 方法,可以给执行对象指定一个Zone。
Zone表示一个代码执行的环境范围,为了方便理解,读者可以将Zone类比为一个代码执行沙箱,
不同沙箱的之间是隔离的,沙箱可以捕获、拦截或修改一些代码行为,
如Zone中可以捕获日志输出、Timer创建、微任务调度的行为,
同时Zone也可以捕获所有未处理的异常。
来看下runZoned的定义
R runZoned<R>(R body(), {
Map zoneValues,
ZoneSpecification zoneSpecification,
Function onError,
})
其中zoneValues是私有数据,可以使用kv的方式取出,通常我们在实际使用的时候传入了runApp()实例!!!即意味着捕获的范围在整个Flutter的app内部
zoneSpecification可以定义一些代码行为,如打印日志等
onError即可捕获所有沙箱范围内抛出的错误
void main() {
runZoned(() => runApp(MyApp()),
zoneSpecification: ZoneSpecification(
print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
parent.print(zone, "Intercepted: $line");
},),
onError: (Object obj, StackTrace stack) {
var details = makeDetails(obj, stack);
// 自定义error收集
reportErrorAndLog(details);
},
);
}
在实际项目中的完整使用
综合以上两点,在一个完整的Flutter App内部应该有自己的崩溃收集策略。
其实现原理就是基于FlutterError.onError()和runZoned()
附上简单的实现代码
void collectLog(String line){
... //收集日志,如存储在本地等
}
void reportErrorAndLog(FlutterErrorDetails details){
... //上报错误和日志逻辑,通过服务端日志接口上报日志
}
FlutterErrorDetails makeDetails(Object obj, StackTrace stack){
...// 构建错误信息,根据FlutterErrorDetail组件自己的error模型及赋予基本特征信息
}
void main() {
FlutterError.onError = (FlutterErrorDetails details) {
reportErrorAndLog(details);
};
runZoned(() => runApp(MyApp()),
zoneSpecification: ZoneSpecification(
print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
collectLog(line); // 收集日志
},
),
onError: (Object obj, StackTrace stack) {
var details = makeDetails(obj, stack);
reportErrorAndLog(details);
},
);
}