作者:意境、三莅
一、 Flutter桥调用请注意结果反馈
通过桥来拓展Flutter的能力,是非常通用的Flutter开发场景。常见的包括:网络请求,本地存储,异构页面通信等 。实现功能固然重要,但是如果控制不好返回值,可能会是一个灾难。例如,Dart侧代码:
void getResult() async { dynamic result = await MethodChannel('methodChannelName') .invokeMethod('methodName', {'params': content}); doOtherJobs(); }
Native 侧代码,以Android代码为例:
@Override public void onMethodCall(MethodCall call, Result result) { switch (call.method) { case "methodName": { doJob(); break; } } }
如果按上述代码实现,就会出现一个非常隐藏的问题:dart侧代码会一直wait,之后的代码(doOtherJobs)永远不会被执行到了。问题的原因是MethodChannel 并不知道底层的逻辑是否执行完毕。那怎样通知MethodChannel执行完了呢?需要调用如下代码:
如下代码调用任一一个即可 result.success(); result.error(); 如果没有对应方法的实现可以调用如下代码: result.notImplemented();
问题虽小,但是影响可能很大,一定要小心。
二、 Flutter await代码带来的潜在并发
Flutter使用Dart语言进行开发。Dart语言具备非常友好的并发编程语法。例如async/await。我们在享受并发语法带来便利的同时,也需要深刻理解代码背后的执行逻辑。只有这样,才能避免走入一些“深坑”。
首先从最简单的逻辑来看:如果一个函数被标记为async,意味着该函数会被异步执行,函数会返回一个Future对象。函数正常执行的到该函数的时候,并不会停下并等待函数的结果返回,而是直接运行下面的代码。如果想要程序停下,等待函数的执行结果,需要配合await关键字来实现。示例如下:
这里有一个非常有意思的问题,使用await等待异步函数执行,到doJob2函数执行,这中间是不是仅仅执行了doAsyncJob函数内容?来看下面的例子:
bool needReturn = false; Future doJob2() async { needReturn = true; } Future doJob() async { if (needReturn) { return; } print('needReturn position1 is $needReturn'); // needReturn == false ? await doSomething(); print('needReturn position2 is $needReturn'); // needReturn == false ? } Future doSomething() async { print('doSomething~~~'); }
第一个问题position1位置的时候needReturn是不是一定是false?
答案是yes,因为needReturn == true会在之前执行的时候,直接返回。要想执行到position1,needReturn一定是false。
第二个问题 position2 位置的时候needReturn 是不是一定是false?
答案是不一定!为什么不一定呢?doSomething函数中并没有设置needReturn为true。needReturn会被修改么?答案是有可能,原因是doJob2可能在其他控制流中被执行。
看起来position2的上一句就是doSomething,但是在await等待的时候,其他的并发函数也可能被执行,如果doJob2被执行,值就会发生了变化。结论:使用await并发执行以后,记得一定要做变量的重新检查。因为这里虽然代码相邻,但是过程中可能执行大量其他并发函数,核心状态并不像看起来的那么可控。
三、 Flutter FPS高不代表一定流畅
流畅滚动是优异体验的核心保障。FPS(Frames Per Second)作为页面流畅度的核心度量指标,被广泛使用。FPS本质上度量的是每秒播放的帧数。下图直观对比不同帧率的显示效果。
原文为gif
Flutter开发页面,同样广泛的使用FPS来度量页面流畅度。但是Flutter一直有一个“细碎抖动”的问题,也就是页面整体是流畅的,但是在滚动的过程中有明显的细碎抖动,这对用户体验产生了伤害。
在实际开发过程中,FPS这一指标对这类抖动问题的度量效率并不高。例如:前900ms刷了50帧,但是最后100ms刷了1帧,最后的FPS值是51,看起来也是一个不错的值。但是用户会在其中明显感知到卡顿。帧率的连贯性是很重要的,即便刷新只有30帧,但是如果一直是这个帧率,用户感知起来也是流畅的。但是如果一下子从50帧掉到30帧用户还是会感知明显的卡顿。所以流畅度的度量需要感知帧率的变化。那Flutter中怎么感知每一帧的变化呢?可以用如下方法获取每一帧的性能数据数据。
WidgetsBinding.instance.addTimingsCallback();
透过该方法,除了能获取每一帧的整体耗时,还可以细化到build和raster两个主要阶段的耗时。这样能更加深入的做性能问题的排查。数据结构体如下:
factory FrameTiming({ required int vsyncStart, required int buildStart, required int buildFinish, required int rasterStart, required int rasterFinish, required int rasterFinishWallTime, int frameNumber = -1, })
那么在知道帧耗时的情况,怎么判定是一次卡顿呢?可以从如下两个维度来度量:
• 帧耗时是之前N帧平均耗时的M倍(这里N和M可以根据实际情况调整,例如一般设置成3帧和2倍)。
• 帧耗时超过两帧电影帧耗时(电影帧单帧耗时:1000ms/24≈41.67ms,这是下线,帧耗时超过这个标准,用户能明显感知到卡顿)。
同时我们也可以通过统计不同分位的帧耗时,更细致感知实际页面渲染情况。例如常见的90分位,99分位帧耗时。大家可以根据实际情况统计。
接下篇:https://developer.aliyun.com/article/1225938?groupCode=idlefish