Commit beb8afa4 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Switch to the assets plugin (#6408)

This patch removes our dependency on asset_bundle.mojom.
parent 32e95cc6
......@@ -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<core.MojoDataPipeConsumer> load(String key) => null;
Future<ByteData> load(String key) => null;
@override
Future<String> loadString(String key, { bool cache: true }) {
......
......@@ -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<core.MojoDataPipeConsumer> load(String key);
Future<ByteData> 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<core.MojoDataPipeConsumer> load(String key) async {
Future<ByteData> 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<String> _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<core.MojoDataPipeConsumer> load(String key) {
Completer<core.MojoDataPipeConsumer> completer = new Completer<core.MojoDataPipeConsumer>();
_bundle.getAsStream(key, (core.MojoDataPipeConsumer assetData) {
completer.complete(assetData);
});
return completer.future;
Future<ByteData> 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]
......
......@@ -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<T> {
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<T> extends ImageProvider<T> {
/// This factors out the common logic of [AssetBundle]-based [ImageProvider]
/// classes, simplifying what subclasses must implement to just [obtainKey].
abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKey> {
/// 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<T> extends ImageProvider<T> {
);
}
/// 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<ImageInfo> loadAsync(T key) async {
final mojo.MojoDataPipeConsumer dataPipe = await loadDataPipe(key);
if (dataPipe == null)
Future<ImageInfo> 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<ui.Image> 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<mojo.MojoDataPipeConsumer> 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<ui.Image> 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<NetworkImage> {
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<AssetBundleImageKey> {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const AssetBundleImageProvider();
@override
Future<AssetBundleImageKey> obtainKey(ImageConfiguration configuration);
@override
Future<mojo.MojoDataPipeConsumer> 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
......
......@@ -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<mojo.MojoDataPipeConsumer> load(String key) {
mojo.MojoDataPipeConsumer pipe;
Future<ByteData> 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<mojo.MojoDataPipeConsumer>(pipe);
return new SynchronousFuture<ByteData>(data);
}
@override
......@@ -83,9 +86,9 @@ class TestAssetImage extends AssetImage {
@override
Future<ImageInfo> 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<ui.Image> decodeImage(@checked TestMojoDataPipeConsumer pipe) {
return new SynchronousFuture<ui.Image>(new TestImage(pipe.scale));
Future<ui.Image> decodeImage(@checked TestByteData data) {
return new SynchronousFuture<ui.Image>(new TestImage(data.scale));
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment