Flutter插件在热重启场景下是否存在析构函数?如何在热重启前释放占用资源?
解决Flutter插件热重启时MIDI端口未关闭导致崩溃的问题
我之前也碰到过类似的Flutter插件热重启资源泄漏问题,尤其是涉及硬件端口这类独占性资源时,确实挺棘手的!结合你的情况,以下几个方案应该能帮到你:
方案1:Android原生层监听Isolate生命周期(最可靠)
热重启时,Flutter Engine不会被销毁,但Dart侧的Isolate会重启。我们可以在原生插件中监听Isolate的停止事件,在旧Isolate销毁时关闭MIDI端口:
- 在你的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的退出信号,主动调用原生方法关闭端口:
- 在你的插件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'); } } }
- 原生层对应实现
closeMidiPort方法,关闭端口即可。
这个方案的好处是跨平台兼容,但依赖Dart层的初始化逻辑,需要确保每次热重启后初始化前先清理旧资源。
方案3:iOS端的补充方案(如果需要支持iOS)
如果你的插件需要支持iOS,热重启时iOS的FlutterEngine同样不会销毁,但可以通过FlutterMethodChannel的setMethodCallHandler的变化来判断:
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




