1. Introduction

Mixing mobile native and FLUTTER modules requires communication between the two modules. Flutter provides us with three ways of interaction:

  • BasicMessageChannel: Both iOS and FLUTTER can actively send messages to each other, most simply data.

  • MethodChannel: A two-way channel that uses basically the same method as BasicMessageChannel, but is a bit more advanced, with the ability to customize methodNames.

  • The EventChannel: Event Streams are continuous one-way communication, which can only be actively called by iOS. They are often used to transmit information and status of native devices, such as battery power, remote notification, network status change, mobile phone direction, gravity sensing, positioning position change, etc.

You can click the corresponding link above to see how to use it, which is not described here. The main record of my use in the use of some problems and doubts.

Version 2.

The current flutter and DART versions in use are as follows:

Flutter 2.8.1, channel stable, https://github.com/flutter/flutter.git Framework, revision 77 d935af4d (5 weekes line) 2021-12-16 08:37:33-0800 Engine • Revision 890A5FCA2E Tools • Dart 2.15.1Copy the code

3. Function Description

A very simple test function:

  • The native ViewController jumps to the flutter module (i.e., presents a FlutterViewController).
  • The Flutter module clicks on the upper-left button to trigger a MethodChannel, informing the native call to the Dismiss method to close the flutter module.

The specific code implementation is as follows (only the main code) :

3.1 Native Code

  • To see if the object is destroyed, inherit the official class and rewrite the deinit method.

    class CusFlutterViewController: FlutterViewController {
        override init(engine: FlutterEngine.nibName: String? .bundle nibBundle: Bundle?). {
            super.init(engine: engine, nibName: nibName, bundle: nibBundle)
            self.modalPresentationStyle = .fullScreen
        }
        required init(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
        deinit {
            debugPrint("deinit \ [self)")}}class CusFlutterMethodChannel: FlutterMethodChannel {
        
        deinit {
            debugPrint("deinit \ [self)")}}Copy the code

    When the FlutterViewController and FlutterMethodChannel are destroyed, the console prints the corresponding log.

  • Create the engine, create the flutterViewController, methodChannel, and implement the callback.

    let flutterEngine = FlutterEngine(name: "my flutter engine")
    flutterEngine.run()
    GeneratedPluginRegistrant.register(with: flutterEngine);
    
    .Omit irrelevant codeself.flutterVC = CusFlutterViewController.init(engine: flutterEngine, nibName: nil, bundle: nil)
    
    self.channel = CusFlutterMethodChannel.init(name: "channel", binaryMessenger: self.flutterVC!.binaryMessenger)
    
    self.channel?.setMethodCallHandler { [weak self] handler, block in
        let method = handler.method
        switch method {
        case "exit_flutter_module":
            self?.dismiss(animated: true, completion: {
                self?.channel?.setMethodCallHandler(nil)
            		self?.channel = nil
            		self?.flutterVC = nil
            })
        default: ()}}self.present(self.flutterVC!, animated: true, completion: nil)
    Copy the code

3.2 Code of the flutter side

When the button is clicked, the native Dismiss flutter module is notified

const MethodChannel flutterMethodChannel = MethodChannel('channel'); . Omit irrelevant code TextButton(child:const Text("back", style: TextStyle(color: Colors.red),),
  onPressed: () async {
    var result = await flutterMethodChannel.invokeMapMethod("exit_flutter_module")?? {}; debugPrint("$result"); },)Copy the code

4. Identify problems

When I run the above code and return the native page from the Flutter module, I find that the console only outputs the Deinit log of the flutterViewController, but the Deinit method of the flutterMethodChannel is not triggered. A new channel object is created each time a flutter page is re-entered, but the destructor deinit is not triggered on return. This means that when I entered and exited the flutter page N times, N channel objects were created in memory and none of them were released.

I don’t want to talk about my specific exploration process here, but I will talk about my findings directly. To create a channel from above:

self.channel = CusFlutterMethodChannel.init(name: "channel", binaryMessenger: self.flutterVC!.binaryMessenger)
Copy the code

Modified to:

self.channel = CusFlutterMethodChannel.init(name: "channel", binaryMessenger: self.flutterVC!.binaryMessenger, codec: FlutterStandardMethodCodec.sharedInstance())
Copy the code

The above problem is solved. When returning from the flutter page to the native interface, the destructor of the channel fires and the console log has output.

5. Confused

There is little difference between the two constructors:

/**
 * Creates a `FlutterMethodChannel` with the specified name and binary messenger.
 *
 * The channel name logically identifies the channel; identically named channels
 * interfere with each other's communication.
 *
 * The binary messenger is a facility for sending raw, binary messages to the
 * Flutter side. This protocol is implemented by `FlutterEngine` and `FlutterViewController`.
 *
 * The channel uses `FlutterStandardMethodCodec` to encode and decode method calls
 * and result envelopes.
 *
 * @param name The channel name.
 * @param messenger The binary messenger.
 */
+ (instancetype)methodChannelWithName:(NSString*)name
                      binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;

/**
 * Creates a `FlutterMethodChannel` with the specified name, binary messenger, and
 * method codec.
 *
 * The channel name logically identifies the channel; identically named channels
 * interfere with each other's communication.
 *
 * The binary messenger is a facility for sending raw, binary messages to the
 * Flutter side. This protocol is implemented by `FlutterEngine` and `FlutterViewController`.
 *
 * @param name The channel name.
 * @param messenger The binary messenger.
 * @param codec The method codec.
 */
+ (instancetype)methodChannelWithName:(NSString*)name
                      binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger
                                codec:(NSObject<FlutterMethodCodec>*)codec;
Copy the code

The first method is the second method the codec is FlutterStandardMethodCodec by default. As to why the channel constructed by the first method cannot be released, only the source code can be solved. If you have good ideas and insights, please feel free to comment!