Unverified Commit 0d19d46b authored by gaaclarke's avatar gaaclarke Committed by GitHub

Started handling messages from background isolates. (#109005)

Started handling messages from background isolates.
parent d671a834
...@@ -18,6 +18,7 @@ found in the LICENSE file. --> ...@@ -18,6 +18,7 @@ found in the LICENSE file. -->
Application and put your custom class here. --> Application and put your custom class here. -->
<application android:name="${applicationName}" android:label="channels" android:icon="@mipmap/ic_launcher"> <application android:name="${applicationName}" android:label="channels" android:icon="@mipmap/ic_launcher">
<activity android:name=".MainActivity" <activity android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@android:style/Theme.Black.NoTitleBar" android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
......
...@@ -114,7 +114,18 @@ const UInt8 PAIR = 129; ...@@ -114,7 +114,18 @@ const UInt8 PAIR = 129;
[FlutterMethodChannel methodChannelWithName:@"std-method" [FlutterMethodChannel methodChannelWithName:@"std-method"
binaryMessenger:flutterController binaryMessenger:flutterController
codec:[FlutterStandardMethodCodec codecWithReaderWriter:extendedReaderWriter]]]; 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 { - (void)setupMessagingHandshakeOnChannel:(FlutterBasicMessageChannel*)channel {
......
...@@ -173,6 +173,8 @@ class _TestAppState extends State<TestApp> { ...@@ -173,6 +173,8 @@ class _TestAppState extends State<TestApp> {
() => basicStringMessageToUnknownChannel(), () => basicStringMessageToUnknownChannel(),
() => basicJsonMessageToUnknownChannel(), () => basicJsonMessageToUnknownChannel(),
() => basicStandardMessageToUnknownChannel(), () => basicStandardMessageToUnknownChannel(),
if (Platform.isIOS)
() => basicBackgroundStandardEcho(123),
]; ];
Future<TestStepResult>? _result; Future<TestStepResult>? _result;
int _step = 0; int _step = 0;
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:isolate';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
...@@ -78,6 +79,41 @@ Future<TestStepResult> basicStandardHandshake(dynamic message) async { ...@@ -78,6 +79,41 @@ Future<TestStepResult> basicStandardHandshake(dynamic message) async {
'Standard >${toString(message)}<', channel, message); '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 { Future<TestStepResult> basicBinaryMessageToUnknownChannel() async {
const BasicMessageChannel<ByteData?> channel = const BasicMessageChannel<ByteData?> channel =
BasicMessageChannel<ByteData?>( BasicMessageChannel<ByteData?>(
......
...@@ -90,6 +90,8 @@ class TestStepResult { ...@@ -90,6 +90,8 @@ class TestStepResult {
], ],
); );
} }
static bool deepEquals(dynamic a, dynamic b) => _deepEquals(a, b);
} }
Future<TestStepResult> resultOfHandshake( Future<TestStepResult> resultOfHandshake(
......
...@@ -82,6 +82,21 @@ mixin ServicesBinding on BindingBase, SchedulerBinding { ...@@ -82,6 +82,21 @@ mixin ServicesBinding on BindingBase, SchedulerBinding {
BinaryMessenger get defaultBinaryMessenger => _defaultBinaryMessenger; BinaryMessenger get defaultBinaryMessenger => _defaultBinaryMessenger;
late final BinaryMessenger _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 /// The low level buffering and dispatch mechanism for messages sent by
/// plugins on the engine side to their corresponding plugin code on /// plugins on the engine side to their corresponding plugin code on
/// the framework side. /// the framework side.
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer'; import 'dart:developer';
import 'dart:isolate' show ReceivePort;
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -13,6 +15,7 @@ import 'debug.dart' show debugProfilePlatformChannels; ...@@ -13,6 +15,7 @@ import 'debug.dart' show debugProfilePlatformChannels;
import 'message_codec.dart'; import 'message_codec.dart';
import 'message_codecs.dart'; import 'message_codecs.dart';
export 'dart:ui' show RootIsolateToken;
export 'binary_messenger.dart' show BinaryMessenger; export 'binary_messenger.dart' show BinaryMessenger;
export 'message_codec.dart' show MessageCodec, MethodCall, MethodCodec; export 'message_codec.dart' show MessageCodec, MethodCall, MethodCodec;
...@@ -123,6 +126,93 @@ void _debugRecordDownStream(String channelTypeName, String name, ...@@ -123,6 +126,93 @@ void _debugRecordDownStream(String channelTypeName, String name,
_debugLaunchProfilePlatformChannels(); _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 /// A named channel for communicating with platform plugins using asynchronous
/// message passing. /// message passing.
/// ///
...@@ -160,10 +250,14 @@ class BasicMessageChannel<T> { ...@@ -160,10 +250,14 @@ class BasicMessageChannel<T> {
/// The message codec used by this channel, not null. /// The message codec used by this channel, not null.
final MessageCodec<T> codec; 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 { BinaryMessenger get binaryMessenger {
final BinaryMessenger result = final BinaryMessenger result = _binaryMessenger ?? _findBinaryMessenger();
_binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger;
return !kReleaseMode && debugProfilePlatformChannels return !kReleaseMode && debugProfilePlatformChannels
? _debugBinaryMessengers[this] ??= _ProfiledBinaryMessenger( ? _debugBinaryMessengers[this] ??= _ProfiledBinaryMessenger(
// ignore: no_runtimetype_tostring // ignore: no_runtimetype_tostring
...@@ -246,12 +340,14 @@ class MethodChannel { ...@@ -246,12 +340,14 @@ class MethodChannel {
/// The message codec used by this channel, not null. /// The message codec used by this channel, not null.
final MethodCodec codec; 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 { BinaryMessenger get binaryMessenger {
final BinaryMessenger result = final BinaryMessenger result = _binaryMessenger ?? _findBinaryMessenger();
_binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger;
return !kReleaseMode && debugProfilePlatformChannels return !kReleaseMode && debugProfilePlatformChannels
? _debugBinaryMessengers[this] ??= _ProfiledBinaryMessenger( ? _debugBinaryMessengers[this] ??= _ProfiledBinaryMessenger(
// ignore: no_runtimetype_tostring // ignore: no_runtimetype_tostring
...@@ -600,8 +696,14 @@ class EventChannel { ...@@ -600,8 +696,14 @@ class EventChannel {
/// The message codec used by this channel, not null. /// The message codec used by this channel, not null.
final MethodCodec codec; final MethodCodec codec;
/// The messenger used by this channel to send platform messages, not null. /// The messenger which sends the bytes for this channel.
BinaryMessenger get binaryMessenger => _binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger; ///
/// 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; final BinaryMessenger? _binaryMessenger;
/// Sets up a broadcast stream for receiving events on this channel. /// Sets up a broadcast stream for receiving events on this channel.
......
...@@ -104,6 +104,9 @@ mixin TestDefaultBinaryMessengerBinding on BindingBase, ServicesBinding { ...@@ -104,6 +104,9 @@ mixin TestDefaultBinaryMessengerBinding on BindingBase, ServicesBinding {
TestDefaultBinaryMessenger createBinaryMessenger() { TestDefaultBinaryMessenger createBinaryMessenger() {
return TestDefaultBinaryMessenger(super.createBinaryMessenger()); return TestDefaultBinaryMessenger(super.createBinaryMessenger());
} }
@override
bool get useBackgroundIsolateBinaryMessenger => false;
} }
/// Base class for bindings used by widgets library tests. /// Base class for bindings used by widgets library tests.
......
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