Started handling messages from background isolates. (#109005)
Started handling messages from background isolates.
diff --git a/dev/integration_tests/channels/android/app/src/main/AndroidManifest.xml b/dev/integration_tests/channels/android/app/src/main/AndroidManifest.xml
index 6bf32c2..d63de06 100644
--- a/dev/integration_tests/channels/android/app/src/main/AndroidManifest.xml
+++ b/dev/integration_tests/channels/android/app/src/main/AndroidManifest.xml
@@ -18,6 +18,7 @@
Application and put your custom class here. -->
<application android:name="${applicationName}" android:label="channels" android:icon="@mipmap/ic_launcher">
<activity android:name=".MainActivity"
+ android:exported="true"
android:launchMode="singleTop"
android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
diff --git a/dev/integration_tests/channels/ios/Runner/AppDelegate.m b/dev/integration_tests/channels/ios/Runner/AppDelegate.m
index 719abd3..8fc53c3 100644
--- a/dev/integration_tests/channels/ios/Runner/AppDelegate.m
+++ b/dev/integration_tests/channels/ios/Runner/AppDelegate.m
@@ -114,7 +114,18 @@
[FlutterMethodChannel methodChannelWithName:@"std-method"
binaryMessenger:flutterController
codec:[FlutterStandardMethodCodec codecWithReaderWriter:extendedReaderWriter]]];
- return [super application:application didFinishLaunchingWithOptions:launchOptions];
+
+ [[FlutterBasicMessageChannel
+ messageChannelWithName:@"std-echo"
+ binaryMessenger:flutterController
+ codec:[FlutterStandardMessageCodec
+ codecWithReaderWriter:extendedReaderWriter]]
+ setMessageHandler:^(id message, FlutterReply reply) {
+ reply(message);
+ }];
+
+ return [super application:application
+ didFinishLaunchingWithOptions:launchOptions];
}
- (void)setupMessagingHandshakeOnChannel:(FlutterBasicMessageChannel*)channel {
diff --git a/dev/integration_tests/channels/lib/main.dart b/dev/integration_tests/channels/lib/main.dart
index 2a1489d..87e99ad 100644
--- a/dev/integration_tests/channels/lib/main.dart
+++ b/dev/integration_tests/channels/lib/main.dart
@@ -173,6 +173,8 @@
() => basicStringMessageToUnknownChannel(),
() => basicJsonMessageToUnknownChannel(),
() => basicStandardMessageToUnknownChannel(),
+ if (Platform.isIOS)
+ () => basicBackgroundStandardEcho(123),
];
Future<TestStepResult>? _result;
int _step = 0;
diff --git a/dev/integration_tests/channels/lib/src/basic_messaging.dart b/dev/integration_tests/channels/lib/src/basic_messaging.dart
index 08392fc..c7a4133 100644
--- a/dev/integration_tests/channels/lib/src/basic_messaging.dart
+++ b/dev/integration_tests/channels/lib/src/basic_messaging.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:isolate';
import 'package:flutter/services.dart';
@@ -78,6 +79,41 @@
'Standard >${toString(message)}<', channel, message);
}
+Future<void> _basicBackgroundStandardEchoMain(List<Object> args) async {
+ final SendPort sendPort = args[2] as SendPort;
+ final Object message = args[1];
+ final String name = 'Background Echo >${toString(message)}<';
+ const String description =
+ 'Uses a platform channel from a background isolate.';
+ try {
+ BackgroundIsolateBinaryMessenger.ensureInitialized(
+ args[0] as RootIsolateToken);
+ const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+ 'std-echo',
+ ExtendedStandardMessageCodec(),
+ );
+ final Object response = await channel.send(message) as Object;
+
+ final TestStatus testStatus = TestStepResult.deepEquals(message, response)
+ ? TestStatus.ok
+ : TestStatus.failed;
+ sendPort.send(TestStepResult(name, description, testStatus));
+ } catch (ex) {
+ sendPort.send(TestStepResult(name, description, TestStatus.failed,
+ error: ex.toString()));
+ }
+}
+
+Future<TestStepResult> basicBackgroundStandardEcho(Object message) async {
+ final ReceivePort receivePort = ReceivePort();
+ Isolate.spawn(_basicBackgroundStandardEchoMain, <Object>[
+ ServicesBinding.instance.rootIsolateToken!,
+ message,
+ receivePort.sendPort,
+ ]);
+ return await receivePort.first as TestStepResult;
+}
+
Future<TestStepResult> basicBinaryMessageToUnknownChannel() async {
const BasicMessageChannel<ByteData?> channel =
BasicMessageChannel<ByteData?>(
diff --git a/dev/integration_tests/channels/lib/src/test_step.dart b/dev/integration_tests/channels/lib/src/test_step.dart
index 6702560..b449099 100644
--- a/dev/integration_tests/channels/lib/src/test_step.dart
+++ b/dev/integration_tests/channels/lib/src/test_step.dart
@@ -90,6 +90,8 @@
],
);
}
+
+ static bool deepEquals(dynamic a, dynamic b) => _deepEquals(a, b);
}
Future<TestStepResult> resultOfHandshake(
diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart
index 5fd8436..964238f 100644
--- a/packages/flutter/lib/src/services/binding.dart
+++ b/packages/flutter/lib/src/services/binding.dart
@@ -82,6 +82,21 @@
BinaryMessenger get defaultBinaryMessenger => _defaultBinaryMessenger;
late final BinaryMessenger _defaultBinaryMessenger;
+ /// A token that represents the root isolate, used for coordinating with background
+ /// isolates.
+ ///
+ /// This property is primarily intended for use with
+ /// [BackgroundIsolateBinaryMessenger.ensureInitialized], which takes a
+ /// [RootIsolateToken] as its argument. The value `null` is returned when
+ /// executed from background isolates.
+ ui.RootIsolateToken? get rootIsolateToken => ui.RootIsolateToken.instance;
+
+ /// Returns `true` if executed on a background (non-root) isolate.
+ ///
+ /// The value `false` will always be returned on web since there is no notion
+ /// of root/background isolates on the web.
+ bool get useBackgroundIsolateBinaryMessenger => !kIsWeb && rootIsolateToken == null;
+
/// The low level buffering and dispatch mechanism for messages sent by
/// plugins on the engine side to their corresponding plugin code on
/// the framework side.
diff --git a/packages/flutter/lib/src/services/platform_channel.dart b/packages/flutter/lib/src/services/platform_channel.dart
index 66f1e7e..f7811c7 100644
--- a/packages/flutter/lib/src/services/platform_channel.dart
+++ b/packages/flutter/lib/src/services/platform_channel.dart
@@ -4,6 +4,8 @@
import 'dart:async';
import 'dart:developer';
+import 'dart:isolate' show ReceivePort;
+import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
@@ -13,6 +15,7 @@
import 'message_codec.dart';
import 'message_codecs.dart';
+export 'dart:ui' show RootIsolateToken;
export 'binary_messenger.dart' show BinaryMessenger;
export 'message_codec.dart' show MessageCodec, MethodCall, MethodCodec;
@@ -123,6 +126,93 @@
_debugLaunchProfilePlatformChannels();
}
+/// A [BinaryMessenger] for use on background (non-root) isolates.
+class BackgroundIsolateBinaryMessenger extends BinaryMessenger {
+ BackgroundIsolateBinaryMessenger._();
+
+ final ReceivePort _receivePort = ReceivePort();
+ final Map<int, Completer<ByteData?>> _completers = <int, Completer<ByteData?>>{};
+ int _messageCount = 0;
+
+ /// The existing instance of this class, if any.
+ ///
+ /// Throws if [ensureInitialized] has not been called at least once.
+ static BinaryMessenger get instance {
+ if (_instance == null) {
+ throw StateError(
+ 'The BackgroundIsolateBinaryMessenger.instance value is invalid '
+ 'until BackgroundIsolateBinaryMessenger.ensureInitialized is '
+ 'executed.');
+ }
+ return _instance!;
+ }
+
+ static BinaryMessenger? _instance;
+
+ /// Ensures that [BackgroundIsolateBinaryMessenger.instance] has been initialized.
+ ///
+ /// The argument should be the value obtained from [ServicesBinding.rootIsolateToken]
+ /// on the root isolate.
+ ///
+ /// This function is idempotent (calling it multiple times is harmless but has no effect).
+ static void ensureInitialized(ui.RootIsolateToken token) {
+ if (_instance == null) {
+ ui.PlatformDispatcher.instance.registerBackgroundIsolate(token);
+ final BackgroundIsolateBinaryMessenger portBinaryMessenger = BackgroundIsolateBinaryMessenger._();
+ _instance = portBinaryMessenger;
+ portBinaryMessenger._receivePort.listen((dynamic message) {
+ try {
+ final List<dynamic> args = message as List<dynamic>;
+ final int identifier = args[0] as int;
+ final Uint8List bytes = args[1] as Uint8List;
+ final ByteData byteData = ByteData.sublistView(bytes);
+ portBinaryMessenger._completers.remove(identifier)!.complete(byteData);
+ } catch (exception, stack) {
+ FlutterError.reportError(FlutterErrorDetails(
+ exception: exception,
+ stack: stack,
+ library: 'services library',
+ context:
+ ErrorDescription('during a platform message response callback'),
+ ));
+ }
+ });
+ }
+ }
+
+ @override
+ Future<void> handlePlatformMessage(String channel, ByteData? data, ui.PlatformMessageResponseCallback? callback) {
+ throw UnimplementedError('handlePlatformMessage is deprecated.');
+ }
+
+ @override
+ Future<ByteData?>? send(String channel, ByteData? message) {
+ final Completer<ByteData?> completer = Completer<ByteData?>();
+ _messageCount += 1;
+ final int messageIdentifier = _messageCount;
+ _completers[messageIdentifier] = completer;
+ ui.PlatformDispatcher.instance.sendPortPlatformMessage(
+ channel,
+ message,
+ messageIdentifier,
+ _receivePort.sendPort,
+ );
+ return completer.future;
+ }
+
+ @override
+ void setMessageHandler(String channel, MessageHandler? handler) {
+ throw UnsupportedError(
+ 'Background isolates do not support setMessageHandler(). Messages from the host platform always go to the root isolate.');
+ }
+}
+
+BinaryMessenger _findBinaryMessenger() {
+ return ServicesBinding.instance.useBackgroundIsolateBinaryMessenger
+ ? BackgroundIsolateBinaryMessenger.instance
+ : ServicesBinding.instance.defaultBinaryMessenger;
+}
+
/// A named channel for communicating with platform plugins using asynchronous
/// message passing.
///
@@ -160,10 +250,14 @@
/// The message codec used by this channel, not null.
final MessageCodec<T> codec;
- /// The messenger which sends the bytes for this channel, not null.
+ /// The messenger which sends the bytes for this channel.
+ ///
+ /// On the root isolate or web, this defaults to the
+ /// [ServicesBinding.defaultBinaryMessenger]. In other contexts the default
+ /// value is a [BackgroundIsolateBinaryMessenger] from
+ /// [BackgroundIsolateBinaryMessenger.ensureInitialized].
BinaryMessenger get binaryMessenger {
- final BinaryMessenger result =
- _binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger;
+ final BinaryMessenger result = _binaryMessenger ?? _findBinaryMessenger();
return !kReleaseMode && debugProfilePlatformChannels
? _debugBinaryMessengers[this] ??= _ProfiledBinaryMessenger(
// ignore: no_runtimetype_tostring
@@ -246,12 +340,14 @@
/// The message codec used by this channel, not null.
final MethodCodec codec;
- /// The messenger used by this channel to send platform messages.
+ /// The messenger which sends the bytes for this channel.
///
- /// The messenger may not be null.
+ /// On the root isolate or web, this defaults to the
+ /// [ServicesBinding.defaultBinaryMessenger]. In other contexts the default
+ /// value is a [BackgroundIsolateBinaryMessenger] from
+ /// [BackgroundIsolateBinaryMessenger.ensureInitialized].
BinaryMessenger get binaryMessenger {
- final BinaryMessenger result =
- _binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger;
+ final BinaryMessenger result = _binaryMessenger ?? _findBinaryMessenger();
return !kReleaseMode && debugProfilePlatformChannels
? _debugBinaryMessengers[this] ??= _ProfiledBinaryMessenger(
// ignore: no_runtimetype_tostring
@@ -600,8 +696,14 @@
/// The message codec used by this channel, not null.
final MethodCodec codec;
- /// The messenger used by this channel to send platform messages, not null.
- BinaryMessenger get binaryMessenger => _binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger;
+ /// The messenger which sends the bytes for this channel.
+ ///
+ /// On the root isolate or web, this defaults to the
+ /// [ServicesBinding.defaultBinaryMessenger]. In other contexts the default
+ /// value is a [BackgroundIsolateBinaryMessenger] from
+ /// [BackgroundIsolateBinaryMessenger.ensureInitialized].
+ BinaryMessenger get binaryMessenger =>
+ _binaryMessenger ?? _findBinaryMessenger();
final BinaryMessenger? _binaryMessenger;
/// Sets up a broadcast stream for receiving events on this channel.
diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart
index 758b32c..cc4bb37 100644
--- a/packages/flutter_test/lib/src/binding.dart
+++ b/packages/flutter_test/lib/src/binding.dart
@@ -104,6 +104,9 @@
TestDefaultBinaryMessenger createBinaryMessenger() {
return TestDefaultBinaryMessenger(super.createBinaryMessenger());
}
+
+ @override
+ bool get useBackgroundIsolateBinaryMessenger => false;
}
/// Base class for bindings used by widgets library tests.