This reverts commit 2b8f2d05045590cd4aeb9f02c67baaa69db35e5e.
This commit is contained in:
parent
2b8f2d0504
commit
a04ab7129b
@ -2,7 +2,10 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/services.dart' show AssetManifest, PlatformAssetBundle, rootBundle;
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart' show PlatformAssetBundle;
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../common.dart';
|
||||
@ -15,12 +18,16 @@ void main() async {
|
||||
final BenchmarkResultPrinter printer = BenchmarkResultPrinter();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final Stopwatch watch = Stopwatch();
|
||||
final PlatformAssetBundle bundle = rootBundle as PlatformAssetBundle;
|
||||
final PlatformAssetBundle bundle = PlatformAssetBundle();
|
||||
|
||||
final ByteData assetManifestBytes = await bundle.load('money_asset_manifest.json');
|
||||
watch.start();
|
||||
for (int i = 0; i < _kNumIterations; i++) {
|
||||
await AssetManifest.loadFromAssetBundle(bundle);
|
||||
bundle.clear();
|
||||
final String json = utf8.decode(assetManifestBytes.buffer.asUint8List());
|
||||
// This is a test, so we don't need to worry about this rule.
|
||||
// ignore: invalid_use_of_visible_for_testing_member
|
||||
await AssetImage.manifestParser(json);
|
||||
}
|
||||
watch.stop();
|
||||
|
||||
|
@ -11,7 +11,6 @@
|
||||
library services;
|
||||
|
||||
export 'src/services/asset_bundle.dart';
|
||||
export 'src/services/asset_manifest.dart';
|
||||
export 'src/services/autofill.dart';
|
||||
export 'src/services/binary_messenger.dart';
|
||||
export 'src/services/binding.dart';
|
||||
|
@ -96,22 +96,12 @@ abstract class AssetBundle {
|
||||
}
|
||||
|
||||
/// Retrieve a string from the asset bundle, parse it with the given function,
|
||||
/// and return that function's result.
|
||||
/// and return the function's result.
|
||||
///
|
||||
/// Implementations may cache the result, so a particular key should only be
|
||||
/// used with one parser for the lifetime of the asset bundle.
|
||||
Future<T> loadStructuredData<T>(String key, Future<T> Function(String value) parser);
|
||||
|
||||
/// Retrieve [ByteData] from the asset bundle, parse it with the given function,
|
||||
/// and return that function's result.
|
||||
///
|
||||
/// Implementations may cache the result, so a particular key should only be
|
||||
/// used with one parser for the lifetime of the asset bundle.
|
||||
Future<T> loadStructuredBinaryData<T>(String key, FutureOr<T> Function(ByteData data) parser) async {
|
||||
final ByteData data = await load(key);
|
||||
return parser(data);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@ -164,16 +154,6 @@ class NetworkAssetBundle extends AssetBundle {
|
||||
return parser(await loadString(key));
|
||||
}
|
||||
|
||||
/// Retrieve [ByteData] from the asset bundle, parse it with the given function,
|
||||
/// and return the function's result.
|
||||
///
|
||||
/// The result is not cached. The parser is run each time the resource is
|
||||
/// fetched.
|
||||
@override
|
||||
Future<T> loadStructuredBinaryData<T>(String key, FutureOr<T> Function(ByteData data) parser) async {
|
||||
return parser(await load(key));
|
||||
}
|
||||
|
||||
// TODO(ianh): Once the underlying network logic learns about caching, we
|
||||
// should implement evict().
|
||||
|
||||
@ -193,7 +173,6 @@ abstract class CachingAssetBundle extends AssetBundle {
|
||||
// TODO(ianh): Replace this with an intelligent cache, see https://github.com/flutter/flutter/issues/3568
|
||||
final Map<String, Future<String>> _stringCache = <String, Future<String>>{};
|
||||
final Map<String, Future<dynamic>> _structuredDataCache = <String, Future<dynamic>>{};
|
||||
final Map<String, Future<dynamic>> _structuredBinaryDataCache = <String, Future<dynamic>>{};
|
||||
|
||||
@override
|
||||
Future<String> loadString(String key, { bool cache = true }) {
|
||||
@ -242,66 +221,16 @@ abstract class CachingAssetBundle extends AssetBundle {
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
/// Retrieve bytedata from the asset bundle, parse it with the given function,
|
||||
/// and return the function's result.
|
||||
///
|
||||
/// The result of parsing the bytedata is cached (the bytedata itself is not).
|
||||
/// For any given `key`, the `parser` is only run the first time.
|
||||
///
|
||||
/// Once the value has been parsed, the future returned by this function for
|
||||
/// subsequent calls will be a [SynchronousFuture], which resolves its
|
||||
/// callback synchronously.
|
||||
@override
|
||||
Future<T> loadStructuredBinaryData<T>(String key, FutureOr<T> Function(ByteData data) parser) {
|
||||
if (_structuredBinaryDataCache.containsKey(key)) {
|
||||
return _structuredBinaryDataCache[key]! as Future<T>;
|
||||
}
|
||||
|
||||
// load can return a SynchronousFuture in certain cases, like in the
|
||||
// flutter_test framework. So, we need to support both async and sync flows.
|
||||
Completer<T>? completer; // For async flow.
|
||||
SynchronousFuture<T>? result; // For sync flow.
|
||||
|
||||
load(key)
|
||||
.then<T>(parser)
|
||||
.then<void>((T value) {
|
||||
result = SynchronousFuture<T>(value);
|
||||
if (completer != null) {
|
||||
// The load and parse operation ran asynchronously. We already returned
|
||||
// from the loadStructuredBinaryData function and therefore the caller
|
||||
// was given the future of the completer.
|
||||
completer.complete(value);
|
||||
}
|
||||
}, onError: (Object error, StackTrace stack) {
|
||||
completer!.completeError(error, stack);
|
||||
});
|
||||
|
||||
if (result != null) {
|
||||
// The above code ran synchronously. We can synchronously return the result.
|
||||
_structuredBinaryDataCache[key] = result!;
|
||||
return result!;
|
||||
}
|
||||
|
||||
// Since the above code is being run asynchronously and thus hasn't run its
|
||||
// `then` handler yet, we'll return a completer that will be completed
|
||||
// when the handler does run.
|
||||
completer = Completer<T>();
|
||||
_structuredBinaryDataCache[key] = completer.future;
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@override
|
||||
void evict(String key) {
|
||||
_stringCache.remove(key);
|
||||
_structuredDataCache.remove(key);
|
||||
_structuredBinaryDataCache.remove(key);
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
_stringCache.clear();
|
||||
_structuredDataCache.clear();
|
||||
_structuredBinaryDataCache.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -343,7 +272,7 @@ class PlatformAssetBundle extends CachingAssetBundle {
|
||||
bool debugUsePlatformChannel = false;
|
||||
assert(() {
|
||||
// dart:io is safe to use here since we early return for web
|
||||
// above. If that code is changed, this needs to be guarded on
|
||||
// above. If that code is changed, this needs to be gaurded on
|
||||
// web presence. Override how assets are loaded in tests so that
|
||||
// the old loader behavior that allows tests to load assets from
|
||||
// the current package using the package prefix.
|
||||
|
@ -1,134 +0,0 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'asset_bundle.dart';
|
||||
import 'message_codecs.dart';
|
||||
|
||||
const String _kAssetManifestFilename = 'AssetManifest.bin';
|
||||
|
||||
/// Contains details about available assets and their variants.
|
||||
/// See [Asset variants](https://docs.flutter.dev/development/ui/assets-and-images#asset-variants)
|
||||
/// to learn about asset variants and how to declare them.
|
||||
abstract class AssetManifest {
|
||||
/// Loads asset manifest data from an [AssetBundle] object and creates an
|
||||
/// [AssetManifest] object from that data.
|
||||
static Future<AssetManifest> loadFromAssetBundle(AssetBundle bundle) {
|
||||
return bundle.loadStructuredBinaryData(_kAssetManifestFilename, _AssetManifestBin.fromStandardMessageCodecMessage);
|
||||
}
|
||||
|
||||
/// Lists the keys of all main assets. This does not include assets
|
||||
/// that are variants of other assets.
|
||||
///
|
||||
/// The logical key maps to the path of an asset specified in the pubspec.yaml
|
||||
/// file at build time.
|
||||
///
|
||||
/// See [Specifying assets](https://docs.flutter.dev/development/ui/assets-and-images#specifying-assets)
|
||||
/// and [Loading assets](https://docs.flutter.dev/development/ui/assets-and-images#loading-assets) for more
|
||||
/// information.
|
||||
List<String> listAssets();
|
||||
|
||||
/// Retrieves metadata about an asset and its variants.
|
||||
///
|
||||
/// Note that this method considers a main asset to be a variant of itself and
|
||||
/// includes it in the returned list.
|
||||
///
|
||||
/// Throws an [ArgumentError] if [key] cannot be found within the manifest. To
|
||||
/// avoid this, use a key obtained from the [listAssets] method.
|
||||
List<AssetMetadata> getAssetVariants(String key);
|
||||
}
|
||||
|
||||
// Lazily parses the binary asset manifest into a data structure that's easier to work
|
||||
// with.
|
||||
//
|
||||
// The binary asset manifest is a map of asset keys to a list of objects
|
||||
// representing the asset's variants.
|
||||
//
|
||||
// The entries with each variant object are:
|
||||
// - "asset": the location of this variant to load it from.
|
||||
// - "dpr": The device-pixel-ratio that the asset is best-suited for.
|
||||
//
|
||||
// New fields could be added to this object schema to support new asset variation
|
||||
// features, such as themes, locale/region support, reading directions, and so on.
|
||||
class _AssetManifestBin implements AssetManifest {
|
||||
_AssetManifestBin(Map<Object?, Object?> standardMessageData): _data = standardMessageData;
|
||||
|
||||
factory _AssetManifestBin.fromStandardMessageCodecMessage(ByteData message) {
|
||||
final dynamic data = const StandardMessageCodec().decodeMessage(message);
|
||||
return _AssetManifestBin(data as Map<Object?, Object?>);
|
||||
}
|
||||
|
||||
final Map<Object?, Object?> _data;
|
||||
final Map<String, List<AssetMetadata>> _typeCastedData = <String, List<AssetMetadata>>{};
|
||||
|
||||
@override
|
||||
List<AssetMetadata> getAssetVariants(String key) {
|
||||
// We lazily delay typecasting to prevent a performance hiccup when parsing
|
||||
// large asset manifests. This is important to keep an app's first asset
|
||||
// load fast.
|
||||
if (!_typeCastedData.containsKey(key)) {
|
||||
final Object? variantData = _data[key];
|
||||
if (variantData == null) {
|
||||
throw ArgumentError('Asset key $key was not found within the asset manifest.');
|
||||
}
|
||||
_typeCastedData[key] = ((_data[key] ?? <Object?>[]) as Iterable<Object?>)
|
||||
.cast<Map<Object?, Object?>>()
|
||||
.map((Map<Object?, Object?> data) => AssetMetadata(
|
||||
key: data['asset']! as String,
|
||||
targetDevicePixelRatio: data['dpr']! as double,
|
||||
main: false,
|
||||
))
|
||||
.toList();
|
||||
|
||||
_data.remove(key);
|
||||
}
|
||||
|
||||
final AssetMetadata mainAsset = AssetMetadata(key: key,
|
||||
targetDevicePixelRatio: null,
|
||||
main: true
|
||||
);
|
||||
|
||||
return <AssetMetadata>[mainAsset, ..._typeCastedData[key]!];
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> listAssets() {
|
||||
return <String>[..._data.keys.cast<String>(), ..._typeCastedData.keys];
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains information about an asset.
|
||||
@immutable
|
||||
class AssetMetadata {
|
||||
/// Creates an object containing information about an asset.
|
||||
const AssetMetadata({
|
||||
required this.key,
|
||||
required this.targetDevicePixelRatio,
|
||||
required this.main,
|
||||
});
|
||||
|
||||
/// The device pixel ratio that this asset is most ideal for. This is determined
|
||||
/// by the name of the parent folder of the asset file. For example, if the
|
||||
/// parent folder is named "3.0x", the target device pixel ratio of that
|
||||
/// asset will be interpreted as 3.
|
||||
///
|
||||
/// This will be null if the parent folder name is not a ratio value followed
|
||||
/// by an "x".
|
||||
///
|
||||
/// See [Declaring resolution-aware image assets](https://docs.flutter.dev/development/ui/assets-and-images#resolution-aware)
|
||||
/// for more information.
|
||||
final double? targetDevicePixelRatio;
|
||||
|
||||
/// The asset's key, which is the path to the asset specified in the pubspec.yaml
|
||||
/// file at build time.
|
||||
final String key;
|
||||
|
||||
/// Whether or not this is a main asset. In other words, this is true if
|
||||
/// this asset is not a variant of another asset.
|
||||
///
|
||||
/// See [Asset variants](https://docs.flutter.dev/development/ui/assets-and-images#asset-variants)
|
||||
/// for more about asset variants.
|
||||
final bool main;
|
||||
}
|
@ -14,28 +14,16 @@ class TestAssetBundle extends CachingAssetBundle {
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
loadCallCount[key] = (loadCallCount[key] ?? 0) + 1;
|
||||
loadCallCount[key] = loadCallCount[key] ?? 0 + 1;
|
||||
if (key == 'AssetManifest.json') {
|
||||
return ByteData.view(Uint8List.fromList(const Utf8Encoder().convert('{"one": ["one"]}')).buffer);
|
||||
}
|
||||
|
||||
if (key == 'AssetManifest.bin') {
|
||||
return const StandardMessageCodec().encodeMessage(<String, Object>{
|
||||
'one': <Object>[]
|
||||
})!;
|
||||
}
|
||||
|
||||
if (key == 'counter') {
|
||||
return ByteData.view(Uint8List.fromList(const Utf8Encoder().convert(loadCallCount[key]!.toString())).buffer);
|
||||
}
|
||||
|
||||
if (key == 'one') {
|
||||
return ByteData(1)..setInt8(0, 49);
|
||||
}
|
||||
|
||||
throw FlutterError('key not found');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void main() {
|
||||
@ -52,7 +40,7 @@ void main() {
|
||||
final String assetString = await bundle.loadString('one');
|
||||
expect(assetString, equals('1'));
|
||||
|
||||
expect(bundle.loadCallCount['one'], 2);
|
||||
expect(bundle.loadCallCount['one'], 1);
|
||||
|
||||
late Object loadException;
|
||||
try {
|
||||
@ -113,69 +101,4 @@ void main() {
|
||||
),
|
||||
);
|
||||
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56314
|
||||
|
||||
test('CachingAssetBundle caches results for loadString, loadStructuredData, and loadBinaryStructuredData', () async {
|
||||
final TestAssetBundle bundle = TestAssetBundle();
|
||||
|
||||
final String firstLoadStringResult = await bundle.loadString('counter');
|
||||
final String secondLoadStringResult = await bundle.loadString('counter');
|
||||
expect(firstLoadStringResult, '1');
|
||||
expect(secondLoadStringResult, '1');
|
||||
|
||||
final String firstLoadStructuredDataResult = await bundle.loadStructuredData('AssetManifest.json', (String value) => Future<String>.value('one'));
|
||||
final String secondLoadStructuredDataResult = await bundle.loadStructuredData('AssetManifest.json', (String value) => Future<String>.value('two'));
|
||||
expect(firstLoadStructuredDataResult, 'one');
|
||||
expect(secondLoadStructuredDataResult, 'one');
|
||||
|
||||
final String firstLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData('AssetManifest.bin', (ByteData value) => Future<String>.value('one'));
|
||||
final String secondLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData('AssetManifest.bin', (ByteData value) => Future<String>.value('two'));
|
||||
expect(firstLoadStructuredBinaryDataResult, 'one');
|
||||
expect(secondLoadStructuredBinaryDataResult, 'one');
|
||||
});
|
||||
|
||||
test("CachingAssetBundle.clear clears all cached values'", () async {
|
||||
final TestAssetBundle bundle = TestAssetBundle();
|
||||
|
||||
await bundle.loadString('counter');
|
||||
bundle.clear();
|
||||
final String secondLoadStringResult = await bundle.loadString('counter');
|
||||
expect(secondLoadStringResult, '2');
|
||||
|
||||
await bundle.loadStructuredData('AssetManifest.json', (String value) => Future<String>.value('one'));
|
||||
bundle.clear();
|
||||
final String secondLoadStructuredDataResult = await bundle.loadStructuredData('AssetManifest.json', (String value) => Future<String>.value('two'));
|
||||
expect(secondLoadStructuredDataResult, 'two');
|
||||
|
||||
await bundle.loadStructuredBinaryData('AssetManifest.bin', (ByteData value) => Future<String>.value('one'));
|
||||
bundle.clear();
|
||||
final String secondLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData('AssetManifest.bin', (ByteData value) => Future<String>.value('two'));
|
||||
expect(secondLoadStructuredBinaryDataResult, 'two');
|
||||
});
|
||||
|
||||
test('CachingAssetBundle.evict evicts a particular key from the cache', () async {
|
||||
final TestAssetBundle bundle = TestAssetBundle();
|
||||
|
||||
await bundle.loadString('counter');
|
||||
bundle.evict('counter');
|
||||
final String secondLoadStringResult = await bundle.loadString('counter');
|
||||
expect(secondLoadStringResult, '2');
|
||||
|
||||
await bundle.loadStructuredData('AssetManifest.json', (String value) => Future<String>.value('one'));
|
||||
bundle.evict('AssetManifest.json');
|
||||
final String secondLoadStructuredDataResult = await bundle.loadStructuredData('AssetManifest.json', (String value) => Future<String>.value('two'));
|
||||
expect(secondLoadStructuredDataResult, 'two');
|
||||
|
||||
await bundle.loadStructuredBinaryData('AssetManifest.bin', (ByteData value) => Future<String>.value('one'));
|
||||
bundle.evict('AssetManifest.bin');
|
||||
final String secondLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData('AssetManifest.bin', (ByteData value) => Future<String>.value('two'));
|
||||
expect(secondLoadStructuredBinaryDataResult, 'two');
|
||||
});
|
||||
|
||||
test('loadStructuredBinaryData correctly loads ByteData', () async {
|
||||
final TestAssetBundle bundle = TestAssetBundle();
|
||||
final Map<Object?, Object?> assetManifest =
|
||||
await bundle.loadStructuredBinaryData('AssetManifest.bin', (ByteData data) => const StandardMessageCodec().decodeMessage(data) as Map<Object?, Object?>);
|
||||
expect(assetManifest.keys.toList(), equals(<String>['one']));
|
||||
expect(assetManifest['one'], <Object>[]);
|
||||
});
|
||||
}
|
||||
|
@ -1,68 +0,0 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class TestAssetBundle extends AssetBundle {
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
if (key == 'AssetManifest.bin') {
|
||||
final Map<String, List<Object>> binManifestData = <String, List<Object>>{
|
||||
'assets/foo.png': <Object>[
|
||||
<String, Object>{
|
||||
'asset': 'assets/2x/foo.png',
|
||||
'dpr': 2.0
|
||||
}
|
||||
],
|
||||
'assets/bar.png': <Object>[],
|
||||
};
|
||||
|
||||
final ByteData data = const StandardMessageCodec().encodeMessage(binManifestData)!;
|
||||
return data;
|
||||
}
|
||||
|
||||
throw ArgumentError('Unexpected key');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<T> loadStructuredData<T>(String key, Future<T> Function(String value) parser) async {
|
||||
return parser(await loadString(key));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
test('loadFromBundle correctly parses a binary asset manifest', () async {
|
||||
final AssetManifest manifest = await AssetManifest.loadFromAssetBundle(TestAssetBundle());
|
||||
|
||||
expect(manifest.listAssets(), unorderedEquals(<String>['assets/foo.png', 'assets/bar.png']));
|
||||
|
||||
final List<AssetMetadata> fooVariants = manifest.getAssetVariants('assets/foo.png');
|
||||
expect(fooVariants.length, 2);
|
||||
final AssetMetadata firstFooVariant = fooVariants[0];
|
||||
expect(firstFooVariant.key, 'assets/foo.png');
|
||||
expect(firstFooVariant.targetDevicePixelRatio, null);
|
||||
expect(firstFooVariant.main, true);
|
||||
final AssetMetadata secondFooVariant = fooVariants[1];
|
||||
expect(secondFooVariant.key, 'assets/2x/foo.png');
|
||||
expect(secondFooVariant.targetDevicePixelRatio, 2.0);
|
||||
expect(secondFooVariant.main, false);
|
||||
|
||||
final List<AssetMetadata> barVariants = manifest.getAssetVariants('assets/bar.png');
|
||||
expect(barVariants.length, 1);
|
||||
final AssetMetadata firstBarVariant = barVariants[0];
|
||||
expect(firstBarVariant.key, 'assets/bar.png');
|
||||
expect(firstBarVariant.targetDevicePixelRatio, null);
|
||||
expect(firstBarVariant.main, true);
|
||||
});
|
||||
|
||||
test('getAssetVariants throws if given a key not contained in the asset manifest', () async {
|
||||
final AssetManifest manifest = await AssetManifest.loadFromAssetBundle(TestAssetBundle());
|
||||
|
||||
expect(() => manifest.getAssetVariants('invalid asset key'), throwsArgumentError);
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user