Provide an observatory extension to evict resources (#5241)

...so that you can use hot reload mode to update assets.
diff --git a/packages/flutter/lib/src/services/asset_bundle.dart b/packages/flutter/lib/src/services/asset_bundle.dart
index 0cdfff5..31bd199 100644
--- a/packages/flutter/lib/src/services/asset_bundle.dart
+++ b/packages/flutter/lib/src/services/asset_bundle.dart
@@ -62,6 +62,11 @@
   /// used with one parser for the lifetime of the asset bundle.
   Future<dynamic> loadStructuredData(String key, dynamic parser(String value));
 
+  /// If this is a caching asset bundle, and the given key describes a cached
+  /// asset, then evict the asset from the cache so that the next time it is
+  /// loaded, the cache will be reread from the asset bundle.
+  void evict(String key) { }
+
   @override
   String toString() => '$runtimeType@$hashCode()';
 }
@@ -101,6 +106,9 @@
     return parser(await loadString(key));
   }
 
+  // TODO(ianh): Once the underlying network logic learns about caching, we
+  // should implement evict().
+
   @override
   String toString() => '$runtimeType@$hashCode($_baseUrl)';
 }
@@ -155,6 +163,12 @@
     });
     return completer.future;
   }
+
+  @override
+  void evict(String key) {
+    _stringCache.remove(key);
+    _structuredDataCache.remove(key);
+  }
 }
 
 /// An [AssetBundle] that loads resources from a Mojo service.
diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart
index c223ffc..415f413 100644
--- a/packages/flutter/lib/src/services/binding.dart
+++ b/packages/flutter/lib/src/services/binding.dart
@@ -7,6 +7,7 @@
 import 'package:flutter/foundation.dart';
 
 import 'asset_bundle.dart';
+import 'image_cache.dart';
 import 'shell.dart';
 
 /// Ensures that the [MojoShell] singleton is created synchronously
@@ -47,4 +48,21 @@
       }
     }
   }
+
+  @override
+  void initServiceExtensions() {
+    super.initServiceExtensions();
+    registerStringServiceExtension(
+      // ext.flutter.evict value=foo.png will cause foo.png to be evicted from the rootBundle cache
+      // and cause the entire image cache to be cleared. This is used by hot reload mode to clear
+      // out the cache of resources that have changed.
+      // TODO(ianh): find a way to only evict affected images, not all images
+      name: 'evict',
+      getter: () => '',
+      setter: (String value) {
+        rootBundle.evict(value);
+        imageCache.clear();
+      }
+    );
+  }
 }
diff --git a/packages/flutter/lib/src/services/image_cache.dart b/packages/flutter/lib/src/services/image_cache.dart
index ec517ef..c4b5ff6 100644
--- a/packages/flutter/lib/src/services/image_cache.dart
+++ b/packages/flutter/lib/src/services/image_cache.dart
@@ -55,6 +55,16 @@
     }
   }
 
+  /// Evicts all entries from the cache.
+  ///
+  /// This is useful if, for instance, the root asset bundle has been updated
+  /// and therefore new images must be obtained.
+  // TODO(ianh): Provide a way to target individual images. This is currently non-trivial
+  // because by the time we get to the imageCache, the keys we're using are opaque.
+  void clear() {
+    _cache.clear();
+  }
+
   /// Returns the previously cached [ImageStream] for the given key, if available;
   /// if not, calls the given callback to obtain it first. In either case, the
   /// key is moved to the "most recently used" position.
diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart
index a0cb221..9045fc7 100644
--- a/packages/flutter/lib/src/widgets/framework.dart
+++ b/packages/flutter/lib/src/widgets/framework.dart
@@ -731,6 +731,28 @@
   @protected
   void didUpdateConfig(T oldConfig) { }
 
+  /// Called whenever the application is reassembled during debugging.
+  ///
+  /// This method should rerun any initialization logic that depends on global
+  /// state, for example, image loading from asset bundles (since the asset
+  /// bundle may have changed).
+  ///
+  /// In addition to this method being invoked, it is guaranteed that the
+  /// [build] method will be invoked when a reassemble is signalled. Most
+  /// widgets therefore do not need to do anything in the [reassemble] method.
+  ///
+  /// This function will only be called during development. In release builds,
+  /// the `ext.flutter.reassemble` hook is not available, and so this code will
+  /// never execute.
+  ///
+  /// See also:
+  ///
+  /// * [BindingBase.reassembleApplication]
+  /// * [Image], which uses this to reload images
+  @protected
+  @mustCallSuper
+  void reassemble() { }
+
   /// Notify the framework that the internal state of this object has changed.
   ///
   /// Whenever you change the internal state of a [State] object, make the
@@ -2061,6 +2083,7 @@
   @override
   void _reassemble() {
     _builder = state.build;
+    state.reassemble();
     super._reassemble();
   }
 
diff --git a/packages/flutter/lib/src/widgets/image.dart b/packages/flutter/lib/src/widgets/image.dart
index d83c150..7ee99fb 100644
--- a/packages/flutter/lib/src/widgets/image.dart
+++ b/packages/flutter/lib/src/widgets/image.dart
@@ -206,6 +206,12 @@
   }
 
   @override
+  void reassemble() {
+    _resolveImage();
+    super.reassemble();
+  }
+
+  @override
   void dispose() {
     _imageStream.removeListener(_handleImageChanged);
     super.dispose();