From beb8afa456da170d61d961912c02167464ad966a Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Wed, 19 Oct 2016 22:04:56 -0700 Subject: [PATCH] Switch to the assets plugin (#6408) This patch removes our dependency on asset_bundle.mojom. --- .../test/example_code_parser_test.dart | 4 +- .../lib/src/services/asset_bundle.dart | 65 ++----- .../lib/src/services/image_provider.dart | 161 +++++++----------- .../test/widget/image_resolution_test.dart | 35 ++-- 4 files changed, 94 insertions(+), 171 deletions(-) diff --git a/examples/flutter_gallery/test/example_code_parser_test.dart b/examples/flutter_gallery/test/example_code_parser_test.dart index c93e82d29f0..68ffad6b678 100644 --- a/examples/flutter_gallery/test/example_code_parser_test.dart +++ b/examples/flutter_gallery/test/example_code_parser_test.dart @@ -3,10 +3,10 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:typed_data'; import 'package:flutter/services.dart'; import 'package:flutter_gallery/gallery/example_code_parser.dart'; -import 'package:mojo/core.dart' as core; import 'package:test/test.dart'; void main() { @@ -36,7 +36,7 @@ test 1 1 class TestAssetBundle extends AssetBundle { @override - Future load(String key) => null; + Future load(String key) => null; @override Future loadString(String key, { bool cache: true }) { diff --git a/packages/flutter/lib/src/services/asset_bundle.dart b/packages/flutter/lib/src/services/asset_bundle.dart index 00bb656f8fe..2ab59b9903a 100644 --- a/packages/flutter/lib/src/services/asset_bundle.dart +++ b/packages/flutter/lib/src/services/asset_bundle.dart @@ -4,14 +4,12 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; -import 'dart:ui' as ui; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/http.dart' as http; -import 'package:mojo/core.dart' as core; -import 'package:flutter_services/mojo/asset_bundle/asset_bundle.dart' as mojom; + +import 'platform_messages.dart'; /// A collection of resources used by the application. /// @@ -42,7 +40,7 @@ import 'package:flutter_services/mojo/asset_bundle/asset_bundle.dart' as mojom; /// * [rootBundle] abstract class AssetBundle { /// Retrieve a binary resource from the asset bundle as a data stream. - Future load(String key); + Future load(String key); /// Retrieve a string from the asset bundle. /// @@ -83,13 +81,11 @@ class NetworkAssetBundle extends AssetBundle { String _urlFromKey(String key) => _baseUrl.resolve(key).toString(); @override - Future load(String key) async { + Future load(String key) async { http.Response response = await http.get(_urlFromKey(key)); if (response.statusCode == 200) return null; - core.MojoDataPipe pipe = new core.MojoDataPipe(); - core.DataPipeFiller.fillHandle(pipe.producer, response.bodyBytes.buffer.asByteData()); - return pipe.consumer; + return response.bodyBytes.buffer.asByteData(); } @override @@ -138,9 +134,8 @@ abstract class CachingAssetBundle extends AssetBundle { } Future _fetchString(String key) async { - final core.MojoDataPipeConsumer pipe = await load(key); - final ByteData data = await core.DataPipeDrainer.drainHandle(pipe); - return UTF8.decode(new Uint8List.view(data.buffer)); + final ByteData data = await load(key); + return UTF8.decode(data.buffer.asUint8List()); } /// Retrieve a string from the asset bundle, parse it with the given function, @@ -190,47 +185,17 @@ abstract class CachingAssetBundle extends AssetBundle { } } -/// An [AssetBundle] that loads resources from a Mojo service. -class MojoAssetBundle extends CachingAssetBundle { - /// Creates an [AssetBundle] interface around the given [mojom.AssetBundleProxy] Mojo service. - MojoAssetBundle(this._bundle); - - mojom.AssetBundleProxy _bundle; - +/// An [AssetBundle] that loads resources using platform messages. +class PlatformAssetBundle extends CachingAssetBundle { @override - Future load(String key) { - Completer completer = new Completer(); - _bundle.getAsStream(key, (core.MojoDataPipeConsumer assetData) { - completer.complete(assetData); - }); - return completer.future; + Future load(String key) { + Uint8List encoded = UTF8.encoder.convert(key); + return PlatformMessages.sendBinary('flutter/assets', encoded.buffer.asByteData()); } } AssetBundle _initRootBundle() { - int h = ui.MojoServices.takeRootBundle(); - if (h == core.MojoHandle.INVALID) { - assert(() { - if (!Platform.environment.containsKey('FLUTTER_TEST')) { - FlutterError.reportError(new FlutterErrorDetails( - exception: - 'dart:ui MojoServices.takeRootBundle() returned an invalid handle.\n' - 'This might happen if the Dart VM was restarted without restarting the underlying Flutter engine, ' - 'or if the Flutter framework\'s rootBundle object was first accessed after some other code called ' - 'takeRootBundle. The root bundle handle can only be obtained once in the lifetime of the Flutter ' - 'engine. Mojo handles cannot be shared.\n' - 'The rootBundle object will be initialised with a NetworkAssetBundle instead of a MojoAssetBundle. ' - 'This may cause subsequent network errors.', - library: 'services library', - context: 'while initialising the root bundle' - )); - } - return true; - }); - return new NetworkAssetBundle(Uri.base); - } - core.MojoHandle handle = new core.MojoHandle(h); - return new MojoAssetBundle(new mojom.AssetBundleProxy.fromHandle(handle)); + return new PlatformAssetBundle(); } /// The [AssetBundle] from which this application was loaded. @@ -248,10 +213,6 @@ AssetBundle _initRootBundle() { /// convenience, the [WidgetsApp] or [MaterialApp] widget at the top of the /// widget hierarchy configures the [DefaultAssetBundle] to be the [rootBundle]. /// -/// In normal operation, the [rootBundle] is a [MojoAssetBundle], though it can -/// also end up being a [NetworkAssetBundle] in some cases (e.g. if the -/// application's resources are being served from a local HTTP server). -/// /// See also: /// /// * [DefaultAssetBundle] diff --git a/packages/flutter/lib/src/services/image_provider.dart b/packages/flutter/lib/src/services/image_provider.dart index 5d6e36c2ef7..7fec73f2e9b 100644 --- a/packages/flutter/lib/src/services/image_provider.dart +++ b/packages/flutter/lib/src/services/image_provider.dart @@ -10,7 +10,6 @@ import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/http.dart' as http; import 'package:meta/meta.dart'; -import 'package:mojo/core.dart' as mojo; import 'asset_bundle.dart'; import 'image_cache.dart'; @@ -209,26 +208,62 @@ abstract class ImageProvider { String toString() => '$runtimeType()'; } -/// A subclass of [ImageProvider] that knows how to invoke -/// [decodeImageFromDataPipe]. +/// Key for the image obtained by an [AssetImage] or [ExactAssetImage]. /// -/// This factors out the common logic of many [ImageProvider] classes, -/// simplifying what subclasses must implement to just three small methods: +/// This is used to identify the precise resource in the [imageCache]. +class AssetBundleImageKey { + /// Creates the key for an [AssetImage] or [AssetBundleImageProvider]. + /// + /// The arguments must not be null. + const AssetBundleImageKey({ + @required this.bundle, + @required this.name, + @required this.scale + }); + + /// The bundle from which the image will be obtained. + /// + /// The image is obtained by calling [AssetBundle.load] on the given [bundle] + /// using the key given by [name]. + final AssetBundle bundle; + + /// The key to use to obtain the resource from the [bundle]. This is the + /// argument passed to [AssetBundle.load]. + final String name; + + /// The scale to place in the [ImageInfo] object of the image. + final double scale; + + @override + bool operator ==(dynamic other) { + if (other.runtimeType != runtimeType) + return false; + final AssetBundleImageKey typedOther = other; + return bundle == typedOther.bundle + && name == typedOther.name + && scale == typedOther.scale; + } + + @override + int get hashCode => hashValues(bundle, name, scale); + + @override + String toString() => '$runtimeType(bundle: $bundle, name: $name, scale: $scale)'; +} + +/// A subclass of [ImageProvider] that knows about [AssetBundle]s. /// -/// * [obtainKey], to resolve an [ImageConfiguration]. -/// * [getScale], to determine the scale of the image associated with a -/// particular key. -/// * [loadDataPipe], to obtain the [mojo.MojoDataPipeConsumer] object that -/// contains the actual image data. -abstract class DataPipeImageProvider extends ImageProvider { +/// This factors out the common logic of [AssetBundle]-based [ImageProvider] +/// classes, simplifying what subclasses must implement to just [obtainKey]. +abstract class AssetBundleImageProvider extends ImageProvider { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. - const DataPipeImageProvider(); + const AssetBundleImageProvider(); /// Converts a key into an [ImageStreamCompleter], and begins fetching the /// image using [loadAsync]. @override - ImageStreamCompleter load(T key) { + ImageStreamCompleter load(AssetBundleImageKey key) { return new OneFrameImageStreamCompleter( loadAsync(key), informationCollector: (StringBuffer information) { @@ -238,39 +273,29 @@ abstract class DataPipeImageProvider extends ImageProvider { ); } - /// Fetches the image from the data pipe, decodes it, and returns a + /// Fetches the image from the asset bundle, decodes it, and returns a /// corresponding [ImageInfo] object. /// /// This function is used by [load]. @protected - Future loadAsync(T key) async { - final mojo.MojoDataPipeConsumer dataPipe = await loadDataPipe(key); - if (dataPipe == null) + Future loadAsync(AssetBundleImageKey key) async { + final ByteData data = await key.bundle.load(key.name); + if (data == null) throw 'Unable to read data'; - final ui.Image image = await decodeImage(dataPipe); + final ui.Image image = await decodeImage(data); if (image == null) throw 'Unable to decode image data'; - return new ImageInfo(image: image, scale: getScale(key)); + return new ImageInfo(image: image, scale: key.scale); } - /// Converts raw image data from a [mojo.MojoDataPipeConsumer] data pipe into - /// a decoded [ui.Image] which can be passed to a [Canvas]. + /// Converts raw image data from a [ByteData] buffer into a decoded + /// [ui.Image] which can be passed to a [Canvas]. /// - /// By default, this just uses [decodeImageFromDataPipe]. This method could be + /// By default, this just uses [decodeImageFromList]. This method could be /// overridden in subclasses (e.g. for testing). - Future decodeImage(mojo.MojoDataPipeConsumer pipe) => decodeImageFromDataPipe(pipe); - - /// Returns the data pipe that contains the image data to decode. - /// - /// Must be implemented by subclasses of [DataPipeImageProvider]. - @protected - Future loadDataPipe(T key); - - /// Returns the scale to use when creating the [ImageInfo] for the given key. - /// - /// Must be implemented by subclasses of [DataPipeImageProvider]. - @protected - double getScale(T key); + Future decodeImage(ByteData data) { + return decodeImageFromList(data.buffer.asUint8List()); + } } /// Fetches the given URL from the network, associating it with the given scale. @@ -345,72 +370,6 @@ class NetworkImage extends ImageProvider { String toString() => '$runtimeType("$url", scale: $scale)'; } -/// Key for the image obtained by an [AssetImage] or [AssetBundleImageProvider]. -/// -/// This is used to identify the precise resource in the [imageCache]. -class AssetBundleImageKey { - /// Creates the key for an [AssetImage] or [AssetBundleImageProvider]. - /// - /// The arguments must not be null. - const AssetBundleImageKey({ - @required this.bundle, - @required this.name, - @required this.scale - }); - - /// The bundle from which the image will be obtained. - /// - /// The image is obtained by calling [AssetBundle.load] on the given [bundle] - /// using the key given by [name]. - final AssetBundle bundle; - - /// The key to use to obtain the resource from the [bundle]. This is the - /// argument passed to [AssetBundle.load]. - final String name; - - /// The scale to place in the [ImageInfo] object of the image. - final double scale; - - @override - bool operator ==(dynamic other) { - if (other.runtimeType != runtimeType) - return false; - final AssetBundleImageKey typedOther = other; - return bundle == typedOther.bundle - && name == typedOther.name - && scale == typedOther.scale; - } - - @override - int get hashCode => hashValues(bundle, name, scale); - - @override - String toString() => '$runtimeType(bundle: $bundle, name: $name, scale: $scale)'; -} - -/// A subclass of [DataPipeImageProvider] that knows about [AssetBundle]s. -/// -/// This factors out the common logic of [AssetBundle]-based [ImageProvider] -/// classes, simplifying what subclasses must implement to just [obtainKey]. -abstract class AssetBundleImageProvider extends DataPipeImageProvider { - /// Abstract const constructor. This constructor enables subclasses to provide - /// const constructors so that they can be used in const expressions. - const AssetBundleImageProvider(); - - @override - Future obtainKey(ImageConfiguration configuration); - - @override - Future loadDataPipe(AssetBundleImageKey key) { - return key.bundle.load(key.name); - } - - @override - double getScale(AssetBundleImageKey key) { - return key.scale; - } -} - /// Fetches an image from an [AssetBundle], associating it with the given scale. /// /// This implementation requires an explicit final [name] and [scale] on diff --git a/packages/flutter/test/widget/image_resolution_test.dart b/packages/flutter/test/widget/image_resolution_test.dart index 3986e8cba15..37819d44486 100644 --- a/packages/flutter/test/widget/image_resolution_test.dart +++ b/packages/flutter/test/widget/image_resolution_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:typed_data'; import 'dart:ui' as ui show Image; import 'package:flutter/rendering.dart'; @@ -10,7 +11,6 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:meta/meta.dart'; -import 'package:mojo/core.dart' as mojo; class TestImage extends ui.Image { TestImage(this.scale); @@ -26,9 +26,12 @@ class TestImage extends ui.Image { void dispose() { } } -class TestMojoDataPipeConsumer extends mojo.MojoDataPipeConsumer { - TestMojoDataPipeConsumer(this.scale) : super(null); +class TestByteData implements ByteData { + TestByteData(this.scale); final double scale; + + @override + dynamic noSuchMethod(Invocation invocation) => null; } String testManifest = ''' @@ -44,26 +47,26 @@ String testManifest = ''' class TestAssetBundle extends CachingAssetBundle { @override - Future load(String key) { - mojo.MojoDataPipeConsumer pipe; + Future load(String key) { + ByteData data; switch (key) { case 'assets/image.png': - pipe = new TestMojoDataPipeConsumer(1.0); + data = new TestByteData(1.0); break; case 'assets/1.5x/image.png': - pipe = new TestMojoDataPipeConsumer(1.5); + data = new TestByteData(1.5); break; case 'assets/2.0x/image.png': - pipe = new TestMojoDataPipeConsumer(2.0); + data = new TestByteData(2.0); break; case 'assets/3.0x/image.png': - pipe = new TestMojoDataPipeConsumer(3.0); + data = new TestByteData(3.0); break; case 'assets/4.0x/image.png': - pipe = new TestMojoDataPipeConsumer(4.0); + data = new TestByteData(4.0); break; } - return new SynchronousFuture(pipe); + return new SynchronousFuture(data); } @override @@ -83,9 +86,9 @@ class TestAssetImage extends AssetImage { @override Future loadAsync(AssetBundleImageKey key) { ImageInfo result; - key.bundle.load(key.name).then((mojo.MojoDataPipeConsumer dataPipe) { - decodeImage(dataPipe).then((ui.Image image) { - result = new ImageInfo(image: image, scale: getScale(key)); + key.bundle.load(key.name).then((ByteData data) { + decodeImage(data).then((ui.Image image) { + result = new ImageInfo(image: image, scale: key.scale); }); }); assert(result != null); @@ -93,8 +96,8 @@ class TestAssetImage extends AssetImage { } @override - Future decodeImage(@checked TestMojoDataPipeConsumer pipe) { - return new SynchronousFuture(new TestImage(pipe.scale)); + Future decodeImage(@checked TestByteData data) { + return new SynchronousFuture(new TestImage(data.scale)); } }