You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

Flutter插件在热重启场景下是否存在析构函数?如何在热重启前释放占用资源?

解决Flutter插件热重启时MIDI端口未关闭导致崩溃的问题

我之前也碰到过类似的Flutter插件热重启资源泄漏问题,尤其是涉及硬件端口这类独占性资源时,确实挺棘手的!结合你的情况,以下几个方案应该能帮到你:

方案1:Android原生层监听Isolate生命周期(最可靠)

热重启时,Flutter Engine不会被销毁,但Dart侧的Isolate会重启。我们可以在原生插件中监听Isolate的停止事件,在旧Isolate销毁时关闭MIDI端口:

  1. 在你的Android插件类(继承FlutterPlugin)中,实现DartExecutor.IsolateRunListener接口:
class YourMidiPlugin : FlutterPlugin, DartExecutor.IsolateRunListener {
    private var midiPort: MidiPort? = null

    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        // 注册Isolate监听
        binding.dartExecutor.addIsolateRunListener(this)
        // 其他初始化逻辑...
    }

    override fun onIsolateStarted() {
        // 新Isolate启动时,可重新初始化端口(如果需要)
    }

    override fun onIsolateStopped() {
        // 旧Isolate停止(热重启触发),关闭MIDI端口
        midiPort?.close()
        midiPort = null
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        // 移除监听,防止内存泄漏
        binding.dartExecutor.removeIsolateRunListener(this)
        // 常规销毁逻辑
        midiPort?.close()
        midiPort = null
    }
}

这个方法能精准捕获热重启时的Isolate销毁事件,确保资源被及时释放,是目前针对Android端最有效的方案。

方案2:Dart层主动通知原生释放资源

虽然热重启时Dart侧会重置,但我们可以在Dart层利用WidgetsBindingObserver或者监听VM的退出信号,主动调用原生方法关闭端口:

  1. 在你的插件Dart类中,添加生命周期监听:
class YourMidiPlugin {
  static const MethodChannel _channel = MethodChannel('your_midi_channel');
  static bool _isInitialized = false;

  static Future<void> initialize() async {
    if (_isInitialized) {
      // 如果已经初始化过,先通知原生释放旧资源
      await _channel.invokeMethod('closeMidiPort');
    }
    // 初始化新的端口
    await _channel.invokeMethod('openMidiPort');
    _isInitialized = true;
    
    // 监听App生命周期,比如后台切换时也释放资源(可选)
    WidgetsBinding.instance.addObserver(_LifecycleObserver());
  }
}

class _LifecycleObserver extends WidgetsBindingObserver {
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.detached) {
      YourMidiPlugin._channel.invokeMethod('closeMidiPort');
    }
  }
}
  1. 原生层对应实现closeMidiPort方法,关闭端口即可。

这个方案的好处是跨平台兼容,但依赖Dart层的初始化逻辑,需要确保每次热重启后初始化前先清理旧资源。

方案3:iOS端的补充方案(如果需要支持iOS)

如果你的插件需要支持iOS,热重启时iOS的FlutterEngine同样不会销毁,但可以通过FlutterMethodChannelsetMethodCallHandler的变化来判断:

class YourMidiPlugin: NSObject, FlutterPlugin {
    static var midiPort: MidiPort?
    
    static func register(with registrar: FlutterPluginRegistrar) {
        let channel = FlutterMethodChannel(name: "your_midi_channel", binaryMessenger: registrar.messenger())
        let instance = YourMidiPlugin()
        registrar.addMethodCallDelegate(instance, channel: channel)
    }
    
    func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        switch call.method {
        case "openMidiPort":
            // 先关闭旧端口再打开新的
            Self.midiPort?.close()
            Self.midiPort = MidiPort()
            result(nil)
        case "closeMidiPort":
            Self.midiPort?.close()
            Self.midiPort = nil
            result(nil)
        default:
            result(FlutterMethodNotImplemented)
        }
    }
}

iOS端热重启时,register(with:)会被重新调用,所以在打开新端口前先关闭旧的也能避免冲突。


内容的提问来源于stack exchange,提问作者dmnk

火山引擎 最新活动