[flutter_tool] Reland: support --fast-start for Android applications (as an opt-in) (#46140)
diff --git a/dev/bots/test.dart b/dev/bots/test.dart
index d0e8014..a98d0ef 100644
--- a/dev/bots/test.dart
+++ b/dev/bots/test.dart
@@ -279,6 +279,13 @@
await selectSubshard(subshards);
}
+// Example apps that should not be built by _runBuildTests`
+const List<String> _excludedExampleApplications = <String>[
+ // This application contains no platform code and cannot be built, except for
+ // as a part of a '--fast-start' Android application.
+ 'splash',
+];
+
/// Verifies that AOT, APK, and IPA (if on macOS) builds the examples apps
/// without crashing. It does not actually launch the apps. That happens later
/// in the devicelab. This is just a smoke-test. In particular, this will verify
@@ -290,6 +297,9 @@
if (fileEntity is! Directory) {
continue;
}
+ if (_excludedExampleApplications.any(fileEntity.path.endsWith)) {
+ continue;
+ }
final String examplePath = fileEntity.path;
await _flutterBuildAot(examplePath);
await _flutterBuildApk(examplePath);
@@ -763,6 +773,7 @@
if (Platform.isMacOS) () => _runDevicelabTest('flutter_create_offline_test_mac'),
if (Platform.isLinux) () => _runDevicelabTest('flutter_create_offline_test_linux'),
if (Platform.isWindows) () => _runDevicelabTest('flutter_create_offline_test_windows'),
+ () => _runDevicelabTest('gradle_fast_start_test', environment: gradleEnvironment),
// TODO(ianh): Fails on macOS looking for "dexdump", https://github.com/flutter/flutter/issues/42494
if (!Platform.isMacOS) () => _runDevicelabTest('gradle_jetifier_test', environment: gradleEnvironment),
() => _runDevicelabTest('gradle_non_android_plugin_test', environment: gradleEnvironment),
diff --git a/dev/devicelab/bin/tasks/gradle_fast_start_test.dart b/dev/devicelab/bin/tasks/gradle_fast_start_test.dart
new file mode 100644
index 0000000..73fdf12
--- /dev/null
+++ b/dev/devicelab/bin/tasks/gradle_fast_start_test.dart
@@ -0,0 +1,42 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter_devicelab/framework/apk_utils.dart';
+import 'package:flutter_devicelab/framework/framework.dart';
+import 'package:flutter_devicelab/framework/utils.dart';
+
+Future<void> main() async {
+ await task(() async {
+ try {
+ await runPluginProjectTest((FlutterPluginProject pluginProject) async {
+ section('APK content for task assembleDebug with --fast-start');
+ await pluginProject.runGradleTask('assembleDebug',
+ options: <String>['-Pfast-start=true']);
+
+ final Iterable<String> apkFiles = await getFilesInApk(pluginProject.debugApkPath);
+
+ checkCollectionContains<String>(<String>[
+ ...debugAssets,
+ ...baseApkFiles,
+ 'lib/x86/libflutter.so',
+ 'lib/x86_64/libflutter.so',
+ 'lib/armeabi-v7a/libflutter.so',
+ 'lib/arm64-v8a/libflutter.so',
+ ], apkFiles);
+
+ checkCollectionDoesNotContain<String>(<String>[
+ ...flutterAssets,
+ ], apkFiles);
+ });
+
+ return TaskResult.success(null);
+ } on TaskResult catch (taskResult) {
+ return taskResult;
+ } catch (e) {
+ return TaskResult.failure(e.toString());
+ }
+ });
+}
diff --git a/examples/splash/lib/main.dart b/examples/splash/lib/main.dart
new file mode 100644
index 0000000..3f55136
--- /dev/null
+++ b/examples/splash/lib/main.dart
@@ -0,0 +1,16 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+
+void main() {
+ runApp(
+ const DecoratedBox(
+ decoration: BoxDecoration(color: Colors.white),
+ child: Center(
+ child: FlutterLogo(size: 48),
+ ),
+ ),
+ );
+}
diff --git a/examples/splash/pubspec.yaml b/examples/splash/pubspec.yaml
new file mode 100644
index 0000000..40a17ca
--- /dev/null
+++ b/examples/splash/pubspec.yaml
@@ -0,0 +1,41 @@
+name: splash
+
+environment:
+ # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite.
+ sdk: ">=2.0.0-dev.68.0 <3.0.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+
+ collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+ archive: 2.0.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ args: 1.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ async: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ image: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ path: 1.6.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ pedantic: 1.8.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ petitparser: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ quiver: 2.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ source_span: 1.5.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ stack_trace: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ stream_channel: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ string_scanner: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ test_api: 0.2.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ xml: 3.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+
+# PUBSPEC CHECKSUM: f789
diff --git a/examples/splash/test/splash_test.dart b/examples/splash/test/splash_test.dart
new file mode 100644
index 0000000..e1d597f
--- /dev/null
+++ b/examples/splash/test/splash_test.dart
@@ -0,0 +1,16 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:splash/main.dart' as entrypoint;
+
+void main() {
+ testWidgets('Displays flutter logo', (WidgetTester tester) async {
+ entrypoint.main();
+
+ expect(find.byType(FlutterLogo), findsOneWidget);
+ });
+}
diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle
index fde4000..e852f2f 100644
--- a/packages/flutter_tools/gradle/flutter.gradle
+++ b/packages/flutter_tools/gradle/flutter.gradle
@@ -472,6 +472,15 @@
return false
}
+ /// Whether to build the debug app in "fast-start" mode.
+ private Boolean isFastStart() {
+ if (project.hasProperty("fast-start")) {
+ return project.property("fast-start").toBoolean()
+ }
+ return false
+ }
+
+
private static Boolean shouldShrinkResources(Project project) {
if (project.hasProperty("shrink")) {
return project.property("shrink").toBoolean()
@@ -610,6 +619,7 @@
localEngineSrcPath this.localEngineSrcPath
targetPath target
verbose isVerbose()
+ fastStart isFastStart()
fileSystemRoots fileSystemRootsValue
fileSystemScheme fileSystemSchemeValue
trackWidgetCreation trackWidgetCreationValue
@@ -729,6 +739,8 @@
String localEngine
String localEngineSrcPath
@Input
+ Boolean fastStart
+ @Input
String targetPath
@Optional
Boolean verbose
@@ -769,9 +781,13 @@
// cache.
String[] ruleNames;
if (buildMode == "debug") {
- ruleNames = ["debug_android_application"]
+ if (fastStart) {
+ ruleNames = ["faststart_android_application"]
+ } else {
+ ruleNames = ["debug_android_application"]
+ }
} else {
- ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" }
+ ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" }
}
project.exec {
executable flutterExecutable.absolutePath
@@ -788,7 +804,11 @@
args "assemble"
args "--depfile", "${intermediateDir}/flutter_build.d"
args "--output", "${intermediateDir}"
- args "-dTargetFile=${targetPath}"
+ if (!fastStart || buildMode != "debug") {
+ args "-dTargetFile=${targetPath}"
+ } else {
+ args "-dTargetFile=${Paths.get(flutterRoot.absolutePath, "examples", "splash", "lib", "main.dart")}"
+ }
args "-dTargetPlatform=android"
args "-dBuildMode=${buildMode}"
if (extraFrontEndOptions != null) {
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 0678841..5a523f6 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -527,6 +527,7 @@
androidBuildInfo: AndroidBuildInfo(
debuggingOptions.buildInfo,
targetArchs: <AndroidArch>[androidArch],
+ fastStart: debuggingOptions.fastStart
),
);
// Package has been built, so we can get the updated application ID and
@@ -644,6 +645,9 @@
bool get supportsHotRestart => true;
@override
+ bool get supportsFastStart => true;
+
+ @override
Future<bool> stopApp(AndroidApk app) {
final List<String> command = adbCommandForDevice(<String>['shell', 'am', 'force-stop', app.id]);
return processUtils.stream(command).then<bool>(
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 7f904c7..4440d98 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -331,6 +331,9 @@
// Don't use settings.gradle from the current project since it includes the plugins as subprojects.
command.add('--settings-file=settings_aar.gradle');
}
+ if (androidBuildInfo.fastStart) {
+ command.add('-Pfast-start=true');
+ }
command.add(assembleTask);
GradleHandledError detectedGradleError;
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart
index 775461a..2f51d60 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -103,6 +103,7 @@
],
this.splitPerAbi = false,
this.shrink = false,
+ this.fastStart = false,
});
// The build info containing the mode and flavor.
@@ -120,6 +121,9 @@
/// The target platforms for the build.
final Iterable<AndroidArch> targetArchs;
+
+ /// Whether to bootstrap an empty application.
+ final bool fastStart;
}
/// A summary of the compilation strategy used for Dart.
diff --git a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
index 36f1898..b13f632 100644
--- a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
+++ b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
@@ -423,7 +423,7 @@
@override
Future<OperationResult> restart({
bool fullRestart = false,
- bool pauseAfterRestart = false,
+ bool pause = false,
String reason,
bool benchmarkMode = false,
}) async {
@@ -692,7 +692,7 @@
@override
Future<OperationResult> restart({
bool fullRestart = false,
- bool pauseAfterRestart = false,
+ bool pause = false,
String reason,
bool benchmarkMode = false,
}) async {
diff --git a/packages/flutter_tools/lib/src/build_system/targets/android.dart b/packages/flutter_tools/lib/src/build_system/targets/android.dart
index 5a030fb..1798d95 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/android.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/android.dart
@@ -31,10 +31,13 @@
List<Source> get outputs => const <Source>[];
@override
- List<String> get depfiles => const <String>[
- 'flutter_assets.d',
+ List<String> get depfiles => <String>[
+ if (_copyAssets)
+ 'flutter_assets.d',
];
+ bool get _copyAssets => true;
+
@override
Future<void> build(Environment environment) async {
if (environment.defines[kBuildMode] == null) {
@@ -56,8 +59,10 @@
fs.file(isolateSnapshotData)
.copySync(outputDirectory.childFile('isolate_snapshot_data').path);
}
- final Depfile assetDepfile = await copyAssets(environment, outputDirectory);
- assetDepfile.writeToFile(environment.buildDir.childFile('flutter_assets.d'));
+ if (_copyAssets) {
+ final Depfile assetDepfile = await copyAssets(environment, outputDirectory);
+ assetDepfile.writeToFile(environment.buildDir.childFile('flutter_assets.d'));
+ }
}
@override
@@ -90,6 +95,17 @@
];
}
+/// A minimal android application that does not include assets.
+class FastStartAndroidApplication extends DebugAndroidApplication {
+ const FastStartAndroidApplication();
+
+ @override
+ String get name => 'faststart_android_application';
+
+ @override
+ bool get _copyAssets => false;
+}
+
/// An implementation of [AndroidAssetBundle] that only includes assets.
class AotAndroidAssetBundle extends AndroidAssetBundle {
const AotAndroidAssetBundle();
diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart
index 74856e3..5265b5c 100644
--- a/packages/flutter_tools/lib/src/commands/assemble.dart
+++ b/packages/flutter_tools/lib/src/commands/assemble.dart
@@ -37,6 +37,7 @@
DebugBundleLinuxAssets(),
WebReleaseBundle(),
DebugAndroidApplication(),
+ FastStartAndroidApplication(),
ProfileAndroidApplication(),
ReleaseAndroidApplication(),
// These are one-off rules for bundle and aot compat
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index 5c1b9db..a53b373 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -578,7 +578,7 @@
}
_inProgressHotReload = app._runInZone<OperationResult>(this, () {
- return app.restart(fullRestart: fullRestart, pauseAfterRestart: pauseAfterRestart, reason: restartReason);
+ return app.restart(fullRestart: fullRestart, pause: pauseAfterRestart, reason: restartReason);
});
return _inProgressHotReload.whenComplete(() {
_inProgressHotReload = null;
@@ -945,8 +945,8 @@
_AppRunLogger _logger;
- Future<OperationResult> restart({ bool fullRestart = false, bool pauseAfterRestart = false, String reason }) {
- return runner.restart(fullRestart: fullRestart, pauseAfterRestart: pauseAfterRestart, reason: reason);
+ Future<OperationResult> restart({ bool fullRestart = false, bool pause = false, String reason }) {
+ return runner.restart(fullRestart: fullRestart, pause: pause, reason: reason);
}
Future<OperationResult> reloadMethod({ String classId, String libraryId }) {
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index fb13cb7..222f207 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -187,6 +187,14 @@
hide: true,
help: 'Whether to automatically invoke webOnlyInitializePlatform.',
)
+ ..addFlag('fast-start',
+ negatable: true,
+ defaultsTo: false,
+ hide: true,
+ help: 'Whether to quickly bootstrap applications with a minimal app. '
+ 'Currently this is only supported on Android devices. This option '
+ 'cannot be paired with --use-application-binary.'
+ )
..addOption(FlutterOptions.kExtraFrontEndOptions, hide: true)
..addOption(FlutterOptions.kExtraGenSnapshotOptions, hide: true)
..addMultiOption(FlutterOptions.kEnableExperiment,
@@ -284,6 +292,11 @@
if (!runningWithPrebuiltApplication) {
await super.validateCommand();
}
+
+ if (boolArg('fast-start') && runningWithPrebuiltApplication) {
+ throwToolExit('--fast-start is not supported with --use-application-binary');
+ }
+
devices = await findAllTargetDevices();
if (devices == null) {
throwToolExit(null);
@@ -322,6 +335,9 @@
hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
vmserviceOutFile: stringArg('vmservice-out-file'),
+ // Allow forcing fast-start to off to prevent doing more work on devices that
+ // don't support it.
+ fastStart: boolArg('fast-start') && devices.every((Device device) => device.supportsFastStart),
);
}
}
@@ -384,6 +400,12 @@
}
for (Device device in devices) {
+ if (!device.supportsFastStart && boolArg('fast-start')) {
+ printStatus(
+ 'Using --fast-start option with device ${device.name}, but this device '
+ 'does not support it. Overriding the setting to false.'
+ );
+ }
if (await device.isLocalEmulator) {
if (await device.supportsHardwareRendering) {
final bool enableSoftwareRendering = boolArg('enable-software-rendering') == true;
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 2b081ba..b335d3e 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -420,6 +420,9 @@
/// application.
bool get supportsScreenshot => false;
+ /// Whether the device supports the '--fast-start' development mode.
+ bool get supportsFastStart => false;
+
/// Stop an app package on the current device.
Future<bool> stopApp(covariant ApplicationPackage app);
@@ -534,6 +537,7 @@
this.hostname,
this.port,
this.vmserviceOutFile,
+ this.fastStart = false,
}) : debuggingEnabled = true;
DebuggingOptions.disabled(this.buildInfo, { this.initializePlatform = true, this.port, this.hostname, this.cacheSkSL = false, })
@@ -550,7 +554,8 @@
verboseSystemLogs = false,
hostVmServicePort = null,
deviceVmServicePort = null,
- vmserviceOutFile = null;
+ vmserviceOutFile = null,
+ fastStart = false;
final bool debuggingEnabled;
@@ -574,6 +579,7 @@
final String hostname;
/// A file where the vmservice URL should be written after the application is started.
final String vmserviceOutFile;
+ final bool fastStart;
bool get hasObservatoryPort => hostVmServicePort != null;
}
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 0402b83..e666af6 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -714,7 +714,7 @@
bool get supportsRestart => false;
- Future<OperationResult> restart({ bool fullRestart = false, bool pauseAfterRestart = false, String reason }) {
+ Future<OperationResult> restart({ bool fullRestart = false, bool pause = false, String reason }) {
final String mode = isRunningProfile ? 'profile' :
isRunningRelease ? 'release' : 'this';
throw '${fullRestart ? 'Restart' : 'Reload'} is not supported in $mode mode';
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 9629942..a3fbe18 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -103,7 +103,7 @@
bool pause = false,
}) async {
// TODO(cbernaschina): check that isolateId is the id of the UI isolate.
- final OperationResult result = await restart(pauseAfterRestart: pause);
+ final OperationResult result = await restart(pause: pause);
if (!result.isOk) {
throw rpc.RpcException(
rpc_error_code.INTERNAL_ERROR,
@@ -114,7 +114,7 @@
Future<void> _restartService({ bool pause = false }) async {
final OperationResult result =
- await restart(fullRestart: true, pauseAfterRestart: pause);
+ await restart(fullRestart: true, pause: pause);
if (!result.isOk) {
throw rpc.RpcException(
rpc_error_code.INTERNAL_ERROR,
@@ -263,6 +263,18 @@
}
}
+ // In fast-start mode, apps are initialized from a placeholder splashscreen
+ // app. We must do a restart here to load the program and assets for the
+ // real app.
+ if (debuggingOptions.fastStart) {
+ await restart(
+ fullRestart: true,
+ benchmarkMode: !debuggingOptions.startPaused,
+ reason: 'restart',
+ silent: true,
+ );
+ }
+
appStartedCompleter?.complete();
if (benchmarkMode) {
@@ -408,12 +420,10 @@
Uri packagesUri,
Uri assetsDirectoryUri,
) {
- final List<Future<void>> futures = <Future<void>>[
- for (FlutterView view in device.views) view.runFromSource(entryUri, packagesUri, assetsDirectoryUri),
- ];
- final Completer<void> completer = Completer<void>();
- Future.wait(futures).whenComplete(() { completer.complete(null); });
- return completer.future;
+ return Future.wait(<Future<void>>[
+ for (FlutterView view in device.views)
+ view.runFromSource(entryUri, packagesUri, assetsDirectoryUri),
+ ]);
}
Future<void> _launchFromDevFS(String mainScript) async {
@@ -510,9 +520,11 @@
for (FlutterDevice device in flutterDevices) {
for (FlutterView view in device.views) {
isolateNotifications.add(
- view.owner.vm.vmService.onIsolateEvent.then((Stream<ServiceEvent> serviceEvents) async {
+ view.owner.vm.vmService.onIsolateEvent
+ .then((Stream<ServiceEvent> serviceEvents) async {
await for (ServiceEvent serviceEvent in serviceEvents) {
- if (serviceEvent.owner.name.contains('_spawn') && serviceEvent.kind == ServiceEvent.kIsolateExit) {
+ if (serviceEvent.owner.name.contains('_spawn')
+ && serviceEvent.kind == ServiceEvent.kIsolateExit) {
return;
}
}
@@ -573,9 +585,10 @@
@override
Future<OperationResult> restart({
bool fullRestart = false,
- bool pauseAfterRestart = false,
String reason,
bool benchmarkMode = false,
+ bool silent = false,
+ bool pause = false,
}) async {
String targetPlatform;
String sdkName;
@@ -602,8 +615,11 @@
emulator: emulator,
reason: reason,
benchmarkMode: benchmarkMode,
+ silent: silent,
);
- printStatus('Restarted application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
+ if (!silent) {
+ printStatus('Restarted application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
+ }
return result;
}
final OperationResult result = await _hotReloadHelper(
@@ -611,11 +627,13 @@
sdkName: sdkName,
emulator: emulator,
reason: reason,
- pauseAfterRestart: pauseAfterRestart,
+ pause: pause,
);
if (result.isOk) {
final String elapsed = getElapsedAsMilliseconds(timer.elapsed);
- printStatus('${result.message} in $elapsed.');
+ if (!silent) {
+ printStatus('${result.message} in $elapsed.');
+ }
}
return result;
}
@@ -626,15 +644,19 @@
bool emulator,
String reason,
bool benchmarkMode,
+ bool silent,
}) async {
if (!canHotRestart) {
return OperationResult(1, 'hotRestart not supported');
}
- final Status status = logger.startProgress(
- 'Performing hot restart...',
- timeout: timeoutConfiguration.fastOperation,
- progressId: 'hot.restart',
- );
+ Status status;
+ if (!silent) {
+ status = logger.startProgress(
+ 'Performing hot restart...',
+ timeout: timeoutConfiguration.fastOperation,
+ progressId: 'hot.restart',
+ );
+ }
OperationResult result;
String restartEvent = 'restart';
try {
@@ -662,7 +684,7 @@
emulator: emulator,
fullRestart: true,
reason: reason).send();
- status.cancel();
+ status?.cancel();
}
return result;
}
@@ -672,7 +694,7 @@
String sdkName,
bool emulator,
String reason,
- bool pauseAfterRestart = false,
+ bool pause,
}) async {
final bool reloadOnTopOfSnapshot = _runningFromSnapshot;
final String progressPrefix = reloadOnTopOfSnapshot ? 'Initializing' : 'Performing';
@@ -687,8 +709,8 @@
targetPlatform: targetPlatform,
sdkName: sdkName,
emulator: emulator,
- pause: pauseAfterRestart,
reason: reason,
+ pause: pause,
onSlow: (String message) {
status?.cancel();
status = logger.startProgress(
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
index 87a0a15..fad57ed 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
@@ -11,12 +11,15 @@
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/context.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/run.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/features.dart';
+import 'package:flutter_tools/src/globals.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/resident_runner.dart';
@@ -56,6 +59,72 @@
}
});
+ testUsingContext('does not support "--use-application-binary" and "--fast-start"', () async {
+ fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+ fs.file('pubspec.yaml').createSync();
+ fs.file('.packages').createSync();
+
+ final RunCommand command = RunCommand();
+ applyMocksToCommand(command);
+ try {
+ await createTestCommandRunner(command).run(<String>[
+ 'run',
+ '--use-application-binary=app/bar/faz',
+ '--fast-start',
+ '--no-pub',
+ '--show-test-device',
+ ]);
+ fail('Expect exception');
+ } catch (e) {
+ expect(e.toString(), contains('--fast-start is not supported with --use-application-binary'));
+ }
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Forces fast start off for devices that do not support it', () async {
+ final MockDevice mockDevice = MockDevice(TargetPlatform.android_arm);
+ when(mockDevice.name).thenReturn('mockdevice');
+ when(mockDevice.supportsFastStart).thenReturn(false);
+ when(mockDevice.supportsHotReload).thenReturn(true);
+ when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async => false);
+ when(deviceManager.hasSpecifiedAllDevices).thenReturn(false);
+ when(deviceManager.findTargetDevices(any)).thenAnswer((Invocation invocation) {
+ return Future<List<Device>>.value(<Device>[mockDevice]);
+ });
+ when(deviceManager.getDevices()).thenAnswer((Invocation invocation) {
+ return Stream<Device>.value(mockDevice);
+ });
+ fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+ fs.file('pubspec.yaml').createSync();
+ fs.file('.packages').createSync();
+
+ final RunCommand command = RunCommand();
+ applyMocksToCommand(command);
+ try {
+ await createTestCommandRunner(command).run(<String>[
+ 'run',
+ '--fast-start',
+ '--no-pub',
+ ]);
+ fail('Expect exception');
+ } catch (e) {
+ expect(e, isInstanceOf<ToolExit>());
+ }
+
+ final BufferLogger bufferLogger = logger as BufferLogger;
+ expect(bufferLogger.statusText, contains(
+ 'Using --fast-start option with device mockdevice, but this device '
+ 'does not support it. Overriding the setting to false.'
+ ));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ DeviceManager: () => MockDeviceManager(),
+ });
+
+
group('run app', () {
MemoryFileSystem fs;
MockArtifacts mockArtifacts;
@@ -166,6 +235,7 @@
final MockDevice mockDevice = MockDevice(TargetPlatform.ios);
when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) => Future<bool>.value(false));
when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(MockDeviceLogReader());
+ when(mockDevice.supportsFastStart).thenReturn(true);
// App fails to start because we're only interested in usage
when(mockDevice.startApp(
any,
@@ -498,6 +568,9 @@
bool get supportsHotReload => false;
@override
+ bool get supportsFastStart => false;
+
+ @override
Future<String> get sdkNameAndVersion => Future<String>.value('');
@override
diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
index 39b55c6..0dd418d 100644
--- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -154,6 +154,41 @@
expect(onAppStart.isCompleted, true);
}));
+ test('ResidentRunner can attach to device successfully with --fast-start', () => testbed.run(() async {
+ when(mockDevice.supportsHotRestart).thenReturn(true);
+ when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
+ return 'Example';
+ });
+ when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
+ return TargetPlatform.android_arm;
+ });
+ when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async {
+ return false;
+ });
+ residentRunner = HotRunner(
+ <FlutterDevice>[
+ mockFlutterDevice,
+ ],
+ stayResident: false,
+ debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, fastStart: true, startPaused: true),
+ );
+ final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
+ final Completer<void> onAppStart = Completer<void>.sync();
+ final Future<int> result = residentRunner.attach(
+ appStartedCompleter: onAppStart,
+ connectionInfoCompleter: onConnectionInfo,
+ );
+ final Future<DebugConnectionInfo> connectionInfo = onConnectionInfo.future;
+
+ expect(await result, 0);
+
+ verify(mockFlutterDevice.initLogReader()).called(1);
+
+ expect(onConnectionInfo.isCompleted, true);
+ expect((await connectionInfo).baseUri, 'foo://bar');
+ expect(onAppStart.isCompleted, true);
+ }));
+
test('ResidentRunner can handle an RPC exception from hot reload', () => testbed.run(() async {
when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
return 'Example';
diff --git a/packages/flutter_tools/test/integration.shard/test_driver.dart b/packages/flutter_tools/test/integration.shard/test_driver.dart
index 4279b7a..ff881e1 100644
--- a/packages/flutter_tools/test/integration.shard/test_driver.dart
+++ b/packages/flutter_tools/test/integration.shard/test_driver.dart
@@ -497,7 +497,7 @@
// fast.
unawaited(_process.exitCode.then((_) {
if (!prematureExitGuard.isCompleted) {
- prematureExitGuard.completeError('Process existed prematurely: ${args.join(' ')}');
+ prematureExitGuard.completeError('Process existed prematurely: ${args.join(' ')}: $_errorBuffer');
}
}));