From b6c6e365894785c282c38924445d5cf5ee72b8b5 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Thu, 25 Jan 2018 13:51:49 -0800 Subject: [PATCH] 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 --- .../lib/src/services/asset_bundle.dart | 9 ++++++ .../flutter/lib/src/services/binding.dart | 29 +++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/flutter/lib/src/services/asset_bundle.dart b/packages/flutter/lib/src/services/asset_bundle.dart index 5e38f9bf38..29f0178dd2 100644 --- a/packages/flutter/lib/src/services/asset_bundle.dart +++ b/packages/flutter/lib/src/services/asset_bundle.dart @@ -160,6 +160,15 @@ abstract class CachingAssetBundle extends AssetBundle { 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 f4e1919aea..676ac389a9 100644 --- a/packages/flutter/lib/src/services/binding.dart +++ b/packages/flutter/lib/src/services/binding.dart @@ -40,9 +40,32 @@ abstract class ServicesBinding extends BindingBase { } Stream _addLicenses() async* { - final String rawLicenses = await rootBundle.loadString('LICENSE', cache: false); - final List licenses = await compute(_parseLicenses, rawLicenses, debugLabel: 'parseLicenses'); - yield* new Stream.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 rawLicenses = new Completer(); + Timer.run(() async { + rawLicenses.complete(rootBundle.loadString('LICENSE', cache: false)); + }); + await rawLicenses.future; + final Completer> parsedLicenses = new Completer>(); + Timer.run(() async { + parsedLicenses.complete(compute(_parseLicenses, await rawLicenses.future, debugLabel: 'parseLicenses')); + }); + await parsedLicenses.future; + yield* new Stream.fromIterable(await parsedLicenses.future); } // This is run in another isolate created by _addLicenses above.