improve startup time
diff --git a/packages/flutter_tools/lib/flutter_tools.dart b/packages/flutter_tools/lib/flutter_tools.dart
index e1c513a..dfd7a94 100644
--- a/packages/flutter_tools/lib/flutter_tools.dart
+++ b/packages/flutter_tools/lib/flutter_tools.dart
@@ -3,8 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
-
-import 'package:archive/archive.dart';
+import 'dart:io';
import 'src/flx.dart' as flx;
@@ -12,7 +11,7 @@
/// pre-compiled snapshot.
Future<int> assembleFlx({
Map manifestDescriptor: const {},
- ArchiveFile snapshotFile: null,
+ File snapshotFile: null,
String assetBasePath: flx.defaultAssetBasePath,
String materialAssetBasePath: flx.defaultMaterialAssetBasePath,
String outputPath: flx.defaultFlxOutputPath,
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index aa09f7a..2252b6e 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -5,7 +5,6 @@
import 'dart:async';
import 'dart:io';
-import 'package:crypto/crypto.dart';
import 'package:path/path.dart' as path;
import '../android/android_sdk.dart';
@@ -136,26 +135,16 @@
}
String _getSourceSha1(ApplicationPackage app) {
- var sha1 = new SHA1();
- var file = new File(app.localPath);
- sha1.add(file.readAsBytesSync());
- return CryptoUtils.bytesToHex(sha1.close());
+ File shaFile = new File('${app.localPath}.sha1');
+ return shaFile.existsSync() ? shaFile.readAsStringSync() : '';
}
String get name => modelID;
@override
bool isAppInstalled(ApplicationPackage app) {
- if (runCheckedSync(adbCommandForDevice(<String>['shell', 'pm', 'path', app.id])) == '') {
- printTrace('TODO(iansf): move this log to the caller. ${app.name} is not on the device. Installing now...');
- return false;
- }
- if (_getDeviceApkSha1(app) != _getSourceSha1(app)) {
- printTrace(
- 'TODO(iansf): move this log to the caller. ${app.name} is out of date. Installing now...');
- return false;
- }
- return true;
+ // Just check for the existence of the application SHA.
+ return _getDeviceApkSha1(app) == _getSourceSha1(app);
}
@override
@@ -179,7 +168,7 @@
if (port == 0) {
// Auto-bind to a port. Set up forwarding for that port. Emit a stdout
- // message similar to the command-line VM, so that tools can parse the output.
+ // message similar to the command-line VM so that tools can parse the output.
// "Observatory listening on http://127.0.0.1:52111"
port = await findAvailablePort();
}
@@ -258,30 +247,26 @@
if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
return false;
- flx.DirectoryResult buildResult = await flx.buildInTempDir(
+ String localBundlePath = await flx.buildFlx(
toolchain,
mainPath: mainPath
);
printTrace('Starting bundle for $this.');
- try {
- if (await startBundle(
- package,
- buildResult.localBundlePath,
- checked: checked,
- traceStartup: platformArgs['trace-startup'],
- route: route,
- clearLogs: clearLogs,
- startPaused: startPaused,
- debugPort: debugPort
- )) {
- return true;
- } else {
- return false;
- }
- } finally {
- buildResult.dispose();
+ if (await startBundle(
+ package,
+ localBundlePath,
+ checked: checked,
+ traceStartup: platformArgs['trace-startup'],
+ route: route,
+ clearLogs: clearLogs,
+ startPaused: startPaused,
+ debugPort: debugPort
+ )) {
+ return true;
+ } else {
+ return false;
}
}
diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart
index 1009e96..5a5f419 100644
--- a/packages/flutter_tools/lib/src/base/process.dart
+++ b/packages/flutter_tools/lib/src/base/process.dart
@@ -65,8 +65,12 @@
/// Run cmd and return stdout.
/// Throws an error if cmd exits with a non-zero value.
-String runCheckedSync(List<String> cmd, { String workingDirectory }) {
- return _runWithLoggingSync(cmd, workingDirectory: workingDirectory, checked: true, noisyErrors: true);
+String runCheckedSync(List<String> cmd, {
+ String workingDirectory, bool truncateCommand: false
+}) {
+ return _runWithLoggingSync(
+ cmd, workingDirectory: workingDirectory, checked: true, noisyErrors: true, truncateCommand: truncateCommand
+ );
}
/// Run cmd and return stdout.
@@ -91,9 +95,13 @@
String _runWithLoggingSync(List<String> cmd, {
bool checked: false,
bool noisyErrors: false,
- String workingDirectory
+ String workingDirectory,
+ bool truncateCommand: false
}) {
- printTrace(cmd.join(' '));
+ String cmdText = cmd.join(' ');
+ if (truncateCommand && cmdText.length > 160)
+ cmdText = cmdText.substring(0, 160) + '…';
+ printTrace(cmdText);
ProcessResult results =
Process.runSync(cmd[0], cmd.getRange(1, cmd.length).toList(), workingDirectory: workingDirectory);
if (results.exitCode != 0) {
diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart
index b271fc1..e7f5412 100644
--- a/packages/flutter_tools/lib/src/base/utils.dart
+++ b/packages/flutter_tools/lib/src/base/utils.dart
@@ -3,6 +3,15 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:io';
+
+import 'package:crypto/crypto.dart';
+
+String calculateSha(File file) {
+ SHA1 sha1 = new SHA1();
+ sha1.add(file.readAsBytesSync());
+ return CryptoUtils.bytesToHex(sha1.close());
+}
/// A class to maintain a list of items, fire events when items are added or
/// removed, and calculate a diff of changes when a new list of items is
diff --git a/packages/flutter_tools/lib/src/commands/apk.dart b/packages/flutter_tools/lib/src/commands/apk.dart
index fb3ee01..ac190de 100644
--- a/packages/flutter_tools/lib/src/commands/apk.dart
+++ b/packages/flutter_tools/lib/src/commands/apk.dart
@@ -13,6 +13,7 @@
import '../base/file_system.dart' show ensureDirectoryExists;
import '../base/os.dart';
import '../base/process.dart';
+import '../base/utils.dart';
import '../build_configuration.dart';
import '../device.dart';
import '../flx.dart' as flx;
@@ -296,6 +297,9 @@
ensureDirectoryExists(finalApk.path);
builder.align(unalignedApk, finalApk);
+ File apkShaFile = new File('$outputFile.sha1');
+ apkShaFile.writeAsStringSync(calculateSha(finalApk));
+
printStatus('Generated APK to ${finalApk.path}.');
return 0;
@@ -313,7 +317,7 @@
String keyPassword;
if (keystoreInfo == null) {
- printError('Signing the APK using the debug keystore.');
+ printStatus('Warning: signing the APK using the debug keystore.');
keystore = components.debugKeystore;
keystorePassword = _kDebugKeystorePassword;
keyAlias = _kDebugKeystoreKeyAlias;
@@ -345,13 +349,14 @@
Iterable<FileStat> dependenciesStat = [
manifest,
_kFlutterManifestPath,
- _kPackagesStatusPath
+ _kPackagesStatusPath,
+ '$apkPath.sha1'
].map((String path) => FileStat.statSync(path));
if (apkStat.type == FileSystemEntityType.NOT_FOUND)
return true;
for (FileStat dep in dependenciesStat) {
- if (dep.modified.isAfter(apkStat.modified))
+ if (dep.modified == null || dep.modified.isAfter(apkStat.modified))
return true;
}
return false;
@@ -381,7 +386,7 @@
}
if (!force && !_needsRebuild(outputFile, manifest)) {
- printTrace('APK up to date. Skipping build step.');
+ printTrace('APK up to date; skipping build step.');
return 0;
}
@@ -408,13 +413,8 @@
String mainPath = findMainDartFile(target);
// Build the FLX.
- flx.DirectoryResult buildResult = await flx.buildInTempDir(toolchain, mainPath: mainPath);
-
- try {
- return _buildApk(components, buildResult.localBundlePath, keystore, outputFile);
- } finally {
- buildResult.dispose();
- }
+ String localBundlePath = await flx.buildFlx(toolchain, mainPath: mainPath);
+ return _buildApk(components, localBundlePath, keystore, outputFile);
}
}
diff --git a/packages/flutter_tools/lib/src/flx.dart b/packages/flutter_tools/lib/src/flx.dart
index 43b52dc..8e72b43 100644
--- a/packages/flutter_tools/lib/src/flx.dart
+++ b/packages/flutter_tools/lib/src/flx.dart
@@ -5,9 +5,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
-import 'dart:typed_data';
-import 'package:archive/archive.dart';
import 'package:flx/bundle.dart';
import 'package:flx/signing.dart';
import 'package:path/path.dart' as path;
@@ -16,6 +14,7 @@
import 'base/file_system.dart' show ensureDirectoryExists;
import 'globals.dart';
import 'toolchain.dart';
+import 'zip.dart';
const String defaultMainPath = 'lib/main.dart';
const String defaultAssetBasePath = '.';
@@ -140,20 +139,17 @@
return loadYaml(manifestDescriptor);
}
-bool _addAssetFile(Archive archive, _Asset asset) {
+ZipEntry _createAssetEntry(_Asset asset) {
String source = asset.source ?? asset.key;
File file = new File('${asset.base}/$source');
if (!file.existsSync()) {
printError('Cannot find asset "$source" in directory "${path.absolute(asset.base)}".');
- return false;
+ return null;
}
- List<int> content = file.readAsBytesSync();
- archive.addFile(new ArchiveFile.noCompress(asset.key, content.length, content));
- return true;
+ return new ZipEntry.fromFile(asset.key, file);
}
-ArchiveFile _createAssetManifest(Map<_Asset, List<_Asset>> assets) {
- String key = 'AssetManifest.json';
+ZipEntry _createAssetManifest(Map<_Asset, List<_Asset>> assets) {
Map<String, List<String>> json = <String, List<String>>{};
for (_Asset main in assets.keys) {
List<String> variants = <String>[];
@@ -161,34 +157,25 @@
variants.add(variant.key);
json[main.key] = variants;
}
- List<int> content = UTF8.encode(JSON.encode(json));
- return new ArchiveFile.noCompress(key, content.length, content);
+ return new ZipEntry.fromString('AssetManifest.json', JSON.encode(json));
}
-ArchiveFile _createFontManifest(Map manifestDescriptor) {
+ZipEntry _createFontManifest(Map manifestDescriptor) {
if (manifestDescriptor != null && manifestDescriptor.containsKey('fonts')) {
- List<int> content = UTF8.encode(JSON.encode(manifestDescriptor['fonts']));
- return new ArchiveFile.noCompress('FontManifest.json', content.length, content);
+ return new ZipEntry.fromString('FontManifest.json', JSON.encode(manifestDescriptor['fonts']));
} else {
return null;
}
}
-ArchiveFile _createSnapshotFile(String snapshotPath) {
- File file = new File(snapshotPath);
- List<int> content = file.readAsBytesSync();
- return new ArchiveFile(_kSnapshotKey, content.length, content);
-}
-
-/// Build the flx in a temp dir and return `localBundlePath` on success.
-Future<DirectoryResult> buildInTempDir(
+/// Build the flx in the build/ directory and return `localBundlePath` on success.
+Future<String> buildFlx(
Toolchain toolchain, {
String mainPath: defaultMainPath
}) async {
int result;
- Directory tempDir = await Directory.systemTemp.createTemp('flutter_tools');
- String localBundlePath = path.join(tempDir.path, 'app.flx');
- String localSnapshotPath = path.join(tempDir.path, 'snapshot_blob.bin');
+ String localBundlePath = path.join('build', 'app.flx');
+ String localSnapshotPath = path.join('build', 'snapshot_blob.bin');
result = await build(
toolchain,
snapshotPath: localSnapshotPath,
@@ -196,7 +183,7 @@
mainPath: mainPath
);
if (result == 0)
- return new DirectoryResult(tempDir, localBundlePath);
+ return localBundlePath;
else
throw result;
}
@@ -227,7 +214,8 @@
Map manifestDescriptor = _loadManifest(manifestPath);
String assetBasePath = path.dirname(path.absolute(manifestPath));
- ArchiveFile snapshotFile = null;
+ File snapshotFile;
+
if (!precompiledSnapshot) {
ensureDirectoryExists(snapshotPath);
@@ -239,7 +227,7 @@
return result;
}
- snapshotFile = _createSnapshotFile(snapshotPath);
+ snapshotFile = new File(snapshotPath);
}
return assemble(
@@ -254,7 +242,7 @@
Future<int> assemble({
Map manifestDescriptor: const {},
- ArchiveFile snapshotFile,
+ File snapshotFile,
String assetBasePath: defaultAssetBasePath,
String materialAssetBasePath: defaultMaterialAssetBasePath,
String outputPath: defaultFlxOutputPath,
@@ -265,25 +253,32 @@
Map<_Asset, List<_Asset>> assets = _parseAssets(manifestDescriptor, assetBasePath);
assets.addAll(_parseMaterialAssets(manifestDescriptor, materialAssetBasePath));
- Archive archive = new Archive();
+ ZipBuilder zipBuilder = new ZipBuilder();
if (snapshotFile != null)
- archive.addFile(snapshotFile);
+ zipBuilder.addEntry(new ZipEntry.fromFile(_kSnapshotKey, snapshotFile));
for (_Asset asset in assets.keys) {
- if (!_addAssetFile(archive, asset))
+ ZipEntry assetEntry = _createAssetEntry(asset);
+ if (assetEntry == null)
return 1;
+ else
+ zipBuilder.addEntry(assetEntry);
+
for (_Asset variant in assets[asset]) {
- if (!_addAssetFile(archive, variant))
+ ZipEntry variantEntry = _createAssetEntry(variant);
+ if (variantEntry == null)
return 1;
+ else
+ zipBuilder.addEntry(variantEntry);
}
}
- archive.addFile(_createAssetManifest(assets));
+ zipBuilder.addEntry(_createAssetManifest(assets));
- ArchiveFile fontManifest = _createFontManifest(manifestDescriptor);
+ ZipEntry fontManifest = _createFontManifest(manifestDescriptor);
if (fontManifest != null)
- archive.addFile(fontManifest);
+ zipBuilder.addEntry(fontManifest);
AsymmetricKeyPair keyPair = keyPairFromPrivateKeyFileSync(privateKeyPath);
printTrace('KeyPair from $privateKeyPath: $keyPair.');
@@ -293,8 +288,10 @@
CipherParameters.get().seedRandom();
}
- printTrace('Encoding zip file.');
- Uint8List zipBytes = new Uint8List.fromList(new ZipEncoder().encode(archive));
+ File zipFile = new File(outputPath.substring(0, outputPath.length - 4) + '.zip');
+ printTrace('Encoding zip file to ${zipFile.path}');
+ zipBuilder.createZip(zipFile, new Directory('build/flx'));
+ List<int> zipBytes = zipFile.readAsBytesSync();
ensureDirectoryExists(outputPath);
@@ -307,7 +304,7 @@
);
bundle.writeSync();
- printTrace('Built and signed flx at $outputPath.');
+ printTrace('Built $outputPath.');
return 0;
}
diff --git a/packages/flutter_tools/lib/src/zip.dart b/packages/flutter_tools/lib/src/zip.dart
new file mode 100644
index 0000000..1eb874b
--- /dev/null
+++ b/packages/flutter_tools/lib/src/zip.dart
@@ -0,0 +1,117 @@
+// 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:io';
+import 'dart:convert' show UTF8;
+
+import 'package:archive/archive.dart';
+import 'package:path/path.dart' as path;
+
+import 'base/process.dart';
+
+abstract class ZipBuilder {
+ factory ZipBuilder() {
+ if (exitsHappy(<String>['which', 'zip'])) {
+ return new _ZipToolBuilder();
+ } else {
+ return new _ArchiveZipBuilder();
+ }
+ }
+
+ ZipBuilder._();
+
+ List<ZipEntry> entries = <ZipEntry>[];
+
+ void addEntry(ZipEntry entry) => entries.add(entry);
+
+ void createZip(File outFile, Directory zipBuildDir);
+}
+
+class ZipEntry {
+ ZipEntry.fromFile(this.archivePath, File file) {
+ this._file = file;
+ }
+
+ ZipEntry.fromString(this.archivePath, String contents) {
+ this._contents = contents;
+ }
+
+ final String archivePath;
+
+ File _file;
+ String _contents;
+
+ bool get isStringEntry => _contents != null;
+}
+
+class _ArchiveZipBuilder extends ZipBuilder {
+ _ArchiveZipBuilder() : super._();
+
+ void createZip(File outFile, Directory zipBuildDir) {
+ Archive archive = new Archive();
+
+ for (ZipEntry entry in entries) {
+ if (entry.isStringEntry) {
+ List<int> data = UTF8.encode(entry._contents);
+ archive.addFile(new ArchiveFile.noCompress(entry.archivePath, data.length, data));
+ } else {
+ List<int> data = entry._file.readAsBytesSync();
+ archive.addFile(new ArchiveFile(entry.archivePath, data.length, data));
+ }
+ }
+
+ List<int> zipData = new ZipEncoder().encode(archive);
+ outFile.writeAsBytesSync(zipData);
+ }
+}
+
+class _ZipToolBuilder extends ZipBuilder {
+ _ZipToolBuilder() : super._();
+
+ void createZip(File outFile, Directory zipBuildDir) {
+ if (outFile.existsSync())
+ outFile.deleteSync();
+
+ if (zipBuildDir.existsSync())
+ zipBuildDir.deleteSync(recursive: true);
+ zipBuildDir.createSync(recursive: true);
+
+ for (ZipEntry entry in entries) {
+ if (entry.isStringEntry) {
+ List<int> data = UTF8.encode(entry._contents);
+ File file = new File(path.join(zipBuildDir.path, entry.archivePath));
+ file.parent.createSync(recursive: true);
+ file.writeAsBytesSync(data);
+ } else {
+ List<int> data = entry._file.readAsBytesSync();
+ File file = new File(path.join(zipBuildDir.path, entry.archivePath));
+ file.parent.createSync(recursive: true);
+ file.writeAsBytesSync(data);
+ }
+ }
+
+ runCheckedSync(
+ <String>['zip', '-q', outFile.absolute.path]..addAll(_getCompressedNames()),
+ workingDirectory: zipBuildDir.path,
+ truncateCommand: true
+ );
+ runCheckedSync(
+ <String>['zip', '-q', '-0', outFile.absolute.path]..addAll(_getStoredNames()),
+ workingDirectory: zipBuildDir.path,
+ truncateCommand: true
+ );
+ }
+
+ Iterable<String> _getCompressedNames() {
+ return entries
+ .where((ZipEntry entry) => !entry.isStringEntry)
+ .map((ZipEntry entry) => entry.archivePath);
+ }
+
+ Iterable<String> _getStoredNames() {
+ return entries
+ .where((ZipEntry entry) => entry.isStringEntry)
+ .map((ZipEntry entry) => entry.archivePath);
+ }
+}
diff --git a/packages/flx/lib/bundle.dart b/packages/flx/lib/bundle.dart
index 0b7fe37..613cf21 100644
--- a/packages/flx/lib/bundle.dart
+++ b/packages/flx/lib/bundle.dart
@@ -71,7 +71,7 @@
Bundle.fromContent({
this.path,
this.manifest,
- contentBytes,
+ List<int> contentBytes,
AsymmetricKeyPair keyPair
}) : _contentBytes = contentBytes {
assert(path != null);