From 0dd3b2a5dafbea74e1037e76a359477bee9ad969 Mon Sep 17 00:00:00 2001 From: littleGnAl Date: Wed, 9 Aug 2023 17:02:03 +0800 Subject: [PATCH] fix: [android] Bound the IrisMethodChannel lifecycle with the FlutterEngine --- .../IrisMethodChannelPlugin.java | 8 +-- lib/src/iris_method_channel.dart | 18 +++++- test/iris_method_channel_test.dart | 61 ++++++++++++++++++- 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/com/agora/iris_method_channel/IrisMethodChannelPlugin.java b/android/src/main/java/com/agora/iris_method_channel/IrisMethodChannelPlugin.java index ce291f6..165c453 100644 --- a/android/src/main/java/com/agora/iris_method_channel/IrisMethodChannelPlugin.java +++ b/android/src/main/java/com/agora/iris_method_channel/IrisMethodChannelPlugin.java @@ -24,15 +24,13 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBindin @Override public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { - if (call.method.equals("getPlatformVersion")) { - result.success("Android " + android.os.Build.VERSION.RELEASE); - } else { - result.notImplemented(); - } + result.notImplemented(); } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + // Notify the dart side to call the `IrisMethodChannel.dispose` to clear the resources. + channel.invokeMethod("onDetachedFromEngine_fromPlatform", null); channel.setMethodCallHandler(null); } } diff --git a/lib/src/iris_method_channel.dart b/lib/src/iris_method_channel.dart index 17cc495..26d7d53 100644 --- a/lib/src/iris_method_channel.dart +++ b/lib/src/iris_method_channel.dart @@ -7,6 +7,7 @@ import 'package:async/async.dart'; import 'package:ffi/ffi.dart'; import 'package:flutter/foundation.dart' show SynchronousFuture, VoidCallback, visibleForTesting; +import 'package:flutter/services.dart'; import 'package:iris_method_channel/src/bindings/native_iris_api_common_bindings.dart' as iris; import 'package:iris_method_channel/src/iris_event.dart'; @@ -139,7 +140,7 @@ class _Messenger implements DisposableObject { @override Future dispose() async { - if (!_isDisposed) { + if (_isDisposed) { return; } _isDisposed = true; @@ -309,6 +310,8 @@ class IrisMethodChannel { late Isolate workerIsolate; late _HotRestartFinalizer _hotRestartFinalizer; + final MethodChannel _channel = const MethodChannel('iris_method_channel'); + static Future _execute(_InitilizationArgs args) async { final SendPort mainApiCallSendPort = args.apiCallPortSendPort; final SendPort mainEventSendPort = args.eventPortSendPort; @@ -401,6 +404,17 @@ class IrisMethodChannel { Isolate.exit(onExitSendPort, 0); } + void _setuponDetachedFromEngineListener() { + _channel.setMethodCallHandler((call) async { + if (call.method == 'onDetachedFromEngine_fromPlatform') { + dispose(); + return true; + } + + return false; + }); + } + Future initilize(List args) async { if (_initilized) { return null; @@ -409,6 +423,8 @@ class IrisMethodChannel { final apiCallPort = ReceivePort(); final eventPort = ReceivePort(); + _setuponDetachedFromEngineListener(); + _hotRestartFinalizer = _HotRestartFinalizer(_nativeBindingsProvider); workerIsolate = await Isolate.spawn( diff --git a/test/iris_method_channel_test.dart b/test/iris_method_channel_test.dart index b9bc951..fb49283 100644 --- a/test/iris_method_channel_test.dart +++ b/test/iris_method_channel_test.dart @@ -4,6 +4,8 @@ import 'dart:isolate'; import 'dart:typed_data'; import 'package:ffi/ffi.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:iris_method_channel/src/bindings/native_iris_api_common_bindings.dart'; import 'package:iris_method_channel/src/bindings/native_iris_event_bindings.dart' as iris_event; @@ -11,7 +13,6 @@ import 'package:iris_method_channel/src/iris_event.dart'; import 'package:iris_method_channel/src/iris_method_channel.dart'; import 'package:iris_method_channel/src/native_bindings_delegate.dart'; import 'package:iris_method_channel/src/scoped_objects.dart'; -import 'package:test/test.dart'; class _ApiParam { _ApiParam(this.event, this.data); @@ -227,6 +228,9 @@ class _TestEventLoopEventHandler extends EventLoopEventHandler { } void main() { + final TestWidgetsFlutterBinding binding = + TestWidgetsFlutterBinding.ensureInitialized(); + late _FakeNativeBindingDelegateMessenger messenger; late NativeBindingsProvider nativeBindingsProvider; late IrisMethodChannel irisMethodChannel; @@ -416,6 +420,61 @@ void main() { await irisMethodChannel.dispose(); }); + test('disposed', () async { + await irisMethodChannel.initilize([]); + await irisMethodChannel.dispose(); + // Wait for `dispose` completed. + await Future.delayed(const Duration(milliseconds: 500)); + final callRecord1 = messenger.callApiRecords + .where((e) => e.methodCall.funcName == 'destroyNativeApiEngine'); + expect(callRecord1.length, 1); + + final callRecord2 = messenger.callApiRecords + .where((e) => e.methodCall.funcName == 'destroyIrisEventHandler'); + expect(callRecord2.length, 1); + }); + + test('disposed multiple times', () async { + await irisMethodChannel.initilize([]); + await irisMethodChannel.dispose(); + await irisMethodChannel.dispose(); + // Wait for `dispose` completed. + await Future.delayed(const Duration(milliseconds: 500)); + final callRecord1 = messenger.callApiRecords + .where((e) => e.methodCall.funcName == 'destroyNativeApiEngine'); + expect(callRecord1.length, 1); + + final callRecord2 = messenger.callApiRecords + .where((e) => e.methodCall.funcName == 'destroyIrisEventHandler'); + expect(callRecord2.length, 1); + }); + + test('disposed after receive onDetachedFromEngine_fromPlatform', () async { + await irisMethodChannel.initilize([]); + + const StandardMethodCodec codec = StandardMethodCodec(); + + final ByteData data = codec.encodeMethodCall(const MethodCall( + 'onDetachedFromEngine_fromPlatform', + )); + await binding.defaultBinaryMessenger.handlePlatformMessage( + 'iris_method_channel', + data, + (ByteData? data) {}, + ); + + // Wait for the `iris_method_channel` method channel call completed. + await Future.delayed(const Duration(milliseconds: 1000)); + + final callRecord1 = messenger.callApiRecords + .where((e) => e.methodCall.funcName == 'destroyNativeApiEngine'); + expect(callRecord1.length, 1); + + final callRecord2 = messenger.callApiRecords + .where((e) => e.methodCall.funcName == 'destroyIrisEventHandler'); + expect(callRecord2.length, 1); + }); + test('invokeMethod after disposed', () async { await irisMethodChannel.initilize([]); await irisMethodChannel.dispose();