Started handling messages from background isolates. (#109005)

Started handling messages from background isolates.
This commit is contained in:
gaaclarke 2022-09-09 15:14:21 -07:00 committed by GitHub
parent d671a834f3
commit 0d19d46b44
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 182 additions and 10 deletions

View file

@ -18,6 +18,7 @@ found in the LICENSE file. -->
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"

View file

@ -114,7 +114,18 @@ const UInt8 PAIR = 129;
[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 {

View file

@ -173,6 +173,8 @@ class _TestAppState extends State<TestApp> {
() => basicStringMessageToUnknownChannel(),
() => basicJsonMessageToUnknownChannel(),
() => basicStandardMessageToUnknownChannel(),
if (Platform.isIOS)
() => basicBackgroundStandardEcho(123),
];
Future<TestStepResult>? _result;
int _step = 0;

View file

@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:isolate';
import 'package:flutter/services.dart';
@ -78,6 +79,41 @@ Future<TestStepResult> basicStandardHandshake(dynamic message) async {
'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?>(

View file

@ -90,6 +90,8 @@ class TestStepResult {
],
);
}
static bool deepEquals(dynamic a, dynamic b) => _deepEquals(a, b);
}
Future<TestStepResult> resultOfHandshake(

View file

@ -82,6 +82,21 @@ mixin ServicesBinding on BindingBase, SchedulerBinding {
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.

View file

@ -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 'debug.dart' show debugProfilePlatformChannels;
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 @@ void _debugRecordDownStream(String channelTypeName, String name,
_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 @@ class BasicMessageChannel<T> {
/// 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 @@ class MethodChannel {
/// 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 @@ class EventChannel {
/// 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.

View file

@ -104,6 +104,9 @@ mixin TestDefaultBinaryMessengerBinding on BindingBase, ServicesBinding {
TestDefaultBinaryMessenger createBinaryMessenger() {
return TestDefaultBinaryMessenger(super.createBinaryMessenger());
}
@override
bool get useBackgroundIsolateBinaryMessenger => false;
}
/// Base class for bindings used by widgets library tests.