1. 前言
Flutter 亚秒级别的热重载是开发者的神兵利器,它能够提供我们快速修改UI、增加功能、修复bug,不需要重新启动应用,就可以看到改动效果。那么Flutter是如何做到这样的功能的呢?下面我们一起探究一下其中的原理。
2. 首先需要知道JIT 和AOT
JIT
JIT(Just In Time),指的是运行时编译,Flutter 在debug模式下采用这种方式,在运行时动态下发和执行代码,启动速度快,但是由于在运行时编译,性能会受影响。
AOT
AOT(Ahead Of Time),指的是在运行之前进行编译,Flutter 在Release模式下采用,可以为特定的平台生成稳定的二进制代码、执行性能好、运行速度快,但是每次都需要重新运行编译,开发效率低。
所以,Flutter提供的两种编译模式中,AOT 是静态编译,编译成设备直接可执行的二进制代码;而JIT 则是先生成中间代码(Script snapshot),然后通过dart Vm 解释执行。
3. Hot Reload 原理
- 扫描修改的文件;
- 生成kernal file(app.dill.incremental.dill);
- 将文件通过Http协议下发到dartVM;
- VM服务通过RPC 调用_reloadSources,进行资源重载;
- VM 资源加载成功,将FlutterDevice UI线程重置(uiIsolate),通过RPC调用,触发Flutter树的重建、重绘。
如下图形象说明:
[图片上传失败...(image-1e9489-1597131036977)]
4. 源码分析
通过命令行输入r或者触发闪电按钮,其实会触发flutter_tools的run_hot文件的HotRunner的restart() 方法(位于flutter/pakages/flutter_tools/);
flutter_tools 调试步骤:
1)用AS打开Flutter_tools,打开 Run/Debug Configurations; [图片上传失败...(image-b2e623-1597131036977)]
2) 添加Dart Command Line App, Name 设置为Flutter Tools Debugger, Dart file 设置为flutter_tools的文件目录,Working directory 设置为一个测试项目路径
3) 运行Debug,运行完成后,断点打在HotRunner 的restart()方法处。
4)随便修改测试项目的Widget,在flutter_tools 工程的Console输入r回车;发现断点断在了restart方法。下面我们从restart()方法开始逐步分析,热重载的原理。
- restart()方法
@override
Future restart({
bool fullRestart = false,
bool pauseAfterRestart = false,
String reason,
bool benchmarkMode = false
}) async {
String targetPlatform;
String sdkName;
bool emulator;
// 判断当前的设备,并且获取当前设备的targetPlatform、sdkName、emulator
if (flutterDevices.length == 1) {
final Device device = flutterDevices.first.device;
targetPlatform = getNameForTargetPlatform(await device.targetPlatform);
sdkName = await device.sdkNameAndVersion;
emulator = await device.isLocalEmulator;
} else if (flutterDevices.length > 1) {
targetPlatform = 'multiple';
sdkName = 'multiple';
emulator = false;
} else {
targetPlatform = 'unknown';
sdkName = 'unknown';
emulator = false;
}
final Stopwatch timer = Stopwatch()..start();
if (fullRestart) {
... 完全重新启动、非热重载部分省略
return;
}
final OperationResult result = await _hotReloadHelper(
targetPlatform: targetPlatform,
sdkName: sdkName,
emulator: emulator,
reason: reason,
pauseAfterRestart: pauseAfterRestart,
);
if (result.isOk) {
final String elapsed = getElapsedAsMilliseconds(timer.elapsed);
printStatus('${result.message} in $elapsed.');
}
return result;
}
调用到restart方法之后,会获取当前设备的一些信息,然后根据是否是fullRestart执行不同流程,我们研究的是Hot reload不是fullRestart所以我们走的是下边的流程,执行_hotReloadHelper();
-
_hotReloadHelper
Future
_hotReloadHelper({ String targetPlatform, String sdkName, bool emulator, String reason, bool pauseAfterRestart = false, }) async { final bool reloadOnTopOfSnapshot = _runningFromSnapshot; final String progressPrefix = reloadOnTopOfSnapshot ? 'Initializing' : 'Performing'; Status status = logger.startProgress( '$progressPrefix hot reload...', timeout: timeoutConfiguration.fastOperation, progressId: 'hot.reload', ); OperationResult result; try { result = await _reloadSources( targetPlatform: targetPlatform, sdkName: sdkName, emulator: emulator, pause: pauseAfterRestart, reason: reason, onSlow: (String message) { status?.cancel(); status = logger.startProgress( message, timeout: timeoutConfiguration.slowOperation, progressId: 'hot.reload', ); }, ); } on rpc.RpcException { HotEvent('exception', targetPlatform: targetPlatform, sdkName: sdkName, emulator: emulator, fullRestart: false, reason: reason).send(); return OperationResult(1, 'hot reload failed to complete', fatal: true); } finally { status.cancel(); } return result; } 这个方法呢,其实也是对于核心逻辑方法_reloadSources()方法的封装。
-
_reloadSources()
Future
_reloadSources({ String targetPlatform, String sdkName, bool emulator, bool pause = false, String reason, void Function(String message) onSlow }) async { // 遍历所有Device的FlutterView, 并且uiIsolate 不存在的话,执行失败 for (FlutterDevice device in flutterDevices) { for (FlutterView view in device.views) { if (view.uiIsolate == null) { return OperationResult(2, 'Application isolate not found', fatal: true); } } } bool shouldReportReloadTime = !_runningFromSnapshot; // 开启计时器 final Stopwatch reloadTimer = Stopwatch()..start(); // 判断FlutterView的uiIsolate是否为null或者处于暂停状态,如果是刷新所有view if (!_isPaused()) { printTrace('Refreshing active FlutterViews before reloading.'); // 触发所有设备的vmService RPC 发送_flutter.listViews消息 await refreshViews(); } final Stopwatch devFSTimer = Stopwatch()..start(); // 同步修改的文件 final UpdateFSReport updatedDevFS = await _updateDevFS(); // Record time it took to synchronize to DevFS. _addBenchmarkData('hotReloadDevFSSyncMilliseconds', devFSTimer.elapsed.inMilliseconds); if (!updatedDevFS.success) { return OperationResult(1, 'DevFS synchronization failed'); } String reloadMessage; final Stopwatch vmReloadTimer = Stopwatch()..start(); Map firstReloadDetails; try { final String entryPath = fs.path.relative( getReloadPath(fullRestart: false), from: projectRootPath, ); final List > allReportsFutures = >[]; for (FlutterDevice device in flutterDevices) { if (_runningFromSnapshot) { // Asset directory has to be set only once when we switch from // running from snapshot to running from uploaded files. await device.resetAssetDirectory(); } final Completer completer = Completer (); allReportsFutures.add(completer.future); // 触发RPC 调用_reloadSources 重新加载资源 final List >> reportFutures = device.reloadSources( entryPath, pause: pause, ); // 处理RPC 调用_reloadSources 返回结果 unawaited(Future.wait(reportFutures).then( (List
这个方法主要干了 5件事情
1. 扫描修改的文件,生成dill文件,并且通过Http服务下发到设备资源文件;
2. RPC 调用_reloadSources 触发VM重新加载修改后的文件
3. RPC 调用 flutter View的uiIsolate refreshView
4. 删除dirty文件
5. rpc触发所有的FlutterView uiIsolate ressemble
5. 不会发生Hot Reload的场景
应用被杀死
-
编译错误
当代码更改导致编译错误时,热重载会生成类似于以下内容的错误消息:
Hot reload was rejected: '/Users/obiwan/Library/Developer/CoreSimulator/Devices/AC94F0FF-16F7-46C8-B4BF-218B73C547AC/data/Containers/Data/Application/4F72B076-42AD-44A4-A7CF-57D9F93E895E/tmp/ios_testWIDYdS/ios_test/lib/main.dart': warning: line 16 pos 38: unbalanced '{' opens here Widget build(BuildContext context) { ^ '/Users/obiwan/Library/Developer/CoreSimulator/Devices/AC94F0FF-16F7-46C8-B4BF-218B73C547AC/data/Containers/Data/Application/4F72B076-42AD-44A4-A7CF-57D9F93E895E/tmp/ios_testWIDYdS/ios_test/lib/main.dart': error: line 33 pos 5: unbalanced ')' );
在这种情况下,只需更正上述代码的错误,即可以继续使用热重载
- CupertinoTabView's builder
在修改了CupertinoTableView 的builder内容时,Hot reload 不会生效
- 枚举类型改变成class 类型
当一个枚举类型,改成一个类时,Hot reload 不会生效
字体修改
泛型修改
修改前
class A
{ T i; } 修改后
class A
{ T i; V v; }
7. Native code
更改原生代码, hot reload 不会生效
8. statelessWidget 和 statefulWidget 的互改
9. 静态变量和全局变量的改变
参考
Flutter Hot Reload doc
深入理解flutter的编译原理与优化
揭秘Flutter Hot Reload(原理篇)
底层原理 - Flutter Hot Reload 详解
美团-Flutter原理与实践
头条开发攻城狮-深入理解Dart虚拟机启动
Dart VM 简介
深入浅出RPC原理