Flutter插件多实例仅单个触发回调的问题排查与修复
问题:Flutter插件单Widget多实例仅第二个能收到回调,第一个完全无响应
原本功能正常的Flutter插件,在单个Widget中使用多个实例时,只有第二个实例能收到回调,第一个实例完全无法触发回调。以下是相关代码和日志:
插件代码
import 'dart:async'; import 'package:flutter/services.dart'; class FlutterOnlineRadio { static const MethodChannel _channel = MethodChannel('flutter_online_radio'); static int instance_count = 0; static int STATUS_IDLE = 0; static int STATUS_PLAYING = 1; static int STATUS_BUFFERING = 2; static int STATUS_PAUSED = 3; static int STATUS_ENDED = 4; int _playbackState = 1; bool _playWhenReady = true; int playerStatus = STATUS_IDLE; String playerStatusString = ""; String instance = ""; void Function(int, String, dynamic)? onStatusChange; FlutterOnlineRadio(String ins) { instance_count++; print("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"); print("Current instance : $instance , new : $ins, totsl instances : $instance_count"); print("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"); instance = ins; _playbackState = 1; //STATE_IDLE; _playWhenReady = true; _channel.setMethodCallHandler((MethodCall call) async { print("New Method Call Received : -------------- $instance of $instance_count -------------- : ${call.method}" ); if (call.method == "onPlayerStateChanged") { dynamic args = call.arguments; _playbackState = args["playbackState"]; _playWhenReady = args["playWhenReady"]; _setPlayingState(); onStatusChange?.call(playerStatus, playerStatusString, args); } else if(call.method == "onTrackUpdate") { onStatusChange?.call(playerStatus, playerStatusString, call.arguments); } }); _channel.invokeMethod('initialize'); } void playRadio({String url = '', String title = '', String imagePath =''}) async { await _channel.invokeMethod('play', {"url": url, "title": title, "imagePath": imagePath}); } void playIfPaused() async { await _channel.invokeMethod('playIfPaused'); } void playAlbum( {String data ='', String imagePath='', String baseUrl='', String dataDir='', int index=0}) async { await _channel.invokeMethod('playAlbum', { "data": data, "imagePath": imagePath, "baseUrl": baseUrl, "dataDir": dataDir, "index": index, }); } void stop() async { await _channel.invokeMethod('stop'); } void pause() async { await _channel.invokeMethod('pause'); } void seekTo(int milisecs) async { await _channel.invokeMethod('seekTo', { "milisecs" : milisecs }); } Future<dynamic> getStatus() async { dynamic status = await _channel.invokeMethod('getStatus'); _playbackState = status["playbackState"]; _playWhenReady = status["playWhenReady"]; _setPlayingState(); dynamic tmp = { "playerStatus": playerStatus, "playerStatusString": playerStatusString, }; var returnVal = {}; returnVal.addAll(tmp); returnVal.addAll(status); return returnVal; } void _setPlayingState() { const int STATE_IDLE = 1; const int STATE_BUFFERING = 2; const int STATE_READY = 3; const int STATE_ENDED = 4; switch (_playbackState) { case STATE_IDLE: playerStatus = STATUS_IDLE; playerStatusString = ""; break; case STATE_READY: playerStatus = (_playWhenReady) ? STATUS_PLAYING : STATUS_PAUSED; playerStatusString = (_playWhenReady) ? "Playing..." : "Paused"; break; case STATE_BUFFERING: playerStatus = STATUS_BUFFERING; playerStatusString = "Buffering..."; break; case STATE_ENDED: playerStatus = STATUS_ENDED; playerStatusString = ""; break; } } }
Widget代码
import 'dart:io'; import 'package:flutter/material.dart'; import '../constants.dart'; import 'package:flutter_online_radio/flutter_online_radio.dart'; class MyPersistentAudio extends StatefulWidget { MyPersistentAudio(); @override _MyPersistentAudioState createState() => _MyPersistentAudioState(); } class _MyPersistentAudioState extends State<MyPersistentAudio> { FlutterOnlineRadio flutterOnlineRadio = new FlutterOnlineRadio("persistent instance 1"); FlutterOnlineRadio flutterOnlineRadio2 = new FlutterOnlineRadio("persistent instance 2"); int playerStatus = FlutterOnlineRadio.STATUS_IDLE; String playerStatusString = ""; String playingTitle = ""; String imagePath = ""; bool widgetActive = false; @override void initState() { flutterOnlineRadio.onStatusChange = (status, statusString, args) { kksPrint("my_persistent_audio.dart : onStatusChange"); kksPrint(status); kksPrint(args); setState(() { //Just Refresh : Important }); }; flutterOnlineRadio2.onStatusChange = (status, statusString, args) { kksPrint("my_persistent_audio.dart : 2 : onStatusChange"); kksPrint(status); kksPrint(args); }; super.initState(); } @override Widget build(BuildContext context) { print(playerStatus); return AnimatedContainer( duration: Duration(seconds: 1), height: 50, width: MediaQuery.of(context).size.width, color: Colors.white, child: Directionality( textDirection: TextDirection.ltr, child: Text("Radio ..." ), ), ); } }
日志信息
I/flutter ( 4848): New Method Call Received : -------------- persistent instance 2 of 2 -------------- : onTrackUpdate I/flutter ( 4848): my_persistent_audio.dart : 2 : onStatusChange I/flutter ( 4848): 0 I/flutter ( 4848): {duration: 3476940, audioId: audio_39_1, bufferedPosition: 2022008, albumId: audio_39, position: 2006515} I/flutter ( 4848): 0 I/flutter ( 4848): New Method Call Received : -------------- persistent instance 2 of 2 -------------- : onTrackUpdate I/flutter ( 4848): my_persistent_audio.dart : 2 : onStatusChange I/flutter ( 4848): 0 I/flutter ( 4848): {duration: 3476940, audioId: audio_39_1, bufferedPosition: 2153090, albumId: audio_39, position: 2007498} I/flutter ( 4848): 0 I/flutter ( 4848): New Method Call Received : -------------- persistent instance 2 of 2 -------------- : onTrackUpdate I/flutter ( 4848): my_persistent_audio.dart : 2 : onStatusChange I/flutter ( 4848): 0 I/flutter ( 4848): {duration: 3476940, audioId: audio_39_1, bufferedPosition: 2153090, albumId: audio_39, position: 2008514} I/flutter ( 4848): 0 I/flutter ( 4848): New Method Call Received : -------------- persistent instance 2 of 2 -------------- : onTrackUpdate I/flutter ( 4848): my_persistent_audio.dart : 2 : onStatusChange I/flutter ( 4848): 0 I/flutter ( 4848): {duration: 3476940, audioId: audio_39_1, bufferedPosition: 2153090, albumId: audio_39, position: 2009522} I/flutter ( 4848): 0 I/flutter ( 4848): New Method Call Received : -------------- persistent instance 2 of 2 -------------- : onTrackUpdate I/flutter ( 4848): my_persistent_audio.dart : 2 : onStatusChange I/flutter ( 4848): 0 I/flutter ( 4848): {duration: 3476940, audioId: audio_39_1, bufferedPosition: 2153090, albumId: audio_39, position: 2010503} I/flutter ( 4848): 0 I/flutter ( 4848): New Method Call Received : -------------- persistent instance 2 of 2 -------------- : onTrackUpdate I/flutter ( 4848): my_persistent_audio.dart : 2 : onStatusChange
问题原因
核心问题是静态MethodChannel的Handler被覆盖:
_channel是类的静态常量,所有实例共享同一个MethodChannel对象- 第一个实例初始化时设置了
setMethodCallHandler,但第二个实例初始化时会直接覆盖这个Handler - 原生端发送的所有回调都会被最后设置的Handler(第二个实例的)处理,第一个实例的Handler完全不会被触发
- 日志中所有回调都显示来自"persistent instance 2",也验证了这一点
解决方法
推荐使用为每个实例创建独立MethodChannel的方案,让每个实例拥有专属的通信通道,避免Handler覆盖:
修改后的插件代码
import 'dart:async'; import 'package:flutter/services.dart'; class FlutterOnlineRadio { static int instance_count = 0; static int STATUS_IDLE = 0; static int STATUS_PLAYING = 1; static int STATUS_BUFFERING = 2; static int STATUS_PAUSED = 3; static int STATUS_ENDED = 4; final MethodChannel _channel; // 改为实例成员,不再共享 int _playbackState = 1; bool _playWhenReady = true; int playerStatus = STATUS_IDLE; String playerStatusString = ""; String instance = ""; final String _channelName; void Function(int, String, dynamic)? onStatusChange; // 初始化时生成专属Channel名称 FlutterOnlineRadio(String ins) : _channelName = 'flutter_online_radio_${instance_count + 1}' { instance_count++; instance = ins; _playbackState = 1; _playWhenReady = true; // 为当前实例创建专属MethodChannel _channel = MethodChannel(_channelName); print("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"); print("Current instance : $instance , new : $ins, total instances : $instance_count, channel: $_channelName"); print("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"); _channel.setMethodCallHandler((MethodCall call) async { print("New Method Call Received : -------------- $instance of $instance_count -------------- : ${call.method}" ); if (call.method == "onPlayerStateChanged") { dynamic args = call.arguments; _playbackState = args["playbackState"]; _playWhenReady = args["playWhenReady"]; _setPlayingState(); onStatusChange?.call(playerStatus, playerStatusString, args); } else if(call.method == "onTrackUpdate") { onStatusChange?.call(playerStatus, playerStatusString, call.arguments); } }); // 初始化时传递Channel名称给原生,让原生针对该Channel发送回调 _channel.invokeMethod('initialize', {"channelName": _channelName}); } // 其余方法保持不变,使用当前实例的_channel void playRadio({String url = '', String title = '', String imagePath =''}) async { await _channel.invokeMethod('play', {"url": url, "title": title, "imagePath": imagePath}); } void playIfPaused() async { await _channel.invokeMethod('playIfPaused'); } void playAlbum( {String data ='', String imagePath='', String baseUrl='', String dataDir='', int index=0}) async { await _channel.invokeMethod('playAlbum', { "data": data, "imagePath": imagePath, "baseUrl": baseUrl, "dataDir": dataDir, "index": index, }); } void stop() async { await _channel.invokeMethod('stop'); } void pause() async { await _channel.invokeMethod('pause'); } void seekTo(int milisecs) async { await _channel.invokeMethod('seekTo', { "milisecs" : milisecs }); } Future<dynamic> getStatus() async { dynamic status = await _channel.invokeMethod('getStatus'); _playbackState = status["playbackState"]; _playWhenReady = status["playWhenReady"]; _setPlayingState(); dynamic tmp = { "playerStatus": playerStatus, "playerStatusString": playerStatusString, }; var returnVal = {}; returnVal.addAll(tmp); returnVal.addAll(status); return returnVal; } void _setPlayingState() { const int STATE_IDLE = 1; const int STATE_BUFFERING = 2; const int STATE_READY = 3; const int STATE_ENDED = 4; switch (_playbackState) { case STATE_IDLE: playerStatus = STATUS_IDLE; playerStatusString = ""; break; case STATE_READY: playerStatus = (_playWhenReady) ? STATUS_PLAYING : STATUS_PAUSED; playerStatusString = (_playWhenReady) ? "Playing..." : "Paused"; break; case STATE_BUFFERING: playerStatus = STATUS_BUFFERING; playerStatusString = "Buffering..."; break; case STATE_ENDED: playerStatus = STATUS_ENDED; playerStatusString = ""; break; } } }
原生端对应修改(以Android为例)
在原生代码中,需要根据Flutter传递的channelName创建对应的MethodChannel,发送回调时使用该Channel:
// 初始化时获取并保存Channel名称 String channelName = call.argument("channelName"); MethodChannel channel = new MethodChannel(getFlutterView(), channelName); // 发送回调时使用该channel channel.invokeMethod("onTrackUpdate", params); channel.invokeMethod("onPlayerStateChanged", params);
内容的提问来源于stack exchange,提问作者Suresh Kumar
相关产品推荐
相关产品推荐

