Flutter 必知必会系列 —— runApp 做了啥
前面介绍了 Flutter
的三棵树机制、Flutter
的自定义绘制、Flutter
路由机制,这些机制都是建立在 Flutter
已经运行起来的基础上的,那么 Flutter
是怎么运行的呢?
接下来的几篇文章,就介绍 Flutter
运行之前的准备工作。
程序开始的 Main 方法
每个程序都有入口方法,比较熟悉的 Java
项目就是 public static void
的 main 方法,同样 Flutter
应用也是一个单纯的 Dart
程序,所以它的入口也是 main
方法。
Flutter
中 main
方法的默认签名如下:
void main() {
runApp(MyApp());
}
它是不接受参数,也没有返回值的同步方法。
这里注意一点,这个方法仅仅是 Flutter
程序的 main
签名。
而 Dart
的 main
方法支持异步、传参。下面我们分别来看。
让 main 方法异步
Dart
程序可以用同步的方式写异步代码,就是一组 async\/await
关键字,这组关键字的使用可以看这里:
改造之后的代码如下:
void main() async{
await Future.delayed(Duration(seconds: 3));
print('await 3 seconds');
}
和普通的 main 方法
相比,上面的 main
方法在声明处增加了 async 关键字,在方法体内部增加了 await
关键字。
打印语句并不会立马执行,会在等待 3 秒之后执行。
Flutter
支持这种语法吗? 支持的!但是让 main 变成 async 的用处并不多。
让 main 方法支持参数
Dart
的 mian
方法也支持添加入参,规则如下:
- 第一个位置的普通参数必须是 List 数组类型,后面可以跟着其他类型的参数
- 不允许存在 required 标记的可选命名参数,参数是可 null 类型
下面我们分别来看:
void main(List<String> args) {
print(args);
}
上面是声明的地方,那么怎么用呢?为程序运行增加额外参数!
我们以 IntelliJ IDEA
为例,就是在 Edit Configuration
的地方,配置 Program arguments
参数。
上面的程序就会收到一个数组,数组的内容是 name
和 sun
所以控制台会打印出 name
和 sun
。
但是 Flutter
并不支持这种语法。我们来看为啥不支持。我们以 Android
为例。
Android
中承载 Flutter
运行的是 FlutterActivity
,运行 Flutter
代码的是 FlutterEngine
。
FlutterActivity
和 Activity
一样,也有 onCreate
等方法,在其 onCreate
方法中也进行了初始化的操作。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
/// 代码省略
delegate = new FlutterActivityAndFragmentDelegate(this);
delegate.onAttach(this);//第一处
/// 代码省略
}
FlutterActivityAndFragmentDelegate
是代理器,处理 FlutterActivity
和 FlutterFragment
的通用逻辑。
第一处的绑定就是初始化了 FlutterEngine
并进行了绑定。
初始化如下:
void setupFlutterEngine() {
// First, check if the host wants to use a cached FlutterEngine.
String cachedEngineId = host.getCachedEngineId();
///--------
// Second, defer to subclasses for a custom FlutterEngine.
flutterEngine = host.provideFlutterEngine(host.getContext());
///--------
flutterEngine =
new FlutterEngine(
host.getContext(),
host.getFlutterShellArgs().toArray(),
/*automaticallyRegisterPlugins=*/ false,
/*willProvideRestorationData=*/ host.shouldRestoreAndSaveState());
isFlutterEngineFromHost = false;
}
初始化 Engine
的流程就是:首先从缓存中取 Engine
,要是没有的话,就尝试生成用户自定义的 Engine
,这两个要是都没有的话,就真正执行构造方法来生成一个引擎。
生成引擎的入参如下:
-
第一个参数上下文:如果是
FlutterActivity
的话,这个上下文就是Activity
本身,如果是FlutterFragment
的话,这个上下文就是Fragment
的宿主Activity
。 -
第二个参数虚拟机参数:
dartVmArgs
就是getFlutterShellArgs
返回值,这个返回值的类型是字符串类型的数组。用于设置Dart
虚拟机的运行参数,比如:ARG_ENABLE_DART_PROFILING
就是虚拟机的端口号之类的。
更明确一点:
所以这个并不是 main
的参数,而是虚拟机的运行配置。
- 第三个参数是否自动注册插件,就是我们在
pubspec.yaml
文件下注册的插件是否自动注册到native
的工程中,一般是自动的。
注册就是调用 GeneratedPluginRegister
的注册方法。
- 第四个参数就是表示引擎是否延迟初始化来响应某些数据,如果设置为
true
,表示引擎会延迟初始化,直到数据可用才初始化。
所以 FlutterActivity
的 onCreate
构造了一些初始化的东西:FlutterEngine
、FlutterView
、DartExecutor
等等。
Activity
中用于显示的是 onStart
,FlutterActivity
的 onStart
调用了代理的逻辑,我们看其中的一段流程。
private void doInitialFlutterViewRun() {
/// 省略代码
String initialRoute = host.getInitialRoute();
if (initialRoute == null) {
initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent());
if (initialRoute == null) {
initialRoute = DEFAULT_INITIAL_ROUTE;
}
}
// 省略代码
DartExecutor.DartEntrypoint entrypoint =
new DartExecutor.DartEntrypoint(
appBundlePathOverride, host.getDartEntrypointFunctionName());
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint); //第一处
}
在运行 Flutter 代码之前做了两件事:
-
确定初始化路由
-
执行 Dart 的入口代码
我们看第一处,第一处只有方法名没有方法的参数,对吧! 方法的名字是什么呢?
public String getDartEntrypointFunctionName() {
try {
Bundle metaData = getMetaData();
String desiredDartEntrypoint =
metaData != null ? metaData.getString(DART_ENTRYPOINT_META_DATA_KEY) : null;
return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT;
} catch (PackageManager.NameNotFoundException e) {
return DEFAULT_DART_ENTRYPOINT;
}
}
就是 io.flutter.Entrypoint
对应的 value
,默认是就是 main
这就是为什么可以执行到 Flutter 的 main 方法,这个过程中没有 main 方法的参数设置,所以 Flutter 的 main
方法不支持设置参数。
小结
Dart
程序的入口方法是 main
方法,支持异步 、入参等特性。但是由于 Flutter
的特殊性,异步并不常用,由于引擎的限制,参数并不支持。
Flutter
的 main
方法 是 FlutterActivity
或者 FlutterFragment
的 onStart
方法中调用的,调用的方式是 DartExecutor
来执行。
Flutter main 方法执行了啥
void main() {
runApp(MyApp());
}
这是默认的 main
方法,这是最简单的 main
,在我们的项目中我们可能会处理很多其他操作,但是最核心的就是一句话:runApp
。
下面我们来看其中的逻辑。
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized() // 第一处
..scheduleAttachRootWidget(app) //第二处
..scheduleWarmUpFrame(); //第三处
}
上面的三处代码就是三件事:WidgetsFlutterBinding
初始化,三棵树初始化并绑定,发布预热帧。
就是这三行代码运行了 Flutter
的工程。
WidgetsFlutterBinding 初始化
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance!;
}
}
ensureInitialized
确保初始化,就是一个确保单例的过程。
第一次执行 ensureInitialized
方法的时候,会走 BindingBase
及其子类的构造方法完成初始化,
确保 Flutter
项目完成初始化并只完成一次。
每一个 Binding
完成的初始化内容如下:
Binding 名 | 作用 |
---|---|
BindingBase | 初始化基类,规定了初始化的框架。initInstances 中完成初始化,比如单例等initServiceExtensions 完成服务注册初始化 |
GestureBinding | 初始化手势识别 和 手势追踪框架 |
SchedulerBinding | 初始化 帧调用任务 |
ServicesBinding | 初始化 插件通道、系统的插件。 |
PaintingBinding | 初始化 图片缓存 |
SemanticsBinding | 初始化 语义框架 |
RendererBinding | 初始化 渲染机制 和 根 RenderObject |
WidgetsBinding | 初始化 Element 机制 和 Debug 显示机制 |
这一篇文章只介绍宏观上的流程,下一节详细介绍各种 Binding
的初始化内容。
绑定根 Widget
runApp
接受一个 Widget
参数,这个 Widget
就是我们的根 Widget
。
我们来看这个 Widget
是怎么绑定到根上的。
void scheduleAttachRootWidget(Widget rootWidget) {
Timer.run(() {
attachRootWidget(rootWidget);
});
}
仅仅调用了 attach
的方法。其方法内部如下:
void attachRootWidget(Widget rootWidget) {
final bool isBootstrapFrame = renderViewElement == null;
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
if (isBootstrapFrame) {
SchedulerBinding.instance!.ensureVisualUpdate();
}
}
总结下来就是:先判断是否已经绑定过了。
根据 renderView
和 rootWidget
来生成一个 RenderObjectToWidgetAdapter
,RenderObjectToWidgetAdapter
是 Element
。三颗树的关系可以看这里。
如果没有绑定过,那么就通过 SchedulerBinding
发起帧的调度和绘制流程。
预热帧
scheduleWarmUpFrame
之前的两行代码,完成了所有的准备工作。
scheduleWarmUpFrame
的作用就是尽可能快的把 Flutter
内容显示出来。
我们知道屏幕的显示是根据 Vsync
信号的,比如下面这张图:
每次收到 Vsync
之后,会进行一系列的计算等,然后显示出来。
scheduleWarmUpFrame
的作用就是不用等待下次的 Vsync
,而是直接发起绘制。
发起绘制的是什么意思呢?就是安排帧。
void scheduleWarmUpFrame() {
/// 省略代码
handleBeginFrame(null);
/// 省略代码
handleDrawFrame();
/// 省略代码
}
省去了一些其他的准备和判断代码,handleBeginFrame
和 handleDrawFrame
是核心代码。这两个方法是整个帧调度的核心,我们放在后面详细讲。
小结
Flutter
的 main
方法完成了前期的准备,三棵树的根节点的生成和绑定,发起预热帧三项任务。和前面的流程加起来就是:
总结
Flutter
程序的入口是 main
方法,调用 main
的地方是 Flutter 容器决定的。然后在 main 方法中执行了一系列的 Binding 初始化 和 根结点绑定的任务。做完这些铺垫,下一篇就看 Binding 是啥。
转载自:https://juejin.cn/post/7083327115734548516