Send asset evictions notices to Flutter framework (#5303)

diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart
index ca5b8fd..bc37079 100644
--- a/packages/flutter_tools/lib/src/devfs.dart
+++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -27,6 +27,7 @@
 
   final String devicePath;
   final AssetBundleEntry bundleEntry;
+  String get assetPath => bundleEntry.archivePath;
 
   final File file;
   FileStat _fileStat;
@@ -71,6 +72,8 @@
 
   bool get _isSourceEntry => file == null;
 
+  bool get _isAssetEntry => bundleEntry != null;
+
   Future<List<int>> contentsAsBytes() async {
     if (_isSourceEntry)
       return bundleEntry.contentsAsBytes();
@@ -246,6 +249,7 @@
   final Map<String, DevFSEntry> _entries = <String, DevFSEntry>{};
   final Set<DevFSEntry> _dirtyEntries = new Set<DevFSEntry>();
   final Set<DevFSEntry> _deletedEntries = new Set<DevFSEntry>();
+  final Set<DevFSEntry> dirtyAssetEntries = new Set<DevFSEntry>();
 
   final List<Future<Response>> _pendingOperations = new List<Future<Response>>();
 
@@ -276,6 +280,8 @@
     _dirtyEntries.clear();
     // Clear the deleted entries list.
     _deletedEntries.clear();
+    // Clear the dirty asset entries.
+    dirtyAssetEntries.clear();
   }
 
   Future<dynamic> update({ DevFSProgressReporter progressReporter,
@@ -422,8 +428,11 @@
     }
     bool needsWrite = entry.isModified;
     if (needsWrite) {
-      if (_dirtyEntries.add(entry))
+      if (_dirtyEntries.add(entry)) {
         _bytes += entry.size;
+        if (entry._isAssetEntry)
+          dirtyAssetEntries.add(entry);
+      }
     }
   }
 
diff --git a/packages/flutter_tools/lib/src/hot.dart b/packages/flutter_tools/lib/src/hot.dart
index 3b69b2d..8dbdb59 100644
--- a/packages/flutter_tools/lib/src/hot.dart
+++ b/packages/flutter_tools/lib/src/hot.dart
@@ -272,6 +272,21 @@
     return true;
   }
 
+  Future<Null> _evictDirtyAssets() async {
+    if (_devFS == null) {
+      return;
+    }
+    if (_devFS.dirtyAssetEntries.length == 0) {
+      return;
+    }
+    if (serviceProtocol.firstIsolateId == null)
+      throw 'Application isolate not found';
+    for (DevFSEntry entry in _devFS.dirtyAssetEntries) {
+      await serviceProtocol.flutterEvictAsset(serviceProtocol.firstIsolateId,
+                                              entry.assetPath);
+    }
+  }
+
   Future<Null> _cleanupDevFS() async {
     if (_devFS != null) {
       // Cleanup the devFS.
@@ -384,6 +399,7 @@
       printError('Hot reload failed:\n$errorMessage');
       return false;
     }
+    await _evictDirtyAssets();
     Status reassembleStatus =
         logger.startProgress('Reassembling application...');
     try {
diff --git a/packages/flutter_tools/lib/src/observatory.dart b/packages/flutter_tools/lib/src/observatory.dart
index 0796550..bcbb52d 100644
--- a/packages/flutter_tools/lib/src/observatory.dart
+++ b/packages/flutter_tools/lib/src/observatory.dart
@@ -267,6 +267,13 @@
     }).then((dynamic result) => new Response(result));
   }
 
+  Future<Response> flutterEvictAsset(String isolateId, String assetPath) {
+    return peer.sendRequest('ext.flutter.evict', <String, dynamic>{
+      'isolateId': isolateId,
+      'value': assetPath
+    }).then((dynamic result) => new Response(result));
+  }
+
   Future<Response> flutterExit(String isolateId) {
     return peer
       .sendRequest('ext.flutter.exit', <String, dynamic>{ 'isolateId': isolateId })