mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Switch to the assets plugin (#6408)
This patch removes our dependency on asset_bundle.mojom.
This commit is contained in:
parent
32e95cc6e1
commit
beb8afa456
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user