Commit 24ab8372 authored by John McCutchan's avatar John McCutchan Committed by GitHub

Support hot reload for applications that don't use the framework (#5868)

parent 05862000
...@@ -107,6 +107,10 @@ abstract class BindingBase { ...@@ -107,6 +107,10 @@ abstract class BindingBase {
name: 'exit', name: 'exit',
callback: _exitApplication callback: _exitApplication
); );
registerSignalServiceExtension(
name: 'frameworkPresent',
callback: () => null
);
assert(() { _debugServiceExtensionsRegistered = true; return true; }); assert(() { _debugServiceExtensionsRegistered = true; return true; });
} }
...@@ -124,6 +128,7 @@ abstract class BindingBase { ...@@ -124,6 +128,7 @@ abstract class BindingBase {
FlutterError.resetErrorCount(); FlutterError.resetErrorCount();
} }
/// Registers a service extension method with the given name (full /// Registers a service extension method with the given name (full
/// name "ext.flutter.name"), which takes no arguments and returns /// name "ext.flutter.name"), which takes no arguments and returns
/// no value. /// no value.
......
...@@ -414,20 +414,26 @@ class HotRunner extends ResidentRunner { ...@@ -414,20 +414,26 @@ class HotRunner extends ResidentRunner {
firstFrameTimer.start(); firstFrameTimer.start();
await _updateDevFS(); await _updateDevFS();
await _launchFromDevFS(_package, _mainPath); await _launchFromDevFS(_package, _mainPath);
bool waitForFrame =
await currentView.uiIsolate.flutterFrameworkPresent();
Status restartStatus = Status restartStatus =
logger.startProgress('Waiting for application to start...'); logger.startProgress('Waiting for application to start...');
if (waitForFrame) {
// Wait for the first frame to be rendered. // Wait for the first frame to be rendered.
await firstFrameTimer.firstFrame(); await firstFrameTimer.firstFrame();
}
restartStatus.stop(showElapsedTime: true); restartStatus.stop(showElapsedTime: true);
if (waitForFrame) {
printStatus('Restart time: ' printStatus('Restart time: '
'${getElapsedAsMilliseconds(firstFrameTimer.elapsed)}'); '${getElapsedAsMilliseconds(firstFrameTimer.elapsed)}');
if (benchmarkMode) { if (benchmarkMode) {
benchmarkData['hotRestartMillisecondsToFrame'] = benchmarkData['hotRestartMillisecondsToFrame'] =
firstFrameTimer.elapsed.inMilliseconds; firstFrameTimer.elapsed.inMilliseconds;
} }
flutterUsage.sendEvent('hot', 'restart');
flutterUsage.sendTiming('hot', 'restart', firstFrameTimer.elapsed); flutterUsage.sendTiming('hot', 'restart', firstFrameTimer.elapsed);
} }
flutterUsage.sendEvent('hot', 'restart');
}
/// Returns [true] if the reload was successful. /// Returns [true] if the reload was successful.
bool _printReloadReport(Map<String, dynamic> reloadReport) { bool _printReloadReport(Map<String, dynamic> reloadReport) {
...@@ -489,14 +495,24 @@ class HotRunner extends ResidentRunner { ...@@ -489,14 +495,24 @@ class HotRunner extends ResidentRunner {
await _evictDirtyAssets(); await _evictDirtyAssets();
Status reassembleStatus = Status reassembleStatus =
logger.startProgress('Reassembling application...'); logger.startProgress('Reassembling application...');
bool waitForFrame = true;
try { try {
await currentView.uiIsolate.flutterReassemble(); waitForFrame = (await currentView.uiIsolate.flutterReassemble() != null);
} catch (_) { } catch (_) {
reassembleStatus.stop(showElapsedTime: true); reassembleStatus.stop(showElapsedTime: true);
printError('Reassembling application failed.'); printError('Reassembling application failed.');
return false; return false;
} }
reassembleStatus.stop(showElapsedTime: true); reassembleStatus.stop(showElapsedTime: true);
try {
/* ensure that a frame is scheduled */
await currentView.uiIsolate.uiWindowScheduleFrame();
} catch (_) {
/* ignore any errors */
}
if (waitForFrame) {
// When the framework is present, we can wait for the first frame
// event and measure reload itme.
await firstFrameTimer.firstFrame(); await firstFrameTimer.firstFrame();
printStatus('Hot reload time: ' printStatus('Hot reload time: '
'${getElapsedAsMilliseconds(firstFrameTimer.elapsed)}'); '${getElapsedAsMilliseconds(firstFrameTimer.elapsed)}');
...@@ -505,6 +521,7 @@ class HotRunner extends ResidentRunner { ...@@ -505,6 +521,7 @@ class HotRunner extends ResidentRunner {
firstFrameTimer.elapsed.inMilliseconds; firstFrameTimer.elapsed.inMilliseconds;
} }
flutterUsage.sendTiming('hot', 'reload', firstFrameTimer.elapsed); flutterUsage.sendTiming('hot', 'reload', firstFrameTimer.elapsed);
}
return true; return true;
} }
......
...@@ -7,6 +7,7 @@ import 'dart:convert' show BASE64; ...@@ -7,6 +7,7 @@ import 'dart:convert' show BASE64;
import 'dart:io'; import 'dart:io';
import 'package:json_rpc_2/json_rpc_2.dart' as rpc; import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
import 'package:json_rpc_2/error_code.dart' as rpc_error_code;
import 'package:web_socket_channel/io.dart'; import 'package:web_socket_channel/io.dart';
import 'globals.dart'; import 'globals.dart';
...@@ -766,12 +767,28 @@ class Isolate extends ServiceObjectOwner { ...@@ -766,12 +767,28 @@ class Isolate extends ServiceObjectOwner {
// Flutter extension methods. // Flutter extension methods.
// Invoke a flutter extension method, if the flutter extension is not
// available, returns null.
Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw(
String method, [Map<String, dynamic> params]) async {
try {
return await invokeRpcRaw(method, params);
} catch (e) {
// If an application is not using the framework
if (_isMethodNotFoundException(e))
return null;
rethrow;
}
}
// Debug dump extension methods.
Future<Map<String, dynamic>> flutterDebugDumpApp() { Future<Map<String, dynamic>> flutterDebugDumpApp() {
return invokeRpcRaw('ext.flutter.debugDumpApp'); return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpApp');
} }
Future<Map<String, dynamic>> flutterDebugDumpRenderTree() { Future<Map<String, dynamic>> flutterDebugDumpRenderTree() {
return invokeRpcRaw('ext.flutter.debugDumpRenderTree'); return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpRenderTree');
} }
// Loader page extension methods. // Loader page extension methods.
...@@ -797,19 +814,35 @@ class Isolate extends ServiceObjectOwner { ...@@ -797,19 +814,35 @@ class Isolate extends ServiceObjectOwner {
}).catchError((dynamic error) => null); }).catchError((dynamic error) => null);
} }
/// Causes the application to pick up any changed code. static bool _isMethodNotFoundException(dynamic e) {
Future<Map<String, dynamic>> flutterReassemble() { return (e is rpc.RpcException) &&
return invokeRpcRaw('ext.flutter.reassemble'); (e.code == rpc_error_code.METHOD_NOT_FOUND);
} }
Future<Map<String, dynamic>> flutterEvictAsset(String assetPath) { // Reload related extension methods.
return invokeRpcRaw('ext.flutter.evict', <String, dynamic>{ Future<Map<String, dynamic>> flutterReassemble() async {
return await invokeFlutterExtensionRpcRaw('ext.flutter.reassemble');
}
Future<bool> flutterFrameworkPresent() async {
return (await invokeFlutterExtensionRpcRaw('ext.flutter.frameworkPresent') != null);
}
Future<Map<String, dynamic>> uiWindowScheduleFrame() async {
return await invokeFlutterExtensionRpcRaw('ext.ui.window.scheduleFrame');
}
Future<Map<String, dynamic>> flutterEvictAsset(String assetPath) async {
return await invokeFlutterExtensionRpcRaw('ext.flutter.evict',
<String, dynamic>{
'value': assetPath 'value': assetPath
}); }
);
} }
Future<Map<String, dynamic>> flutterExit() { // Application control extension methods.
return invokeRpcRaw('ext.flutter.exit').timeout( Future<Map<String, dynamic>> flutterExit() async {
return await invokeFlutterExtensionRpcRaw('ext.flutter.exit').timeout(
const Duration(seconds: 2), onTimeout: () => null); const Duration(seconds: 2), onTimeout: () => null);
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment