Skip to content

Commit 5630d53

Browse files
[tool] Generate a binary version of the asset manifest (#117233)
* initial * update asset_bundle_package_test * Update asset_bundle_test.dart * Update asset_bundle_package_fonts_test.dart * update pubspec checksum for smc dependency * flutter update-packages --force-upgrade * prefer += 1 over ++ Co-authored-by: Jonah Williams <[email protected]> * add regexp comment * rescope int list comparison function * update packages Co-authored-by: Jonah Williams <[email protected]>
1 parent 40bc6b5 commit 5630d53

File tree

8 files changed

+231
-96
lines changed

8 files changed

+231
-96
lines changed

dev/benchmarks/multiple_flutters/module/pubspec.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ dependencies:
4141
typed_data: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
4242
vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
4343
win32: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
44-
xdg_directories: 0.2.0+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
44+
xdg_directories: 0.2.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
4545

4646
flutter:
4747
uses-material-design: true
@@ -51,4 +51,4 @@ flutter:
5151
androidPackage: com.example.multiple_flutters_module
5252
iosBundleIdentifier: com.example.multipleFluttersModule
5353

54-
# PUBSPEC CHECKSUM: e9fc
54+
# PUBSPEC CHECKSUM: eafd

dev/integration_tests/android_views/pubspec.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ dependencies:
4747
vm_service: 9.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
4848
webdriver: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
4949
win32: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
50-
xdg_directories: 0.2.0+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
50+
xdg_directories: 0.2.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
5151

5252
dev_dependencies:
5353
flutter_test:
@@ -94,4 +94,4 @@ dev_dependencies:
9494
flutter:
9595
uses-material-design: true
9696

97-
# PUBSPEC CHECKSUM: 11b0
97+
# PUBSPEC CHECKSUM: 28b1

dev/integration_tests/hybrid_android_views/pubspec.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ dependencies:
4545
vm_service: 9.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
4646
webdriver: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
4747
win32: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
48-
xdg_directories: 0.2.0+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
48+
xdg_directories: 0.2.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
4949

5050
dev_dependencies:
5151
flutter_test:
@@ -92,4 +92,4 @@ dev_dependencies:
9292
flutter:
9393
uses-material-design: true
9494

95-
# PUBSPEC CHECKSUM: 11b0
95+
# PUBSPEC CHECKSUM: 28b1

packages/flutter_tools/lib/src/asset.dart

+99-23
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:typed_data';
6+
57
import 'package:meta/meta.dart';
68
import 'package:package_config/package_config.dart';
9+
import 'package:standard_message_codec/standard_message_codec.dart';
710

811
import 'base/context.dart';
912
import 'base/deferred_component.dart';
@@ -162,7 +165,11 @@ class ManifestAssetBundle implements AssetBundle {
162165

163166
DateTime? _lastBuildTimestamp;
164167

165-
static const String _kAssetManifestJson = 'AssetManifest.json';
168+
// We assume the main asset is designed for a device pixel ratio of 1.0.
169+
static const double _defaultResolution = 1.0;
170+
static const String _kAssetManifestJsonFilename = 'AssetManifest.json';
171+
static const String _kAssetManifestBinFilename = 'AssetManifest.bin';
172+
166173
static const String _kNoticeFile = 'NOTICES';
167174
// Comically, this can't be name with the more common .gz file extension
168175
// because when it's part of an AAR and brought into another APK via gradle,
@@ -230,8 +237,15 @@ class ManifestAssetBundle implements AssetBundle {
230237
// device.
231238
_lastBuildTimestamp = DateTime.now();
232239
if (flutterManifest.isEmpty) {
233-
entries[_kAssetManifestJson] = DevFSStringContent('{}');
234-
entryKinds[_kAssetManifestJson] = AssetKind.regular;
240+
entries[_kAssetManifestJsonFilename] = DevFSStringContent('{}');
241+
entryKinds[_kAssetManifestJsonFilename] = AssetKind.regular;
242+
entries[_kAssetManifestJsonFilename] = DevFSStringContent('{}');
243+
entryKinds[_kAssetManifestJsonFilename] = AssetKind.regular;
244+
final ByteData emptyAssetManifest =
245+
const StandardMessageCodec().encodeMessage(<dynamic, dynamic>{})!;
246+
entries[_kAssetManifestBinFilename] =
247+
DevFSByteContent(emptyAssetManifest.buffer.asUint8List(0, emptyAssetManifest.lengthInBytes));
248+
entryKinds[_kAssetManifestBinFilename] = AssetKind.regular;
235249
return 0;
236250
}
237251

@@ -428,7 +442,10 @@ class ManifestAssetBundle implements AssetBundle {
428442
_wildcardDirectories[uri] ??= _fileSystem.directory(uri);
429443
}
430444

431-
final DevFSStringContent assetManifest = _createAssetManifest(assetVariants, deferredComponentsAssetVariants);
445+
final Map<String, List<String>> assetManifest =
446+
_createAssetManifest(assetVariants, deferredComponentsAssetVariants);
447+
final DevFSStringContent assetManifestJson = DevFSStringContent(json.encode(assetManifest));
448+
final DevFSByteContent assetManifestBinary = _createAssetManifestBinary(assetManifest);
432449
final DevFSStringContent fontManifest = DevFSStringContent(json.encode(fonts));
433450
final LicenseResult licenseResult = _licenseCollector.obtainLicenses(packageConfig, additionalLicenseFiles);
434451
if (licenseResult.errorMessages.isNotEmpty) {
@@ -452,26 +469,40 @@ class ManifestAssetBundle implements AssetBundle {
452469
_fileSystem.file('DOES_NOT_EXIST_RERUN_FOR_WILDCARD$suffix').absolute);
453470
}
454471

455-
_setIfChanged(_kAssetManifestJson, assetManifest, AssetKind.regular);
472+
_setIfChanged(_kAssetManifestJsonFilename, assetManifestJson, AssetKind.regular);
473+
_setIfChanged(_kAssetManifestBinFilename, assetManifestBinary, AssetKind.regular);
456474
_setIfChanged(kFontManifestJson, fontManifest, AssetKind.regular);
457475
_setLicenseIfChanged(licenseResult.combinedLicenses, targetPlatform);
458476
return 0;
459477
}
460478

461479
@override
462480
List<File> additionalDependencies = <File>[];
463-
464-
void _setIfChanged(String key, DevFSStringContent content, AssetKind assetKind) {
465-
if (!entries.containsKey(key)) {
466-
entries[key] = content;
467-
entryKinds[key] = assetKind;
481+
void _setIfChanged(String key, DevFSContent content, AssetKind assetKind) {
482+
final DevFSContent? oldContent = entries[key];
483+
// In the case that the content is unchanged, we want to avoid an overwrite
484+
// as the isModified property may be reset to true,
485+
if (oldContent is DevFSByteContent && content is DevFSByteContent &&
486+
_compareIntLists(oldContent.bytes, content.bytes)) {
468487
return;
469488
}
470-
final DevFSStringContent? oldContent = entries[key] as DevFSStringContent?;
471-
if (oldContent?.string != content.string) {
472-
entries[key] = content;
473-
entryKinds[key] = assetKind;
489+
490+
entries[key] = content;
491+
entryKinds[key] = assetKind;
492+
}
493+
494+
static bool _compareIntLists(List<int> o1, List<int> o2) {
495+
if (o1.length != o2.length) {
496+
return false;
474497
}
498+
499+
for (int index = 0; index < o1.length; index++) {
500+
if (o1[index] != o2[index]) {
501+
return false;
502+
}
503+
}
504+
505+
return true;
475506
}
476507

477508
void _setLicenseIfChanged(
@@ -623,39 +654,84 @@ class ManifestAssetBundle implements AssetBundle {
623654
return deferredComponentsAssetVariants;
624655
}
625656

626-
DevFSStringContent _createAssetManifest(
657+
Map<String, List<String>> _createAssetManifest(
627658
Map<_Asset, List<_Asset>> assetVariants,
628659
Map<String, Map<_Asset, List<_Asset>>> deferredComponentsAssetVariants
629660
) {
630-
final Map<String, List<String>> jsonObject = <String, List<String>>{};
631-
final Map<_Asset, List<String>> jsonEntries = <_Asset, List<String>>{};
661+
final Map<String, List<String>> manifest = <String, List<String>>{};
662+
final Map<_Asset, List<String>> entries = <_Asset, List<String>>{};
632663
assetVariants.forEach((_Asset main, List<_Asset> variants) {
633-
jsonEntries[main] = <String>[
664+
entries[main] = <String>[
634665
for (final _Asset variant in variants)
635666
variant.entryUri.path,
636667
];
637668
});
638669
if (deferredComponentsAssetVariants != null) {
639670
for (final Map<_Asset, List<_Asset>> componentAssets in deferredComponentsAssetVariants.values) {
640671
componentAssets.forEach((_Asset main, List<_Asset> variants) {
641-
jsonEntries[main] = <String>[
672+
entries[main] = <String>[
642673
for (final _Asset variant in variants)
643674
variant.entryUri.path,
644675
];
645676
});
646677
}
647678
}
648-
final List<_Asset> sortedKeys = jsonEntries.keys.toList()
679+
final List<_Asset> sortedKeys = entries.keys.toList()
649680
..sort((_Asset left, _Asset right) => left.entryUri.path.compareTo(right.entryUri.path));
650681
for (final _Asset main in sortedKeys) {
651682
final String decodedEntryPath = Uri.decodeFull(main.entryUri.path);
652-
final List<String> rawEntryVariantsPaths = jsonEntries[main]!;
683+
final List<String> rawEntryVariantsPaths = entries[main]!;
653684
final List<String> decodedEntryVariantPaths = rawEntryVariantsPaths
654685
.map((String value) => Uri.decodeFull(value))
655686
.toList();
656-
jsonObject[decodedEntryPath] = decodedEntryVariantPaths;
687+
manifest[decodedEntryPath] = decodedEntryVariantPaths;
688+
}
689+
return manifest;
690+
}
691+
692+
// Matches path-like strings ending in a number followed by an 'x'.
693+
// Example matches include "assets/animals/2.0x", "plants/3x", and "2.7x".
694+
static final RegExp _extractPixelRatioFromKeyRegExp = RegExp(r'/?(\d+(\.\d*)?)x$');
695+
696+
DevFSByteContent _createAssetManifestBinary(
697+
Map<String, List<String>> assetManifest
698+
) {
699+
double parseScale(String key) {
700+
final Uri assetUri = Uri.parse(key);
701+
String directoryPath = '';
702+
if (assetUri.pathSegments.length > 1) {
703+
directoryPath = assetUri.pathSegments[assetUri.pathSegments.length - 2];
704+
}
705+
706+
final Match? match = _extractPixelRatioFromKeyRegExp.firstMatch(directoryPath);
707+
if (match != null && match.groupCount > 0) {
708+
return double.parse(match.group(1)!);
709+
}
710+
return _defaultResolution;
657711
}
658-
return DevFSStringContent(json.encode(jsonObject));
712+
713+
final Map<String, dynamic> result = <String, dynamic>{};
714+
715+
for (final MapEntry<String, dynamic> manifestEntry in assetManifest.entries) {
716+
final List<dynamic> resultVariants = <dynamic>[];
717+
final List<String> entries = (manifestEntry.value as List<dynamic>).cast<String>();
718+
for (final String variant in entries) {
719+
if (variant == manifestEntry.key) {
720+
// With the newer binary format, don't include the main asset in it's
721+
// list of variants. This reduces parsing time at runtime.
722+
continue;
723+
}
724+
final Map<String, dynamic> resultVariant = <String, dynamic>{};
725+
final double variantDevicePixelRatio = parseScale(variant);
726+
resultVariant['asset'] = variant;
727+
resultVariant['dpr'] = variantDevicePixelRatio;
728+
resultVariants.add(resultVariant);
729+
}
730+
result[manifestEntry.key] = resultVariants;
731+
}
732+
733+
final ByteData message = const StandardMessageCodec().encodeMessage(result)!;
734+
return DevFSByteContent(message.buffer.asUint8List(0, message.lengthInBytes));
659735
}
660736

661737
/// Prefixes family names and asset paths of fonts included from packages with

packages/flutter_tools/pubspec.yaml

+3-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ dependencies:
5757

5858
vm_service: 9.4.0
5959

60+
standard_message_codec: 0.0.1+3
61+
6062
_fe_analyzer_shared: 52.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
6163
analyzer: 5.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
6264
boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -102,4 +104,4 @@ dartdoc:
102104
# Exclude this package from the hosted API docs.
103105
nodoc: true
104106

105-
# PUBSPEC CHECKSUM: 02f9
107+
# PUBSPEC CHECKSUM: 899b

packages/flutter_tools/test/general.shard/asset_bundle_package_fonts_test.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ $fontsSection
111111

112112
final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
113113
await bundle.build(packagesPath: '.packages');
114-
expect(bundle.entries.length, 3); // LICENSE, AssetManifest, FontManifest
115-
expect(bundle.entries.containsKey('FontManifest.json'), isTrue);
114+
expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.bin',
115+
'AssetManifest.json', 'FontManifest.json', 'NOTICES.Z']));
116116
}, overrides: <Type, Generator>{
117117
FileSystem: () => testFileSystem,
118118
ProcessManager: () => FakeProcessManager.any(),

0 commit comments

Comments
 (0)