2
2
// Use of this source code is governed by a BSD-style license that can be
3
3
// found in the LICENSE file.
4
4
5
+ import 'dart:typed_data' ;
6
+
5
7
import 'package:meta/meta.dart' ;
6
8
import 'package:package_config/package_config.dart' ;
9
+ import 'package:standard_message_codec/standard_message_codec.dart' ;
7
10
8
11
import 'base/context.dart' ;
9
12
import 'base/deferred_component.dart' ;
@@ -162,7 +165,11 @@ class ManifestAssetBundle implements AssetBundle {
162
165
163
166
DateTime ? _lastBuildTimestamp;
164
167
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
+
166
173
static const String _kNoticeFile = 'NOTICES' ;
167
174
// Comically, this can't be name with the more common .gz file extension
168
175
// because when it's part of an AAR and brought into another APK via gradle,
@@ -230,8 +237,15 @@ class ManifestAssetBundle implements AssetBundle {
230
237
// device.
231
238
_lastBuildTimestamp = DateTime .now ();
232
239
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;
235
249
return 0 ;
236
250
}
237
251
@@ -428,7 +442,10 @@ class ManifestAssetBundle implements AssetBundle {
428
442
_wildcardDirectories[uri] ?? = _fileSystem.directory (uri);
429
443
}
430
444
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);
432
449
final DevFSStringContent fontManifest = DevFSStringContent (json.encode (fonts));
433
450
final LicenseResult licenseResult = _licenseCollector.obtainLicenses (packageConfig, additionalLicenseFiles);
434
451
if (licenseResult.errorMessages.isNotEmpty) {
@@ -452,26 +469,40 @@ class ManifestAssetBundle implements AssetBundle {
452
469
_fileSystem.file ('DOES_NOT_EXIST_RERUN_FOR_WILDCARD$suffix ' ).absolute);
453
470
}
454
471
455
- _setIfChanged (_kAssetManifestJson, assetManifest, AssetKind .regular);
472
+ _setIfChanged (_kAssetManifestJsonFilename, assetManifestJson, AssetKind .regular);
473
+ _setIfChanged (_kAssetManifestBinFilename, assetManifestBinary, AssetKind .regular);
456
474
_setIfChanged (kFontManifestJson, fontManifest, AssetKind .regular);
457
475
_setLicenseIfChanged (licenseResult.combinedLicenses, targetPlatform);
458
476
return 0 ;
459
477
}
460
478
461
479
@override
462
480
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)) {
468
487
return ;
469
488
}
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 ;
474
497
}
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 ;
475
506
}
476
507
477
508
void _setLicenseIfChanged (
@@ -623,39 +654,84 @@ class ManifestAssetBundle implements AssetBundle {
623
654
return deferredComponentsAssetVariants;
624
655
}
625
656
626
- DevFSStringContent _createAssetManifest (
657
+ Map < String , List < String >> _createAssetManifest (
627
658
Map <_Asset , List <_Asset >> assetVariants,
628
659
Map <String , Map <_Asset , List <_Asset >>> deferredComponentsAssetVariants
629
660
) {
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 >> {};
632
663
assetVariants.forEach ((_Asset main, List <_Asset > variants) {
633
- jsonEntries [main] = < String > [
664
+ entries [main] = < String > [
634
665
for (final _Asset variant in variants)
635
666
variant.entryUri.path,
636
667
];
637
668
});
638
669
if (deferredComponentsAssetVariants != null ) {
639
670
for (final Map <_Asset , List <_Asset >> componentAssets in deferredComponentsAssetVariants.values) {
640
671
componentAssets.forEach ((_Asset main, List <_Asset > variants) {
641
- jsonEntries [main] = < String > [
672
+ entries [main] = < String > [
642
673
for (final _Asset variant in variants)
643
674
variant.entryUri.path,
644
675
];
645
676
});
646
677
}
647
678
}
648
- final List <_Asset > sortedKeys = jsonEntries .keys.toList ()
679
+ final List <_Asset > sortedKeys = entries .keys.toList ()
649
680
..sort ((_Asset left, _Asset right) => left.entryUri.path.compareTo (right.entryUri.path));
650
681
for (final _Asset main in sortedKeys) {
651
682
final String decodedEntryPath = Uri .decodeFull (main.entryUri.path);
652
- final List <String > rawEntryVariantsPaths = jsonEntries [main]! ;
683
+ final List <String > rawEntryVariantsPaths = entries [main]! ;
653
684
final List <String > decodedEntryVariantPaths = rawEntryVariantsPaths
654
685
.map ((String value) => Uri .decodeFull (value))
655
686
.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;
657
711
}
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));
659
735
}
660
736
661
737
/// Prefixes family names and asset paths of fonts included from packages with
0 commit comments