Move UTF-8 decoding off the main thread. (#14206)
This reduces the jank of bringing up the license screen further.
The remaining lost frame or two are caused by Dart itself, see:
https://github.com/dart-lang/sdk/issues/31954
https://github.com/dart-lang/sdk/issues/31959
https://github.com/dart-lang/sdk/issues/31960
Fixes https://github.com/flutter/flutter/issues/5187
diff --git a/packages/flutter/lib/src/services/asset_bundle.dart b/packages/flutter/lib/src/services/asset_bundle.dart
index 5e38f9b..29f0178 100644
--- a/packages/flutter/lib/src/services/asset_bundle.dart
+++ b/packages/flutter/lib/src/services/asset_bundle.dart
@@ -160,6 +160,15 @@
final ByteData data = await load(key);
if (data == null)
throw new FlutterError('Unable to load asset: $key');
+ if (data.lengthInBytes < 10 * 1024) {
+ // 10KB takes about 3ms to parse on a Pixel 2 XL.
+ // See: https://github.com/dart-lang/sdk/issues/31954
+ return UTF8.decode(data.buffer.asUint8List());
+ }
+ return compute(_utf8decode, data, debugLabel: 'UTF8 decode for "$key"');
+ }
+
+ static String _utf8decode(ByteData data) {
return UTF8.decode(data.buffer.asUint8List());
}
diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart
index f4e1919..676ac38 100644
--- a/packages/flutter/lib/src/services/binding.dart
+++ b/packages/flutter/lib/src/services/binding.dart
@@ -40,9 +40,32 @@
}
Stream<LicenseEntry> _addLicenses() async* {
- final String rawLicenses = await rootBundle.loadString('LICENSE', cache: false);
- final List<LicenseEntry> licenses = await compute(_parseLicenses, rawLicenses, debugLabel: 'parseLicenses');
- yield* new Stream<LicenseEntry>.fromIterable(licenses);
+ // We use timers here (rather than scheduleTask from the scheduler binding)
+ // because the services layer can't use the scheduler binding (the scheduler
+ // binding uses the services layer to manage its lifecycle events). Timers
+ // are what scheduleTask uses under the hood anyway. The only difference is
+ // that these will just run next, instead of being prioritized relative to
+ // the other tasks that might be running. Using _something_ here to break
+ // this into two parts is important because isolates take a while to copy
+ // data at the moment, and if we receive the data in the same event loop
+ // iteration as we send the data to the next isolate, we are definitely
+ // going to miss frames. Another solution would be to have the work all
+ // happen in one isolate, and we may go there eventually, but first we are
+ // going to see if isolate communication can be made cheaper.
+ // See: https://github.com/dart-lang/sdk/issues/31959
+ // https://github.com/dart-lang/sdk/issues/31960
+ // TODO(ianh): Remove this complexity once these bugs are fixed.
+ final Completer<String> rawLicenses = new Completer<String>();
+ Timer.run(() async {
+ rawLicenses.complete(rootBundle.loadString('LICENSE', cache: false));
+ });
+ await rawLicenses.future;
+ final Completer<List<LicenseEntry>> parsedLicenses = new Completer<List<LicenseEntry>>();
+ Timer.run(() async {
+ parsedLicenses.complete(compute(_parseLicenses, await rawLicenses.future, debugLabel: 'parseLicenses'));
+ });
+ await parsedLicenses.future;
+ yield* new Stream<LicenseEntry>.fromIterable(await parsedLicenses.future);
}
// This is run in another isolate created by _addLicenses above.