Unverified Commit b6c6e365 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

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
parent 10f721c8
...@@ -160,6 +160,15 @@ abstract class CachingAssetBundle extends AssetBundle { ...@@ -160,6 +160,15 @@ abstract class CachingAssetBundle extends AssetBundle {
final ByteData data = await load(key); final ByteData data = await load(key);
if (data == null) if (data == null)
throw new FlutterError('Unable to load asset: $key'); 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()); return UTF8.decode(data.buffer.asUint8List());
} }
......
...@@ -40,9 +40,32 @@ abstract class ServicesBinding extends BindingBase { ...@@ -40,9 +40,32 @@ abstract class ServicesBinding extends BindingBase {
} }
Stream<LicenseEntry> _addLicenses() async* { Stream<LicenseEntry> _addLicenses() async* {
final String rawLicenses = await rootBundle.loadString('LICENSE', cache: false); // We use timers here (rather than scheduleTask from the scheduler binding)
final List<LicenseEntry> licenses = await compute(_parseLicenses, rawLicenses, debugLabel: 'parseLicenses'); // because the services layer can't use the scheduler binding (the scheduler
yield* new Stream<LicenseEntry>.fromIterable(licenses); // 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. // This is run in another isolate created by _addLicenses above.
......
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