在Flutter框架中,Embedder层负责把Flutter嵌入到各个平台上去,其所做的主要工作包括线程设置、渲染Surface设置,以及插件等。因此, Embedder
负责线程的创建和管理,并且提供Task Runner
给Engine
使用。Engine则是负责
提供Isolate
给Framework
和应用层进行多线程创建。
Embeder主要包含
四个Task Runner
:Platform Runner
,UI Runner
,GPU Runner
,IO Runner。 其中,每个Flutter Engine都会有独立的UI Runner
,GPU Runner
,IO Runner,而Platform Runner在Engine间是共享的。
Platform Runner之所以在多个Engine中之存在一份,主要由于
Platform Runner运行在平台的Main Thread,负责执行Flutter Engine的代码和Native Plugin任务。如果在Platform Runner中运行耗时任务,会影响原生平台任务的正常执行。但是Platform Runner被阻塞后并不会导致页面卡顿。因为Platform Runner不负责Flutter的页面渲染。
我们所写的Dart
代码默认是运行在 Root Isolate
中,而Root Isolate
是运行在UI Runner
上的。UI Runner负责创建管理Layer Tree
最终绘制到屏幕上的内容,因此这个线程被阻塞会直接导致界面卡顿掉帧。
Isolate
是Dart
平台对线程的实现方案。和普通Thread
不同,Isolate
拥有独立的内存,由线程和独立内存构成。Isolate
通过事件循环和消息队列来实现单线程异步并发,这比多线程要轻便的多,由于Dart基于单线程模型,所以开发过程中不需要关心线程安全问题。
GPU Task Runner则被用于执行与设备GPU相关的调用。虽然GPU线程是一个独立线程,但由于UI的布局、绘制、渲染、上屏由CPU和GPU共同完成,因此,如果GPU Task Runner耗时太久,同样会造成Flutter应用的卡顿。所以在GPU Task Runner中,不能执行耗时操作。
IO Runner主要功能是网络、文件等IO操作(比如从后台请求网络数据,或者从磁盘中读取压缩的图片格式,将图片数据进行处理为GPU Runner的渲染做好准备)。之所以将IO操作设计为在独立线程中运行,主要是就是为了避免对UI刷新造成不利影响。
问题:如果我们在Dart代码中执行网络请求、文件读取等使用Future来进行异步,会不会导致UI卡顿?
答案是否定的。原因是网络请求、文件读取等耗时操作当运行到系统IO接口时,会将IO任务交给系统线程处理,并将异步任务加入到事件队列,然后Root Isolate回到事件循环,获取事件队列中的下一个任务进行处理。
而执行大量高CPU的运算类耗时任务,如耗时计算,编解码等,即便代码是异步执行,由于所有代码都运行在Root Isolate中,所以仍然会导致Root Isolate没法及时处理其他异步任务,从而导致UI卡顿。所以对于高CPU耗时任务,应该使用新的Isolate去执行。
总结:平时我们做UI卡顿相关优化, 主要关注UI Runner和GPU Runner即可。
在UI Runner主要是要注意避免不必要的UI的更新和重建,比如通过使用Consumer实现Widget局部重建,善用Key实现组件的复用,以及将复杂的CPU操作如计算、编解码另起一个新的线程处理;
而对于GPU Runner,主要是要尽可能降低页面布局层级、尽量避免使用需要SaveLayer离屏渲染的API,如Opacity、ShaderMask、ColorFilter,以及用RepaintBoundary包裹需要频繁刷新的控件,创建单独的Layer渲染。