Unverified Commit 7673108d authored by Casey Hillers's avatar Casey Hillers Committed by GitHub

Revert "Speed up first asset load by encoding asset manifest in binary rather...

Revert "Speed up first asset load by encoding asset manifest in binary rather than JSON (#113637)" (#116662)

This reverts commit 56cad89b.
parent 352ad3a9
......@@ -3,9 +3,9 @@
// found in the LICENSE file.
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/services.dart' show PlatformAssetBundle, StandardMessageCodec;
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart' show PlatformAssetBundle;
import 'package:flutter/widgets.dart';
import '../common.dart';
......@@ -18,14 +18,16 @@ void main() async {
final BenchmarkResultPrinter printer = BenchmarkResultPrinter();
WidgetsFlutterBinding.ensureInitialized();
final Stopwatch watch = Stopwatch();
final PlatformAssetBundle bundle = PlatformAssetBundle();
final ByteData assetManifest = await loadAssetManifest();
final ByteData assetManifestBytes = await bundle.load('money_asset_manifest.json');
watch.start();
for (int i = 0; i < _kNumIterations; i++) {
// This is effectively a test.
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
AssetImage.parseAssetManifest(assetManifest);
await AssetImage.manifestParser(json);
}
watch.stop();
......@@ -38,49 +40,3 @@ void main() async {
printer.printToStdout();
}
final RegExp _extractRatioRegExp = RegExp(r'/?(\d+(\.\d*)?)x$');
Future<ByteData> loadAssetManifest() async {
double parseScale(String key) {
final Uri assetUri = Uri.parse(key);
String directoryPath = '';
if (assetUri.pathSegments.length > 1) {
directoryPath = assetUri.pathSegments[assetUri.pathSegments.length - 2];
}
final Match? match = _extractRatioRegExp.firstMatch(directoryPath);
if (match != null && match.groupCount > 0) {
return double.parse(match.group(1)!);
}
return 1.0;
}
final Map<String, dynamic> result = <String, dynamic>{};
final PlatformAssetBundle bundle = PlatformAssetBundle();
// For the benchmark, we use the older JSON format and then convert it to the modern binary format.
final ByteData jsonAssetManifestBytes = await bundle.load('money_asset_manifest.json');
final String jsonAssetManifest = utf8.decode(jsonAssetManifestBytes.buffer.asUint8List());
final Map<String, dynamic> assetManifest = json.decode(jsonAssetManifest) as Map<String, dynamic>;
for (final MapEntry<String, dynamic> manifestEntry in assetManifest.entries) {
final List<dynamic> resultVariants = <dynamic>[];
final List<String> entries = (manifestEntry.value as List<dynamic>).cast<String>();
for (final String variant in entries) {
if (variant == manifestEntry.key) {
// With the newer binary format, don't include the main asset in it's
// list of variants. This reduces parsing time at runtime.
continue;
}
final Map<String, dynamic> resultVariant = <String, dynamic>{};
final double variantDevicePixelRatio = parseScale(variant);
resultVariant['asset'] = variant;
resultVariant['dpr'] = variantDevicePixelRatio;
resultVariants.add(resultVariant);
}
result[manifestEntry.key] = resultVariants;
}
return const StandardMessageCodec().encodeMessage(result)!;
}
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_gallery/gallery/example_code_parser.dart';
import 'package:flutter_test/flutter_test.dart';
......@@ -60,9 +58,4 @@ class TestAssetBundle extends AssetBundle {
@override
String toString() => '$runtimeType@$hashCode()';
@override
Future<T> loadStructuredBinaryData<T>(String key, FutureOr<T> Function(ByteData data) parser) async {
return parser(await load(key));
}
}
......@@ -96,25 +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);
if (data == null) {
throw FlutterError('Unable to load asset: $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.
......@@ -169,18 +156,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 {
assert(key != null);
assert(parser != null);
return parser(await load(key));
}
// TODO(ianh): Once the underlying network logic learns about caching, we
// should implement evict().
......@@ -200,7 +175,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 }) {
......@@ -251,69 +225,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) {
assert(key != null);
assert(parser != null);
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 err, StackTrace? stack) {
completer!.completeError(err, 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
......@@ -355,7 +276,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.
......
......@@ -13,14 +13,18 @@ import 'package:flutter_test/flutter_test.dart';
class TestAssetBundle extends CachingAssetBundle {
TestAssetBundle(this._assetBundleMap);
final Map<String, List<Map<dynamic, dynamic>>> _assetBundleMap;
final Map<String, List<String>> _assetBundleMap;
Map<String, int> loadCallCount = <String, int>{};
String get _assetBundleContents {
return json.encode(_assetBundleMap);
}
@override
Future<ByteData> load(String key) async {
if (key == 'AssetManifest.bin') {
return const StandardMessageCodec().encodeMessage(_assetBundleMap)!;
if (key == 'AssetManifest.json') {
return ByteData.view(Uint8List.fromList(const Utf8Encoder().convert(_assetBundleContents)).buffer);
}
loadCallCount[key] = loadCallCount[key] ?? 0 + 1;
......@@ -38,71 +42,12 @@ class TestAssetBundle extends CachingAssetBundle {
}
}
class BundleWithoutAssetManifestBin extends CachingAssetBundle {
BundleWithoutAssetManifestBin(this._legacyAssetBundleMap);
final Map<dynamic, List<String>> _legacyAssetBundleMap;
Map<String, int> loadCallCount = <String, int>{};
@override
Future<ByteData> load(String key) async {
ByteData testByteData(double scale) => ByteData(8)..setFloat64(0, scale);
if (key == 'AssetManifest.bin') {
throw FlutterError('AssetManifest.bin was not found.');
}
if (key == 'AssetManifest.json') {
return ByteData.view(Uint8List.fromList(const Utf8Encoder().convert(json.encode(_legacyAssetBundleMap))).buffer);
}
switch (key) {
case 'assets/image.png':
return testByteData(1.0); // see "...with a main asset and a 1.0x asset"
case 'assets/2.0x/image.png':
return testByteData(1.5);
}
throw FlutterError('Unexpected key: $key');
}
@override
Future<ui.ImmutableBuffer> loadBuffer(String key) async {
final ByteData data = await load(key);
return ui.ImmutableBuffer.fromUint8List(data.buffer.asUint8List());
}
}
void main() {
// TODO(andrewkolos): Once google3 is migrated away from using AssetManifest.json,
// remove all references to it. See https://github.com/flutter/flutter/issues/114913.
test('AssetBundle falls back to using AssetManifest.json if AssetManifest.bin cannot be found.', () async {
const String assetPath = 'assets/image.png';
final Map<dynamic, List<String>> assetBundleMap = <dynamic, List<String>>{};
assetBundleMap[assetPath] = <String>[];
final AssetImage assetImage = AssetImage(assetPath, bundle: BundleWithoutAssetManifestBin(assetBundleMap));
final AssetBundleImageKey key = await assetImage.obtainKey(ImageConfiguration.empty);
expect(key.name, assetPath);
expect(key.scale, 1.0);
});
test('When using AssetManifest.json, on a high DPR device, a high dpr variant is selected.', () async {
const String assetPath = 'assets/image.png';
const String asset2xPath = 'assets/2.0x/image.png';
final Map<dynamic, List<String>> assetBundleMap = <dynamic, List<String>>{};
assetBundleMap[assetPath] = <String>[asset2xPath];
final AssetImage assetImage = AssetImage(assetPath, bundle: BundleWithoutAssetManifestBin(assetBundleMap));
final AssetBundleImageKey key = await assetImage.obtainKey(const ImageConfiguration(devicePixelRatio: 2.0));
expect(key.name, asset2xPath);
expect(key.scale, 2.0);
});
group('1.0 scale device tests', () {
void buildAndTestWithOneAsset(String mainAssetPath) {
final Map<String, List<Map<dynamic, dynamic>>> assetBundleMap =
<String, List<Map<dynamic, dynamic>>>{};
final Map<String, List<String>> assetBundleMap = <String, List<String>>{};
assetBundleMap[mainAssetPath] = <Map<dynamic,dynamic>>[];
assetBundleMap[mainAssetPath] = <String>[];
final AssetImage assetImage = AssetImage(
mainAssetPath,
......@@ -148,13 +93,10 @@ void main() {
const String mainAssetPath = 'assets/normalFolder/normalFile.png';
const String variantPath = 'assets/normalFolder/3.0x/normalFile.png';
final Map<String, List<Map<dynamic, dynamic>>> assetBundleMap =
<String, List<Map<dynamic, dynamic>>>{};
final Map<String, List<String>> assetBundleMap =
<String, List<String>>{};
final Map<dynamic, dynamic> mainAssetVariantManifestEntry = <dynamic, dynamic>{};
mainAssetVariantManifestEntry['asset'] = variantPath;
mainAssetVariantManifestEntry['dpr'] = 3.0;
assetBundleMap[mainAssetPath] = <Map<dynamic, dynamic>>[mainAssetVariantManifestEntry];
assetBundleMap[mainAssetPath] = <String>[mainAssetPath, variantPath];
final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap);
......@@ -181,10 +123,10 @@ void main() {
test('When high-res device and high-res asset not present in bundle then return main variant', () {
const String mainAssetPath = 'assets/normalFolder/normalFile.png';
final Map<String, List<Map<dynamic, dynamic>>> assetBundleMap =
<String, List<Map<dynamic, dynamic>>>{};
final Map<String, List<String>> assetBundleMap =
<String, List<String>>{};
assetBundleMap[mainAssetPath] = <Map<dynamic, dynamic>>[];
assetBundleMap[mainAssetPath] = <String>[mainAssetPath];
final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap);
......@@ -214,18 +156,16 @@ void main() {
const String mainAssetPath = 'assets/normalFolder/normalFile.png';
const String variantPath = 'assets/normalFolder/3.0x/normalFile.png';
void buildBundleAndTestVariantLogic(
double deviceRatio,
double chosenAssetRatio,
String expectedAssetPath,
) {
final Map<String, List<Map<dynamic, dynamic>>> assetBundleMap =
<String, List<Map<dynamic, dynamic>>>{};
final Map<String, List<String>> assetBundleMap =
<String, List<String>>{};
final Map<dynamic, dynamic> mainAssetVariantManifestEntry = <dynamic, dynamic>{};
mainAssetVariantManifestEntry['asset'] = variantPath;
mainAssetVariantManifestEntry['dpr'] = 3.0;
assetBundleMap[mainAssetPath] = <Map<dynamic, dynamic>>[mainAssetVariantManifestEntry];
assetBundleMap[mainAssetPath] = <String>[mainAssetPath, variantPath];
final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap);
......
......@@ -9,14 +9,14 @@ import 'package:flutter/painting.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
class _TestAssetBundle extends CachingAssetBundle {
class TestAssetBundle extends CachingAssetBundle {
Map<String, int> loadCallCount = <String, int>{};
@override
Future<ByteData> load(String key) async {
loadCallCount[key] = loadCallCount[key] ?? 0 + 1;
if (key == 'AssetManifest.bin') {
return const StandardMessageCodec().encodeMessage(json.decode('{"one":[]}'))!;
if (key == 'AssetManifest.json') {
return ByteData.view(Uint8List.fromList(const Utf8Encoder().convert('{"one": ["one"]}')).buffer);
}
if (key == 'one') {
......@@ -30,7 +30,7 @@ void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('Caching asset bundle test', () async {
final _TestAssetBundle bundle = _TestAssetBundle();
final TestAssetBundle bundle = TestAssetBundle();
final ByteData assetData = await bundle.load('one');
expect(assetData.getInt8(0), equals(49));
......@@ -53,7 +53,7 @@ void main() {
test('AssetImage.obtainKey succeeds with ImageConfiguration.empty', () async {
// This is a regression test for https://github.com/flutter/flutter/issues/12392
final AssetImage assetImage = AssetImage('one', bundle: _TestAssetBundle());
final AssetImage assetImage = AssetImage('one', bundle: TestAssetBundle());
final AssetBundleImageKey key = await assetImage.obtainKey(ImageConfiguration.empty);
expect(key.name, 'one');
expect(key.scale, 1.0);
......
......@@ -3,7 +3,6 @@
// found in the LICENSE file.
@TestOn('!chrome')
import 'dart:convert';
import 'dart:ui' as ui show Image;
import 'package:flutter/foundation.dart';
......@@ -17,32 +16,27 @@ import '../image_data.dart';
ByteData testByteData(double scale) => ByteData(8)..setFloat64(0, scale);
double scaleOf(ByteData data) => data.getFloat64(0);
final Map<dynamic, dynamic> testManifest = json.decode('''
const String testManifest = '''
{
"assets/image.png" : [
{"asset": "assets/1.5x/image.png", "dpr": 1.5},
{"asset": "assets/2.0x/image.png", "dpr": 2.0},
{"asset": "assets/3.0x/image.png", "dpr": 3.0},
{"asset": "assets/4.0x/image.png", "dpr": 4.0}
"assets/image.png",
"assets/1.5x/image.png",
"assets/2.0x/image.png",
"assets/3.0x/image.png",
"assets/4.0x/image.png"
]
}
''') as Map<dynamic, dynamic>;
''';
class TestAssetBundle extends CachingAssetBundle {
TestAssetBundle({ this.manifest = testManifest });
TestAssetBundle({ required Map<dynamic, dynamic> manifest }) {
this.manifest = const StandardMessageCodec().encodeMessage(manifest)!;
}
late final ByteData manifest;
final String manifest;
@override
Future<ByteData> load(String key) {
late ByteData data;
switch (key) {
case 'AssetManifest.bin':
data = manifest;
break;
case 'assets/image.png':
data = testByteData(1.0);
break;
......@@ -65,6 +59,14 @@ class TestAssetBundle extends CachingAssetBundle {
return SynchronousFuture<ByteData>(data);
}
@override
Future<String> loadString(String key, { bool cache = true }) {
if (key == 'AssetManifest.json') {
return SynchronousFuture<String>(manifest);
}
return SynchronousFuture<String>('');
}
@override
String toString() => '${describeIdentity(this)}()';
}
......@@ -104,7 +106,7 @@ Widget buildImageAtRatio(String imageName, Key key, double ratio, bool inferSize
devicePixelRatio: ratio,
),
child: DefaultAssetBundle(
bundle: bundle ?? TestAssetBundle(manifest: testManifest),
bundle: bundle ?? TestAssetBundle(),
child: Center(
child: inferSize ?
Image(
......@@ -257,21 +259,46 @@ void main() {
expect(getRenderImage(tester, key).scale, 4.0);
});
testWidgets('Image for device pixel ratio 1.0, with no main asset', (WidgetTester tester) async {
const String manifest = '''
{
"assets/image.png" : [
"assets/1.5x/image.png",
"assets/2.0x/image.png",
"assets/3.0x/image.png",
"assets/4.0x/image.png"
]
}
''';
final AssetBundle bundle = TestAssetBundle(manifest: manifest);
const double ratio = 1.0;
Key key = GlobalKey();
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images, bundle));
expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
expect(getRenderImage(tester, key).scale, 1.5);
key = GlobalKey();
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images, bundle));
expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
expect(getRenderImage(tester, key).scale, 1.5);
});
testWidgets('Image for device pixel ratio 1.0, with a main asset and a 1.0x asset', (WidgetTester tester) async {
// If both a main asset and a 1.0x asset are specified, then prefer
// the 1.0x asset.
final Map<dynamic, dynamic> manifest = json.decode('''
const String manifest = '''
{
"assets/image.png" : [
{"asset": "assets/1.0x/image.png", "dpr":1.0},
{"asset": "assets/1.5x/image.png", "dpr":1.5},
{"asset": "assets/2.0x/image.png", "dpr":2.0},
{"asset": "assets/3.0x/image.png", "dpr":3.0},
{"asset": "assets/4.0x/image.png", "dpr":4.0}
"assets/image.png",
"assets/1.0x/image.png",
"assets/1.5x/image.png",
"assets/2.0x/image.png",
"assets/3.0x/image.png",
"assets/4.0x/image.png"
]
}
''') as Map<dynamic, dynamic>;
''';
final AssetBundle bundle = TestAssetBundle(manifest: manifest);
const double ratio = 1.0;
......@@ -310,13 +337,14 @@ void main() {
// if higher resolution assets are not available we will pick the best
// available.
testWidgets('Low-resolution assets', (WidgetTester tester) async {
final AssetBundle bundle = TestAssetBundle(manifest: json.decode('''
final AssetBundle bundle = TestAssetBundle(manifest: '''
{
"assets/image.png" : [
{"asset": "assets/1.5x/image.png", "dpr": 1.5}
"assets/image.png",
"assets/1.5x/image.png"
]
}
''') as Map<dynamic, dynamic>);
''');
Future<void> testRatio({required double ratio, required double expectedScale}) async {
Key key = GlobalKey();
......
......@@ -2,11 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:typed_data';
import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart';
import 'package:standard_message_codec/standard_message_codec.dart';
import 'base/context.dart';
import 'base/deferred_component.dart';
......@@ -139,9 +136,6 @@ class ManifestAssetBundle implements AssetBundle {
_splitDeferredAssets = splitDeferredAssets,
_licenseCollector = LicenseCollector(fileSystem: fileSystem);
// We assume the main asset is designed for a device pixel ratio of 1.0
static const double _defaultResolution = 1.0;
final Logger _logger;
final FileSystem _fileSystem;
final LicenseCollector _licenseCollector;
......@@ -167,8 +161,7 @@ class ManifestAssetBundle implements AssetBundle {
DateTime? _lastBuildTimestamp;
static const String _kAssetManifestBinFileName = 'AssetManifest.bin';
static const String _kAssetManifestJsonFileName = 'AssetManifest.json';
static const String _kAssetManifestJson = 'AssetManifest.json';
static const String _kNoticeFile = 'NOTICES';
// Comically, this can't be name with the more common .gz file extension
// because when it's part of an AAR and brought into another APK via gradle,
......@@ -236,13 +229,8 @@ class ManifestAssetBundle implements AssetBundle {
// device.
_lastBuildTimestamp = DateTime.now();
if (flutterManifest.isEmpty) {
entries[_kAssetManifestJsonFileName] = DevFSStringContent('{}');
entryKinds[_kAssetManifestJsonFileName] = AssetKind.regular;
final ByteData emptyAssetManifest =
const StandardMessageCodec().encodeMessage(<dynamic, dynamic>{})!;
entries[_kAssetManifestBinFileName] =
DevFSByteContent(emptyAssetManifest.buffer.asUint8List(0, emptyAssetManifest.lengthInBytes));
entryKinds[_kAssetManifestBinFileName] = AssetKind.regular;
entries[_kAssetManifestJson] = DevFSStringContent('{}');
entryKinds[_kAssetManifestJson] = AssetKind.regular;
return 0;
}
......@@ -439,11 +427,7 @@ class ManifestAssetBundle implements AssetBundle {
_wildcardDirectories[uri] ??= _fileSystem.directory(uri);
}
final Map<String, List<String>> assetManifest =
_createAssetManifest(assetVariants, deferredComponentsAssetVariants);
final DevFSStringContent assetManifestJson = DevFSStringContent(json.encode(assetManifest));
final DevFSByteContent assetManifestBinary = _createAssetManifestBinary(assetManifest);
final DevFSStringContent assetManifest = _createAssetManifest(assetVariants, deferredComponentsAssetVariants);
final DevFSStringContent fontManifest = DevFSStringContent(json.encode(fonts));
final LicenseResult licenseResult = _licenseCollector.obtainLicenses(packageConfig, additionalLicenseFiles);
if (licenseResult.errorMessages.isNotEmpty) {
......@@ -467,8 +451,7 @@ class ManifestAssetBundle implements AssetBundle {
_fileSystem.file('DOES_NOT_EXIST_RERUN_FOR_WILDCARD$suffix').absolute);
}
_setIfChanged(_kAssetManifestJsonFileName, assetManifestJson, AssetKind.regular);
_setIfChanged(_kAssetManifestBinFileName, assetManifestBinary, AssetKind.regular);
_setIfChanged(_kAssetManifestJson, assetManifest, AssetKind.regular);
_setIfChanged(kFontManifestJson, fontManifest, AssetKind.regular);
_setLicenseIfChanged(licenseResult.combinedLicenses, targetPlatform);
return 0;
......@@ -477,31 +460,17 @@ class ManifestAssetBundle implements AssetBundle {
@override
List<File> additionalDependencies = <File>[];
void _setIfChanged(String key, DevFSContent content, AssetKind assetKind) {
bool areEqual(List<int> o1, List<int> o2) {
if (o1.length != o2.length) {
return false;
}
for (int index = 0; index < o1.length; index++) {
if (o1[index] != o2[index]) {
return false;
}
}
return true;
}
final DevFSContent? oldContent = entries[key];
// In the case that the content is unchanged, we want to avoid an overwrite
// as the isModified property may be reset to true,
if (oldContent is DevFSByteContent && content is DevFSByteContent &&
areEqual(oldContent.bytes, content.bytes)) {
void _setIfChanged(String key, DevFSStringContent content, AssetKind assetKind) {
if (!entries.containsKey(key)) {
entries[key] = content;
entryKinds[key] = assetKind;
return;
}
entries[key] = content;
entryKinds[key] = assetKind;
final DevFSStringContent? oldContent = entries[key] as DevFSStringContent?;
if (oldContent?.string != content.string) {
entries[key] = content;
entryKinds[key] = assetKind;
}
}
void _setLicenseIfChanged(
......@@ -653,14 +622,14 @@ class ManifestAssetBundle implements AssetBundle {
return deferredComponentsAssetVariants;
}
Map<String, List<String>> _createAssetManifest(
DevFSStringContent _createAssetManifest(
Map<_Asset, List<_Asset>> assetVariants,
Map<String, Map<_Asset, List<_Asset>>> deferredComponentsAssetVariants
) {
final Map<String, List<String>> manifest = <String, List<String>>{};
final Map<_Asset, List<String>> entries = <_Asset, List<String>>{};
final Map<String, List<String>> jsonObject = <String, List<String>>{};
final Map<_Asset, List<String>> jsonEntries = <_Asset, List<String>>{};
assetVariants.forEach((_Asset main, List<_Asset> variants) {
entries[main] = <String>[
jsonEntries[main] = <String>[
for (final _Asset variant in variants)
variant.entryUri.path,
];
......@@ -668,69 +637,26 @@ class ManifestAssetBundle implements AssetBundle {
if (deferredComponentsAssetVariants != null) {
for (final Map<_Asset, List<_Asset>> componentAssets in deferredComponentsAssetVariants.values) {
componentAssets.forEach((_Asset main, List<_Asset> variants) {
entries[main] = <String>[
jsonEntries[main] = <String>[
for (final _Asset variant in variants)
variant.entryUri.path,
];
});
}
}
final List<_Asset> sortedKeys = entries.keys.toList()
final List<_Asset> sortedKeys = jsonEntries.keys.toList()
..sort((_Asset left, _Asset right) => left.entryUri.path.compareTo(right.entryUri.path));
for (final _Asset main in sortedKeys) {
final String decodedEntryPath = Uri.decodeFull(main.entryUri.path);
final List<String> rawEntryVariantsPaths = entries[main]!;
final List<String> rawEntryVariantsPaths = jsonEntries[main]!;
final List<String> decodedEntryVariantPaths = rawEntryVariantsPaths
.map((String value) => Uri.decodeFull(value))
.toList();
manifest[decodedEntryPath] = decodedEntryVariantPaths;
}
return manifest;
}
DevFSByteContent _createAssetManifestBinary(
Map<String, List<String>> assetManifest
) {
double parseScale(String key) {
final Uri assetUri = Uri.parse(key);
String directoryPath = '';
if (assetUri.pathSegments.length > 1) {
directoryPath = assetUri.pathSegments[assetUri.pathSegments.length - 2];
}
final Match? match = _extractRatioRegExp.firstMatch(directoryPath);
if (match != null && match.groupCount > 0) {
return double.parse(match.group(1)!);
}
return _defaultResolution;
}
final Map<String, dynamic> result = <String, dynamic>{};
for (final MapEntry<String, dynamic> manifestEntry in assetManifest.entries) {
final List<dynamic> resultVariants = <dynamic>[];
final List<String> entries = (manifestEntry.value as List<dynamic>).cast<String>();
for (final String variant in entries) {
if (variant == manifestEntry.key) {
// With the newer binary format, don't include the main asset in it's
// list of variants. This reduces parsing time at runtime.
continue;
}
final Map<String, dynamic> resultVariant = <String, dynamic>{};
final double variantDevicePixelRatio = parseScale(variant);
resultVariant['asset'] = variant;
resultVariant['dpr'] = variantDevicePixelRatio;
resultVariants.add(resultVariant);
}
result[manifestEntry.key] = resultVariants;
jsonObject[decodedEntryPath] = decodedEntryVariantPaths;
}
final ByteData message = const StandardMessageCodec().encodeMessage(result)!;
return DevFSByteContent(message.buffer.asUint8List(0, message.lengthInBytes));
return DevFSStringContent(json.encode(jsonObject));
}
static final RegExp _extractRatioRegExp = RegExp(r'/?(\d+(\.\d*)?)x$');
/// Prefixes family names and asset paths of fonts included from packages with
/// 'packages/<package_name>'
List<Font> _parsePackageFonts(
......
......@@ -57,8 +57,6 @@ dependencies:
vm_service: 9.4.0
standard_message_codec: 0.0.1+3
_fe_analyzer_shared: 50.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
analyzer: 5.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
......@@ -90,6 +88,7 @@ dependencies:
watcher: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
dev_dependencies:
collection: 1.17.0
file_testing: 3.0.0
pubspec_parse: 1.2.1
......@@ -98,10 +97,9 @@ dev_dependencies:
json_annotation: 4.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
node_preamble: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
test: 1.22.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.17.0
dartdoc:
# Exclude this package from the hosted API docs.
nodoc: true
# PUBSPEC CHECKSUM: 408d
# PUBSPEC CHECKSUM: 65eb
......@@ -220,7 +220,7 @@ loading-units-spelled-wrong:
expect(logger.statusText, contains('Errors checking the following files:'));
expect(logger.statusText, contains("Invalid loading units yaml file, 'loading-units' entry did not exist."));
expect(logger.statusText, isNot(contains('Previously existing loading units no longer exist:\n\n LoadingUnit 2\n Libraries:\n - lib1\n')));
expect(logger.statusText.contains('Previously existing loading units no longer exist:\n\n LoadingUnit 2\n Libraries:\n - lib1\n'), false);
});
testWithoutContext('loadingUnitCache validator detects malformed file: not a list', () async {
......@@ -382,7 +382,7 @@ loading-units:
validator.displayResults();
validator.attemptToolExit();
expect(logger.statusText, isNot(contains('Errors checking the following files:')));
expect(logger.statusText.contains('Errors checking the following files:'), false);
});
testWithoutContext('androidStringMapping modifies strings file', () async {
......@@ -448,10 +448,9 @@ loading-units:
.childDirectory('main')
.childFile('AndroidManifest.xml');
expect(manifestOutput.existsSync(), true);
final String manifestOutputString = manifestOutput.readAsStringSync();
expect(manifestOutputString, contains('<meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="3:component1,2:component2,4:component2"/>'));
expect(manifestOutputString, isNot(contains('android:value="invalidmapping"')));
expect(manifestOutputString, contains("<!-- Don't delete the meta-data below."));
expect(manifestOutput.readAsStringSync().contains('<meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="3:component1,2:component2,4:component2"/>'), true);
expect(manifestOutput.readAsStringSync().contains('android:value="invalidmapping"'), false);
expect(manifestOutput.readAsStringSync().contains("<!-- Don't delete the meta-data below."), true);
});
testWithoutContext('androidStringMapping adds mapping when no existing mapping', () async {
......@@ -696,8 +695,8 @@ loading-units:
.childDirectory('main')
.childFile('AndroidManifest.xml');
expect(manifestOutput.existsSync(), true);
expect(manifestOutput.readAsStringSync(), contains('<meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="3:component1,2:component2,4:component2"/>'));
expect(manifestOutput.readAsStringSync(), isNot(contains(RegExp(r'android:value[\s\n]*=[\s\n]*"invalidmapping"'))));
expect(manifestOutput.readAsStringSync(), contains("<!-- Don't delete the meta-data below."));
expect(manifestOutput.readAsStringSync().contains('<meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="3:component1,2:component2,4:component2"/>'), true);
expect(manifestOutput.readAsStringSync().contains(RegExp(r'android:value[\s\n]*=[\s\n]*"invalidmapping"')), false);
expect(manifestOutput.readAsStringSync().contains("<!-- Don't delete the meta-data below."), true);
});
}
......@@ -111,9 +111,8 @@ $fontsSection
final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
await bundle.build(packagesPath: '.packages');
expect(bundle.entries.keys, containsAll(
<String>['AssetManifest.bin', 'AssetManifest.json', 'FontManifest.json', 'NOTICES.Z']
));
expect(bundle.entries.length, 3); // LICENSE, AssetManifest, FontManifest
expect(bundle.entries.containsKey('FontManifest.json'), isTrue);
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
......
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