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);