当我们创建一个Flutter项目,发生了什么?当我们在终端运行它,又发生了什么?
创世纪·新建项目

地球形成后,经过了十亿年的雷鸣电闪、火山喷发,才在原始海洋中终于出现第一种生命,它的名字叫蓝绿藻(Blue—green Algae)。
同理,环境搭建后,也经历了数万个步骤,才形成开发者看到的工程。而这一切,都从一座神秘的桥梁说起,它的名字叫壳层(Shell)。
当我们在终端敲下flutter后,系统在环境变量寻找这个命令,瞧,它就躲在这里(如:~/flutter/bin):
$ tree bin -L 1
bin
├── cache
├── flutter
├── flutter.bat
└── internal
现在打开bin/flutter(Windows平台则是bin/flutter.bat)一探究竟,到底做了什么呢?关键源码及解读如下:
#!/usr/bin/env bash
# 定义解释器,相当于指定一个翻译官
# 省略工具函数与变量声明,约130行
if [[ "$EUID" == "0" && ! -f /.dockerenv ]]; then
# 就只是给个警告,其它啥也不做...
fi
if ! hash git 2>/dev/null; then
# 神马?连版本控制都没有,那我死给你看!
fi
if [[ ! -e "$FLUTTER_ROOT/.git" ]]; then
# 虽然可以运行,但没经过官方认证,我也不陪你玩!
fi
(upgrade_flutter) 3< "$PROG_NAME"
# 如果不是最新的稳定版,提示你立即升级
"$DART" --packages="$FLUTTER_TOOLS_DIR/.packages" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"
# 解析传入的命令参数,如doctor、run、build等
小结一下,任何一次执行flutter命令,会经历如下几个步骤
- 检查操作系统与执行权限(
dart-sdk) - 确认当前分支是最新代码(
git rev-parse HEAD) - 如需要更新,创建临时锁(
.upgrade_lock) - 更新结束后,解开临时锁(
.upgrade_lock) - 传递参数给Dart解释器(
flutter_tools)
人类文明·工程目录

太古宙的藻类生命只是最早期的原核生物,从当年的元古宙到如今的显生宙,又经历了寒武纪、侏罗纪、白垩纪,大约过了35亿年,才形成了今天我们看到的世界。
同理,Shell也是最早期的人机交互,往往只有最基础的特性。要构建一个完整、可读、健壮的项目,仍旧有许多工作要做,它需要另外一个内置的二进制解释器,文件路径为/flutter/bin/cache/dart-sdk/dart
有了这个神器,我们就可以跳出系统工具级文明,迈入系统应用级文明啦。现在,以flutter create myproject为例,看看这个解释器做了什么。
入口文件是/flutter/packages/flutter_tools/lib/executable.dart,核心代码如下:
Future<void> main(List<String> args) async {
final bool verbose = args.contains('-v') || args.contains('--verbose');
// 打印详细日志,不留任何死角,妈妈再也不用担心我找不到Bug
await runner.run(args, <FlutterCommand>[
CreateCommand(),
// 当然还其它很多技能的,比如常用运行和打包
], verbose: verbose,
overrides: <Type, Generator>{
// 不同的平台底层差异还是较大的,这些都是适配器
});
}
很常规的操作,主要是解析并且保存参数,然后分而治之,下面看CreateCommand具体的定义:
class CreateCommand extends FlutterCommand {
@override
Future<FlutterCommandResult> runCommand() async {
// 我店推出四款新菜,老板喜欢哪一道?
switch (template) {
case _ProjectType.app:
generatedFileCount += await _generateApp(relativeDir, templateContext, overwrite: argResults['overwrite']);
break;
case _ProjectType.module:
generatedFileCount += await _generateModule(relativeDir, templateContext, overwrite: argResults['overwrite']);
break;
case _ProjectType.package:
generatedFileCount += await _generatePackage(relativeDir, templateContext, overwrite: argResults['overwrite']);
break;
case _ProjectType.plugin:
generatedFileCount += await _generatePlugin(relativeDir, templateContext, overwrite: argResults['overwrite']);
break;
}
if (sampleCode != null) {
generatedFileCount += await _applySample(relativeDir, sampleCode);
}
}
}
String _createAndroidIdentifier(String organization, String name){}
String _createPluginClassName(String name) {}
String _createUTIIdentifier(String organization, String name) {}
限于篇幅,代码细节不再展开,直接小结这700多行代码做的事情:
- 硬性检查,如文件路径、网络状态,
Gradle环境 - 软性检查,如标识符命名是否合法、是否冲突
- 从网络下载模板生成器,传入参数并执行
- 执行
_renderTemplate生成原生工程 - 生成IDE配置文件,当前仅支持
IntelliJ - 执行
doctor.summary(),检测环境 - 创建
lib/main.dart并写入初始内容 - 打印结束语,提示开发者下一步操作
至此项目就搭建完成了,是不是意料之中,是不是毫无亮点,是不是似曾相识?
# 切图仔(前端):创建React App项目
$ npx create-react-app my-app
# 接口佬(后端):创建Spring Boot项目
$ spring init --c=web,data-jpa my-project
# 背锅侠(运维):创建Docker应用
$ docker app init app-name
是的,这就是常说的工程化/自动化,不论是切图仔、接口佬还是背锅侠,开发脚手架都应该遵循这套机制:校验、拉取、赋值、执行。
那么Flutter引以为傲的是什么呢?凭什么颠覆现有的技术选型?要解答这个问题,让我们再一次回到上帝视角,俯瞰人类文明史。
工业革命·引擎核心

同理,技术领域也是从动力开始引发变革。最初是硬件领域,比如曾经的Pentium(奔腾)时代,曾经的Corel(酷睿)时代,以及今天Snapdragon(骁龙)时代。算力的显著提升,推动者软件领域持续变革,当然软件也会反推这硬件继续进步,也就是著名的安迪比尔定律:
Andy gives, Bill takes away
言归正传,要让动力利用更加高效,引擎是最核心的。这是一张被社区反复的引用官方架构图,引擎是衔接底层编译器与底层框架最核心的要素:

把引擎源码拉下来后,我们重点关注shell层(因为它是连接开发者与内核交互的桥梁),它的目录结构是这样的:
$ tree -d shell -L 2
shell
├── common
│ └── fixtures
├── gpu
├── platform
│ ├── android
│ ├── common
│ ├── darwin
│ ├── embedder
│ ├── fuchsia
│ ├── glfw
│ ├── linux
│ └── windows
├── testing
│ └── observatory
└── version
首先来安卓平台,各个层级的入口代码都相当简单,简单来说就是众多初始化与注册工作,文件路径为:engine\src\flutter\shell\platform\android\io\flutter\app\FlutterActivity.java
// 安卓端应用入口
public class FlutterApplication extends Application {
@Override
@CallSuper
public void onCreate() {
super.onCreate();
FlutterMain.startInitialization(this);
}
}
// 安卓端-界面入口
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
那么FlutterActivity是不是又臭又长,非也,无非是增加或覆盖了一些生命周期罢了,相对目录同上:
public class FlutterActivity extends Activity implements FlutterView.Provider, PluginRegistry, ViewFactory {
// 这是像是仙界的南天门,或者地府的黄泉路,dart与原生所有通信都必须经过这里
private final FlutterActivityDelegate delegate = new FlutterActivityDelegate(this, this);
// 三种不同类型的通讯方式:系统事件、视图刷新、内置或第三方插件
private final FlutterActivityEvents eventDelegate = delegate;
private final FlutterView.Provider viewProvider = delegate;
private final PluginRegistry pluginRegistry = delegate;
// 现在我还不知道要创建什么鬼,但必须要有,等你dart文件执行完就知道了
@Override
public FlutterView createFlutterView(Context context) {
return null;
}
// 覆盖默认的生命周期,等待业务自定义
@Override
protected void onDestroy() {
eventDelegate.onDestroy();
super.onDestroy();
}
}
需要特别注意的是,FlutterView是继承自SurfaceView,跟普通的View不同,它开启子线程来刷新页面,绘图时有双缓冲机制。所以在60H主流显示屏上,所以理论上能在16.7ms内完成绘制。另外,从源码可以得知,Flutter只创建了唯一一个Activity,这有点像前端主流的单页应用,算是继承了前两代技术的优良传统?
不过问题就来了,今天新设备已经开始支持120Hz,那使用现有的引擎机制会不会出现丢帧问题? 如果是单页应用,那和原生工程整合后互相跳转是不是特别痛苦?确实是的,我们的项目就遇到了,且暂时没有完美解决方案,等待官方大神填坑吧。
然后是iOS平台,启动机制只是语法的差异,思想是及其相似的(主要是笔者对Swift虽然略懂,但实在讨厌Objective-C的风格,所以无法坚持细看),都有一个事件注册中心统一处理所有事务,文件路径为:/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm,故在此不作赘述。
至此,Flutter Engine的shell部分告一段落了,最后附一张@stephenwzl同学整理的图:

活在当下·生态建设
作为八千年文明的一滴水,作为七十亿人口的一粒沙,普通个体的力量是微乎其微的,谁也无法以一己之力改变人类进程。
同理,作为一名底层搬砖工,要自建一个引擎是极为艰难的,要自创一门语言更加难于登天。我们现在要做的就是为这栋大厦不断添砖加瓦,让其生态更加繁荣。
所以,欢迎加入OpenFlutter同性恋社区,或者加入Flutter 学习小组微信群,携手共建生态吧~
