blob: a1e5554dd3d80ee992e2ddb36d2b886f138ce6a0 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'asset_bundle.dart';
import 'platform_messages.dart';
/// Listens for platform messages and directs them to [BinaryMessages].
///
/// The [ServicesBinding] also registers a [LicenseEntryCollector] that exposes
/// the licenses found in the `LICENSE` file stored at the root of the asset
/// bundle, and implements the `ext.flutter.evict` service extension (see
/// [evict]).
mixin ServicesBinding on BindingBase {
@override
void initInstances() {
super.initInstances();
_instance = this;
window
..onPlatformMessage = BinaryMessages.handlePlatformMessage;
initLicenses();
}
/// The current [ServicesBinding], if one has been created.
static ServicesBinding get instance => _instance;
static ServicesBinding _instance;
/// Adds relevant licenses to the [LicenseRegistry].
///
/// By default, the [ServicesBinding]'s implementation of [initLicenses] adds
/// all the licenses collected by the `flutter` tool during compilation.
@protected
@mustCallSuper
void initLicenses() {
LicenseRegistry.addLicense(_addLicenses);
}
Stream<LicenseEntry> _addLicenses() async* {
// 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 = Completer<String>();
Timer.run(() async {
rawLicenses.complete(rootBundle.loadString('LICENSE', cache: false));
});
await rawLicenses.future;
final Completer<List<LicenseEntry>> parsedLicenses = Completer<List<LicenseEntry>>();
Timer.run(() async {
parsedLicenses.complete(compute(_parseLicenses, await rawLicenses.future, debugLabel: 'parseLicenses'));
});
await parsedLicenses.future;
yield* Stream<LicenseEntry>.fromIterable(await parsedLicenses.future);
}
// This is run in another isolate created by _addLicenses above.
static List<LicenseEntry> _parseLicenses(String rawLicenses) {
final String _licenseSeparator = '\n' + ('-' * 80) + '\n';
final List<LicenseEntry> result = <LicenseEntry>[];
final List<String> licenses = rawLicenses.split(_licenseSeparator);
for (String license in licenses) {
final int split = license.indexOf('\n\n');
if (split >= 0) {
result.add(LicenseEntryWithLineBreaks(
license.substring(0, split).split('\n'),
license.substring(split + 2),
));
} else {
result.add(LicenseEntryWithLineBreaks(const <String>[], license));
}
}
return result;
}
@override
void initServiceExtensions() {
super.initServiceExtensions();
assert(() {
registerStringServiceExtension(
// ext.flutter.evict value=foo.png will cause foo.png to be evicted from
// the rootBundle cache and cause the entire image cache to be cleared.
// This is used by hot reload mode to clear out the cache of resources
// that have changed.
name: 'evict',
getter: () async => '',
setter: (String value) async {
evict(value);
},
);
return true;
}());
}
/// Called in response to the `ext.flutter.evict` service extension.
///
/// This is used by the `flutter` tool during hot reload so that any images
/// that have changed on disk get cleared from caches.
@protected
@mustCallSuper
void evict(String asset) {
rootBundle.evict(asset);
}
}