feat(linux): Get the isolate implementation somewhat working
This commit is contained in:
		
							parent
							
								
									cefe90b93a
								
							
						
					
					
						commit
						01374a8eb9
					
				
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -20,10 +20,10 @@ class MoxxyBackgroundServiceApi { | ||||
| 
 | ||||
|   Future<String> getExtraData() async { | ||||
|     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.getExtraData', codec, | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.getExtraData', | ||||
|         codec, | ||||
|         binaryMessenger: _binaryMessenger); | ||||
|     final List<Object?>? replyList = | ||||
|         await channel.send(null) as List<Object?>?; | ||||
|     final List<Object?>? replyList = await channel.send(null) as List<Object?>?; | ||||
|     if (replyList == null) { | ||||
|       throw PlatformException( | ||||
|         code: 'channel-error', | ||||
| @ -47,7 +47,8 @@ class MoxxyBackgroundServiceApi { | ||||
| 
 | ||||
|   Future<void> setNotificationBody(String arg_body) async { | ||||
|     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.setNotificationBody', codec, | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.setNotificationBody', | ||||
|         codec, | ||||
|         binaryMessenger: _binaryMessenger); | ||||
|     final List<Object?>? replyList = | ||||
|         await channel.send(<Object?>[arg_body]) as List<Object?>?; | ||||
| @ -69,7 +70,8 @@ class MoxxyBackgroundServiceApi { | ||||
| 
 | ||||
|   Future<void> sendData(String arg_data) async { | ||||
|     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.sendData', codec, | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.sendData', | ||||
|         codec, | ||||
|         binaryMessenger: _binaryMessenger); | ||||
|     final List<Object?>? replyList = | ||||
|         await channel.send(<Object?>[arg_data]) as List<Object?>?; | ||||
| @ -93,8 +95,7 @@ class MoxxyBackgroundServiceApi { | ||||
|     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.stop', codec, | ||||
|         binaryMessenger: _binaryMessenger); | ||||
|     final List<Object?>? replyList = | ||||
|         await channel.send(null) as List<Object?>?; | ||||
|     final List<Object?>? replyList = await channel.send(null) as List<Object?>?; | ||||
|     if (replyList == null) { | ||||
|       throw PlatformException( | ||||
|         code: 'channel-error', | ||||
|  | ||||
							
								
								
									
										53
									
								
								lib/src/service/background/isolate.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								lib/src/service/background/isolate.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| import 'dart:convert'; | ||||
| import 'dart:isolate'; | ||||
| import 'dart:ui'; | ||||
| import 'package:logging/logging.dart'; | ||||
| import 'package:moxlib/moxlib.dart'; | ||||
| import 'package:moxxy_native/src/service/background/base.dart'; | ||||
| import 'package:moxxy_native/src/service/config.dart'; | ||||
| import 'package:moxxy_native/src/service/datasender/types.dart'; | ||||
| import 'package:uuid/uuid.dart'; | ||||
| 
 | ||||
| class IsolateBackgroundService extends BackgroundService { | ||||
|   IsolateBackgroundService(this._sendPort); | ||||
|   final SendPort _sendPort; | ||||
|   final ReceivePort receivePort = ReceivePort(); | ||||
| 
 | ||||
|   /// A logger. | ||||
|   final Logger _log = Logger('IsolateBackgroundService'); | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> send(BackgroundEvent event, {String? id}) async { | ||||
|     final data = DataWrapper( | ||||
|       id ?? const Uuid().v4(), | ||||
|       event, | ||||
|     ); | ||||
| 
 | ||||
|     _sendPort.send(jsonEncode(data.toJson())); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> init( | ||||
|     ServiceConfig config, | ||||
|   ) async { | ||||
|     // Ensure that the Dart executor is ready to use plugins | ||||
|     // NOTE: We're not allowed to use this here. Maybe reusing the RootIsolateToken | ||||
|     //       (See IsolateForegroundService) helps? | ||||
|     // WidgetsFlutterBinding.ensureInitialized(); | ||||
|     DartPluginRegistrant.ensureInitialized(); | ||||
| 
 | ||||
|     // Register the channel for Foreground -> Service communication | ||||
|     receivePort.listen((data) async { | ||||
|       // TODO(Unknown): Maybe do something smarter like pigeon and use Lists instead of Maps | ||||
|       await config | ||||
|           .handleData(jsonDecode(data! as String) as Map<String, dynamic>); | ||||
|     }); | ||||
| 
 | ||||
|     // Start execution | ||||
|     _log.finest('Setup complete. Calling main entrypoint...'); | ||||
|     await config.entrypoint(config.initialLocale); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void setNotificationBody(String body) {} | ||||
| } | ||||
							
								
								
									
										15
									
								
								lib/src/service/datasender/isolate.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								lib/src/service/datasender/isolate.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| import 'dart:convert'; | ||||
| import 'dart:isolate'; | ||||
| import 'package:moxlib/moxlib.dart'; | ||||
| import 'package:moxxy_native/src/service/datasender/types.dart'; | ||||
| 
 | ||||
| class IsolateForegroundServiceDataSender | ||||
|     extends AwaitableDataSender<BackgroundCommand, BackgroundEvent> { | ||||
|   IsolateForegroundServiceDataSender(this._port); | ||||
|   final SendPort _port; | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> sendDataImpl(DataWrapper<JsonImplementation> data) async { | ||||
|     _port.send(jsonEncode(data.toJson())); | ||||
|   } | ||||
| } | ||||
| @ -1,8 +1,4 @@ | ||||
| import 'dart:io'; | ||||
| import 'package:moxlib/moxlib.dart'; | ||||
| import 'package:moxxy_native/pigeon/service.g.dart'; | ||||
| import 'package:moxxy_native/src/service/datasender/pigeon.dart'; | ||||
| import 'package:moxxy_native/src/service/exceptions.dart'; | ||||
| 
 | ||||
| typedef ForegroundServiceDataSender | ||||
|     = AwaitableDataSender<BackgroundCommand, BackgroundEvent>; | ||||
| @ -10,11 +6,3 @@ typedef ForegroundServiceDataSender | ||||
| abstract class BackgroundCommand implements JsonImplementation {} | ||||
| 
 | ||||
| abstract class BackgroundEvent implements JsonImplementation {} | ||||
| 
 | ||||
| ForegroundServiceDataSender getForegroundDataSender(MoxxyServiceApi api) { | ||||
|   if (Platform.isAndroid) { | ||||
|     return PigeonForegroundServiceDataSender(api); | ||||
|   } else { | ||||
|     throw UnsupportedPlatformException(); | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										28
									
								
								lib/src/service/entrypoints/base.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								lib/src/service/entrypoints/base.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| import 'dart:io'; | ||||
| import 'package:moxxy_native/src/service/config.dart'; | ||||
| import 'package:moxxy_native/src/service/entrypoints/isolate.dart'; | ||||
| import 'package:moxxy_native/src/service/entrypoints/pigeon.dart'; | ||||
| import 'package:moxxy_native/src/service/exceptions.dart'; | ||||
| 
 | ||||
| typedef PlatformEntrypointCallback = Future<void> Function(dynamic); | ||||
| 
 | ||||
| ServiceConfig getServiceConfig( | ||||
|   HandleEventCallback srvHandleData, | ||||
|   HandleEventCallback uiHandleData, | ||||
|   String initialLocale, | ||||
| ) { | ||||
|   PlatformEntrypointCallback entrypoint; | ||||
|   if (Platform.isAndroid) { | ||||
|     entrypoint = pigeonEntrypoint; | ||||
|   } else if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) { | ||||
|     entrypoint = isolateEntrypoint; | ||||
|   } else { | ||||
|     throw UnsupportedPlatformException(); | ||||
|   } | ||||
| 
 | ||||
|   return ServiceConfig( | ||||
|     entrypoint, | ||||
|     srvHandleData, | ||||
|     initialLocale, | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										30
									
								
								lib/src/service/entrypoints/isolate.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								lib/src/service/entrypoints/isolate.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| import 'dart:isolate'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:get_it/get_it.dart'; | ||||
| import 'package:moxxy_native/src/service/background/base.dart'; | ||||
| import 'package:moxxy_native/src/service/background/isolate.dart'; | ||||
| import 'package:moxxy_native/src/service/config.dart'; | ||||
| 
 | ||||
| @pragma('vm:entry-point') | ||||
| Future<void> isolateEntrypoint(dynamic parameters) async { | ||||
|   parameters as List<dynamic>; | ||||
| 
 | ||||
|   final sendPort = parameters[0] as SendPort; | ||||
|   final config = ServiceConfig.fromString(parameters[1] as String); | ||||
| 
 | ||||
|   // This allows us to use the root isolate's method channels. | ||||
|   // See https://medium.com/flutter/introducing-background-isolate-channels-7a299609cad8 | ||||
|   BackgroundIsolateBinaryMessenger.ensureInitialized( | ||||
|     parameters[2] as RootIsolateToken, | ||||
|   ); | ||||
| 
 | ||||
|   // Set up the background service | ||||
|   final srv = IsolateBackgroundService(sendPort); | ||||
|   GetIt.I.registerSingleton<BackgroundService>(srv); | ||||
| 
 | ||||
|   // Reply back with the new send port | ||||
|   sendPort.send(srv.receivePort.sendPort); | ||||
| 
 | ||||
|   // Run the entrypoint | ||||
|   await srv.init(config); | ||||
| } | ||||
| @ -8,7 +8,7 @@ import 'package:moxxy_native/src/service/config.dart'; | ||||
| /// An entrypoint that should be used when the service runs | ||||
| /// in a new Flutter Engine. | ||||
| @pragma('vm:entry-point') | ||||
| Future<void> pigeonEntrypoint() async { | ||||
| Future<void> pigeonEntrypoint(dynamic _) async { | ||||
|   // ignore: avoid_print | ||||
|   print('androidEntrypoint: Called on new FlutterEngine'); | ||||
| 
 | ||||
|  | ||||
| @ -3,6 +3,7 @@ import 'package:moxlib/moxlib.dart'; | ||||
| import 'package:moxxy_native/src/service/config.dart'; | ||||
| import 'package:moxxy_native/src/service/datasender/types.dart'; | ||||
| import 'package:moxxy_native/src/service/exceptions.dart'; | ||||
| import 'package:moxxy_native/src/service/foreground/isolate.dart'; | ||||
| import 'package:moxxy_native/src/service/foreground/pigeon.dart'; | ||||
| 
 | ||||
| /// Wrapper API that is only available to the UI isolate. | ||||
| @ -40,6 +41,8 @@ ForegroundService getForegroundService() { | ||||
|   if (_service == null) { | ||||
|     if (Platform.isAndroid) { | ||||
|       _service = PigeonForegroundService(); | ||||
|     } else if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) { | ||||
|       _service = IsolateForegroundService(); | ||||
|     } else { | ||||
|       throw UnsupportedPlatformException(); | ||||
|     } | ||||
|  | ||||
							
								
								
									
										98
									
								
								lib/src/service/foreground/isolate.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								lib/src/service/foreground/isolate.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,98 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:convert'; | ||||
| import 'dart:isolate'; | ||||
| import 'dart:ui'; | ||||
| import 'package:logging/logging.dart'; | ||||
| import 'package:moxxy_native/src/service/config.dart'; | ||||
| import 'package:moxxy_native/src/service/datasender/isolate.dart'; | ||||
| import 'package:moxxy_native/src/service/datasender/types.dart'; | ||||
| import 'package:moxxy_native/src/service/entrypoints/isolate.dart'; | ||||
| import 'package:moxxy_native/src/service/foreground/base.dart'; | ||||
| 
 | ||||
| class IsolateForegroundService extends ForegroundService { | ||||
|   /// The port on which we receive data from the isolate. | ||||
|   final ReceivePort _receivePort = ReceivePort(); | ||||
| 
 | ||||
|   /// The port on which we send data to the isolate. | ||||
|   late final SendPort _sendPort; | ||||
| 
 | ||||
|   /// A completer that indicates when _sendPort has been set. | ||||
|   /// For more notes, see the comment in [start]. | ||||
|   Completer<void>? _sendPortCompleter = Completer<void>(); | ||||
| 
 | ||||
|   /// The data sender backing this class. | ||||
|   late final IsolateForegroundServiceDataSender _dataSender; | ||||
| 
 | ||||
|   /// A logger. | ||||
|   final Logger _log = Logger('IsolateForegroundService'); | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> attach( | ||||
|     HandleEventCallback handleData, | ||||
|   ) async { | ||||
|     _receivePort.asBroadcastStream().listen((data) async { | ||||
|       if (data is SendPort) { | ||||
|         // Set the send port. | ||||
|         _sendPort = data; | ||||
| 
 | ||||
|         // Resolve the waiting future. | ||||
|         assert( | ||||
|           _sendPortCompleter != null, | ||||
|           '_sendPort should only be received once!', | ||||
|         ); | ||||
|         _sendPortCompleter?.complete(); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       await handleData( | ||||
|         jsonDecode(data! as String) as Map<String, dynamic>, | ||||
|       ); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> start( | ||||
|     ServiceConfig config, | ||||
|     HandleEventCallback uiHandleData, | ||||
|   ) async { | ||||
|     // Listen for events | ||||
|     await attach(uiHandleData); | ||||
| 
 | ||||
|     await Isolate.spawn( | ||||
|       isolateEntrypoint, | ||||
|       [ | ||||
|         _receivePort.sendPort, | ||||
|         config.toString(), | ||||
|         RootIsolateToken.instance!, | ||||
|       ], | ||||
|     ); | ||||
| 
 | ||||
|     // Wait for [_sendPort] to get set. | ||||
|     // The issue is that [_receivePort] provides a stream that only one listener can listen to. | ||||
|     // This means that we cannot do `await _receivePort.first`. To work around this, we just cram | ||||
|     // an approximation of `_receivePort.first` into the actual listener. | ||||
|     await _sendPortCompleter!.future; | ||||
|     _sendPortCompleter = null; | ||||
| 
 | ||||
|     // Create the data sender | ||||
|     _dataSender = IsolateForegroundServiceDataSender(_sendPort); | ||||
|     _log.finest('Background service started...'); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<bool> isRunning() async => false; | ||||
| 
 | ||||
|   @override | ||||
|   ForegroundServiceDataSender getDataSender() => _dataSender; | ||||
| 
 | ||||
|   @override | ||||
|   Future<BackgroundEvent?> send( | ||||
|     BackgroundCommand command, { | ||||
|     bool awaitable = true, | ||||
|   }) { | ||||
|     return _dataSender.sendData( | ||||
|       command, | ||||
|       awaitable: awaitable, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user