blob: 8992ba60e215ddba9bda95cfd0c4dda5b9f6f1d3 [file] [log] [blame]
// Copyright 2014 The Flutter 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 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';
import 'asset_bundle.dart';
import 'binary_messenger.dart';
import 'restoration.dart';
import 'system_channels.dart';
/// Listens for platform messages and directs them to the [defaultBinaryMessenger].
///
/// 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, SchedulerBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
_defaultBinaryMessenger = createBinaryMessenger();
_restorationManager = createRestorationManager();
window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
initLicenses();
SystemChannels.system.setMessageHandler((dynamic message) => handleSystemMessage(message as Object));
SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
readInitialLifecycleStateFromNativeWindow();
}
/// The current [ServicesBinding], if one has been created.
static ServicesBinding? get instance => _instance;
static ServicesBinding? _instance;
/// The default instance of [BinaryMessenger].
///
/// This is used to send messages from the application to the platform, and
/// keeps track of which handlers have been registered on each channel so
/// it may dispatch incoming messages to the registered handler.
BinaryMessenger get defaultBinaryMessenger => _defaultBinaryMessenger;
late BinaryMessenger _defaultBinaryMessenger;
/// Creates a default [BinaryMessenger] instance that can be used for sending
/// platform messages.
@protected
BinaryMessenger createBinaryMessenger() {
return const _DefaultBinaryMessenger._();
}
/// Called when the operating system notifies the application of a memory
/// pressure situation.
///
/// This method exposes the `memoryPressure` notification from
/// [SystemChannels.system].
@protected
@mustCallSuper
void handleMemoryPressure() { }
/// Handler called for messages received on the [SystemChannels.system]
/// message channel.
///
/// Other bindings may override this to respond to incoming system messages.
@protected
@mustCallSuper
Future<void> handleSystemMessage(Object systemMessage) async {
final Map<String, dynamic> message = systemMessage as Map<String, dynamic>;
final String type = message['type'] as String;
switch (type) {
case 'memoryPressure':
handleMemoryPressure();
break;
}
return;
}
/// 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* {
// 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>();
scheduleTask(() async {
rawLicenses.complete(await rootBundle.loadString('NOTICES', cache: false));
}, Priority.animation);
await rawLicenses.future;
final Completer<List<LicenseEntry>> parsedLicenses = Completer<List<LicenseEntry>>();
scheduleTask(() async {
parsedLicenses.complete(compute<String, List<LicenseEntry>>(_parseLicenses, await rawLicenses.future, debugLabel: 'parseLicenses'));
}, Priority.animation);
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 (final 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);
}
// App life cycle
/// Initializes the [lifecycleState] with the
/// [dart:ui.SingletonFlutterWindow.initialLifecycleState].
///
/// Once the [lifecycleState] is populated through any means (including this
/// method), this method will do nothing. This is because the
/// [dart:ui.SingletonFlutterWindow.initialLifecycleState] may already be
/// stale and it no longer makes sense to use the initial state at dart vm
/// startup as the current state anymore.
///
/// The latest state should be obtained by subscribing to
/// [WidgetsBindingObserver.didChangeAppLifecycleState].
@protected
void readInitialLifecycleStateFromNativeWindow() {
if (lifecycleState != null) {
return;
}
final AppLifecycleState? state = _parseAppLifecycleMessage(window.initialLifecycleState);
if (state != null) {
handleAppLifecycleStateChanged(state);
}
}
Future<String?> _handleLifecycleMessage(String? message) async {
handleAppLifecycleStateChanged(_parseAppLifecycleMessage(message!)!);
return null;
}
static AppLifecycleState? _parseAppLifecycleMessage(String message) {
switch (message) {
case 'AppLifecycleState.paused':
return AppLifecycleState.paused;
case 'AppLifecycleState.resumed':
return AppLifecycleState.resumed;
case 'AppLifecycleState.inactive':
return AppLifecycleState.inactive;
case 'AppLifecycleState.detached':
return AppLifecycleState.detached;
}
return null;
}
/// The [RestorationManager] synchronizes the restoration data between
/// engine and framework.
///
/// See the docs for [RestorationManager] for a discussion of restoration
/// state and how it is organized in Flutter.
///
/// To use a different [RestorationManager] subclasses can override
/// [createRestorationManager], which is called to create the instance
/// returned by this getter.
RestorationManager get restorationManager => _restorationManager;
late RestorationManager _restorationManager;
/// Creates the [RestorationManager] instance available via
/// [restorationManager].
///
/// Can be overridden in subclasses to create a different [RestorationManager].
@protected
RestorationManager createRestorationManager() {
return RestorationManager();
}
}
/// The default implementation of [BinaryMessenger].
///
/// This messenger sends messages from the app-side to the platform-side and
/// dispatches incoming messages from the platform-side to the appropriate
/// handler.
class _DefaultBinaryMessenger extends BinaryMessenger {
const _DefaultBinaryMessenger._();
// Handlers for incoming messages from platform plugins.
// This is static so that this class can have a const constructor.
static final Map<String, MessageHandler> _handlers =
<String, MessageHandler>{};
// Mock handlers that intercept and respond to outgoing messages.
// This is static so that this class can have a const constructor.
static final Map<String, MessageHandler> _mockHandlers =
<String, MessageHandler>{};
Future<ByteData?> _sendPlatformMessage(String channel, ByteData? message) {
final Completer<ByteData?> completer = Completer<ByteData?>();
// ui.PlatformDispatcher.instance is accessed directly instead of using
// ServicesBinding.instance.platformDispatcher because this method might be
// invoked before any binding is initialized. This issue was reported in
// #27541. It is not ideal to statically access
// ui.PlatformDispatcher.instance because the PlatformDispatcher may be
// dependency injected elsewhere with a different instance. However, static
// access at this location seems to be the least bad option.
ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message, (ByteData? reply) {
try {
completer.complete(reply);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message response callback'),
));
}
});
return completer.future;
}
@override
Future<void> handlePlatformMessage(
String channel,
ByteData? data,
ui.PlatformMessageResponseCallback? callback,
) async {
ByteData? response;
try {
final MessageHandler? handler = _handlers[channel];
if (handler != null) {
response = await handler(data);
} else {
ui.channelBuffers.push(channel, data, callback!);
callback = null;
}
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message callback'),
));
} finally {
if (callback != null) {
callback(response);
}
}
}
@override
Future<ByteData?>? send(String channel, ByteData? message) {
final MessageHandler? handler = _mockHandlers[channel];
if (handler != null)
return handler(message);
return _sendPlatformMessage(channel, message);
}
@override
void setMessageHandler(String channel, MessageHandler? handler) {
if (handler == null) {
_handlers.remove(channel);
} else {
_handlers[channel] = handler;
ui.channelBuffers.drain(channel, (ByteData? data, ui.PlatformMessageResponseCallback callback) async {
await handlePlatformMessage(channel, data, callback);
});
}
}
@override
bool checkMessageHandler(String channel, MessageHandler? handler) => _handlers[channel] == handler;
@override
void setMockMessageHandler(String channel, MessageHandler? handler) {
if (handler == null)
_mockHandlers.remove(channel);
else
_mockHandlers[channel] = handler;
}
@override
bool checkMockMessageHandler(String channel, MessageHandler? handler) => _mockHandlers[channel] == handler;
}