From bf1246d881d8bf5a85ff2086c4fd9de779d9cd0f Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Tue, 9 Feb 2016 10:40:21 -0800 Subject: [PATCH] Add a refresh command that rebuilds the snapshot and reloads it on the device This provides a fast way to iterate on changes to a Flutter app that only involve updates to Dart code and do not require a full build and install of the FLX and APK --- packages/flutter_tools/lib/executable.dart | 2 + .../lib/src/android/device_android.dart | 31 ++++++++- .../lib/src/commands/refresh.dart | 65 +++++++++++++++++++ 3 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 packages/flutter_tools/lib/src/commands/refresh.dart diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index dcd3c5373b0..e92e058a9e2 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -20,6 +20,7 @@ import 'src/commands/ios.dart'; import 'src/commands/list.dart'; import 'src/commands/listen.dart'; import 'src/commands/logs.dart'; +import 'src/commands/refresh.dart'; import 'src/commands/run_mojo.dart'; import 'src/commands/start.dart'; import 'src/commands/stop.dart'; @@ -47,6 +48,7 @@ Future main(List args) async { ..addCommand(new ListCommand()) ..addCommand(new ListenCommand()) ..addCommand(new LogsCommand()) + ..addCommand(new RefreshCommand()) ..addCommand(new RunMojoCommand()) ..addCommand(new StartCommand()) ..addCommand(new StopCommand()) diff --git a/packages/flutter_tools/lib/src/android/device_android.dart b/packages/flutter_tools/lib/src/android/device_android.dart index 8cdb6e5d258..e0eadf9c8c5 100644 --- a/packages/flutter_tools/lib/src/android/device_android.dart +++ b/packages/flutter_tools/lib/src/android/device_android.dart @@ -21,6 +21,12 @@ import 'android.dart'; const String _defaultAdbPath = 'adb'; +// Path where the FLX bundle will be copied on the device. +const String _deviceBundlePath = '/data/local/tmp/dev.flx'; + +// Path where the snapshot will be copied on the device. +const String _deviceSnapshotPath = '/data/local/tmp/dev_snapshot.bin'; + class AndroidDeviceDiscovery extends DeviceDiscovery { List _devices = []; @@ -268,12 +274,11 @@ class AndroidDevice extends Device { if (clearLogs) this.clearLogs(); - String deviceTmpPath = '/data/local/tmp/dev.flx'; - runCheckedSync(adbCommandForDevice(['push', bundlePath, deviceTmpPath])); + runCheckedSync(adbCommandForDevice(['push', bundlePath, _deviceBundlePath])); List cmd = adbCommandForDevice([ 'shell', 'am', 'start', '-a', 'android.intent.action.RUN', - '-d', deviceTmpPath, + '-d', _deviceBundlePath, '-f', '0x20000000', // FLAG_ACTIVITY_SINGLE_TOP ]); if (checked) @@ -420,6 +425,26 @@ class AndroidDevice extends Device { void setConnected(bool value) { _connected = value; } + + Future refreshSnapshot(AndroidApk apk, String snapshotPath) async { + if (!FileSystemEntity.isFileSync(snapshotPath)) { + printError('Cannot find $snapshotPath'); + return false; + } + + runCheckedSync(adbCommandForDevice(['push', snapshotPath, _deviceSnapshotPath])); + + List cmd = adbCommandForDevice([ + 'shell', 'am', 'start', + '-a', 'android.intent.action.RUN', + '-d', _deviceBundlePath, + '-f', '0x20000000', // FLAG_ACTIVITY_SINGLE_TOP + '--es', 'snapshot', _deviceSnapshotPath, + apk.launchActivity, + ]); + runCheckedSync(cmd); + return true; + } } /// The [mockAndroid] argument is only to facilitate testing with mocks, so that diff --git a/packages/flutter_tools/lib/src/commands/refresh.dart b/packages/flutter_tools/lib/src/commands/refresh.dart new file mode 100644 index 00000000000..02bb4d5efb6 --- /dev/null +++ b/packages/flutter_tools/lib/src/commands/refresh.dart @@ -0,0 +1,65 @@ +// Copyright 2016 The Chromium 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 'dart:io'; + +import 'package:path/path.dart' as path; + +import '../base/context.dart'; +import '../flx.dart'; +import '../runner/flutter_command.dart'; + +class RefreshCommand extends FlutterCommand { + final String name = 'refresh'; + final String description = 'Build and deploy the Dart code in a Flutter app (Android only).'; + + RefreshCommand() { + argParser.addOption('target', + abbr: 't', + defaultsTo: defaultMainPath, + help: 'Target app path / main entry-point file.' + ); + } + + @override + Future runInProject() async { + printTrace('Downloading toolchain.'); + + await Future.wait([ + downloadToolchain(), + downloadApplicationPackagesAndConnectToDevices(), + ], eagerError: true); + + if (!devices.android.isConnected()) { + printError('No device connected.'); + return 1; + } + + Directory tempDir = await Directory.systemTemp.createTemp('flutter_tools'); + try { + String snapshotPath = path.join(tempDir.path, 'snapshot_blob.bin'); + + int result = await toolchain.compiler.compile( + mainPath: argResults['target'], snapshotPath: snapshotPath + ); + if (result != 0) { + printError('Failed to run the Flutter compiler. Exit code: $result'); + return result; + } + + bool success = await devices.android.refreshSnapshot( + applicationPackages.android, snapshotPath + ); + if (!success) { + printError('Error refreshing snapshot on ${devices.android.name}.'); + return 1; + } + + return 0; + } finally { + tempDir.deleteSync(recursive: true); + } + } +}