Flutter集成Agora问题:应用崩溃/热重启/关闭时无法调用leaveChannel
解决Agora视频通话中应用异常退出时无法离开频道的问题
问题根源
错误码-17表示用户尝试加入已处于其中的频道。正常情况下dispose()方法会调用leaveChannel(),但应用热重启、崩溃或被强制关闭时,Flutter的dispose()不会触发,导致服务器端仍标记该用户在频道内,重启后再次加入就会报错。
解决方案
1. 监听底层应用生命周期事件
仅依赖WidgetBindingObserver无法覆盖所有极端场景,需结合SystemChannels.lifecycle监听应用即将销毁的状态,在此时强制调用离开频道逻辑:
2. 初始化时清理旧引擎实例
每次启动应用初始化Agora引擎前,先尝试清理可能残留的旧引擎实例,避免重复占用UID:
3. 捕获加入频道异常并重试
遇到-17错误时,先强制离开再重新加入频道,作为兜底处理。
修改后的完整代码
import 'dart:math'; import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:agora_rtc_engine/rtc_engine.dart'; import 'package:agora_rtc_engine/rtc_local_view.dart' as RtcLocalView; import 'package:agora_rtc_engine/rtc_remote_view.dart' as RtcRemoteView; void main() => runApp(MaterialApp(home: MyApp())); class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> with WidgetBindingObserver { int? _remoteUid; late RtcEngine _engine; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); // 监听底层生命周期事件 SystemChannels.lifecycle.setMessageHandler((message) async { if (message?.contains('AppLifecycleState.detached') ?? false) { _cleanupAgoraEngine(); } return null; }); initAgora(); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); SystemChannels.lifecycle.setMessageHandler(null); _cleanupAgoraEngine(); super.dispose(); } // 统一清理Agora引擎的方法 void _cleanupAgoraEngine() { try { _engine.leaveChannel(); _engine.destroy(); } catch (e) { print('清理引擎时出错: $e'); } } void _addListeners() { _engine.setEventHandler( RtcEngineEventHandler( joinChannelSuccess: (String channel, int uid, int elapsed) { print("本地用户 $uid 加入频道"); setState(() {}); }, rejoinChannelSuccess: (channel, uid, elapsed) { print("远端用户 $uid 重新加入"); setState(() => _remoteUid = uid); }, userJoined: (int uid, int elapsed) { print("远端用户 $uid 加入"); setState(() => _remoteUid = uid); }, leaveChannel: (stats) { print('****** 用户离开频道'); setState(() => _remoteUid = null); }, userOffline: (int uid, UserOfflineReason reason) { print("远端用户 $uid 离线"); setState(() => _remoteUid = null); }, ), ); } Future<void> initAgora() async { await [Permission.microphone, Permission.camera].request(); print('init agora 1'); // 初始化前先清理可能存在的旧引擎 try { await _engine.leaveChannel(); await _engine.destroy(); } catch (e) { print('无旧引擎需要清理: $e'); } // 创建新引擎实例 _engine = await RtcEngine.createWithContext(RtcEngineContext("xxx")); print('init agora 2'); _addListeners(); print('init agora 3'); await _engine.enableVideo(); await _engine.enableAudio(); var rng = Random(); final uId = rng.nextInt(100); // 捕获加入频道异常并处理 try { await _engine.joinChannel('xxxxx', "firstchannel", null, uId); } on PlatformException catch (e) { if (e.code == '-17') { print('检测到重复加入,先离开再重试'); await _engine.leaveChannel(); await _engine.joinChannel('xxxxx', "firstchannel", null, uId); } else { rethrow; } } print('init agora 4'); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Agora Video Call')), body: Stack( children: [ Center(child: _remoteVideo()), Align( alignment: Alignment.bottomRight, child: Container( width: 100, height: 100, child: const Center(child: RtcLocalView.SurfaceView()), ), ), TextButton( onPressed: () async { await _engine.leaveChannel(); setState(() => _remoteUid = null); }, child: const Text('Leave'), ) ], ), ); } Widget _remoteVideo() { if (_remoteUid != null) { return RtcRemoteView.SurfaceView(uid: _remoteUid!, channelId: 'firstchannel'); } else { return const Text( '等待远端用户加入', textAlign: TextAlign.center, ); } } }
额外说明
- Agora服务器默认会在用户心跳中断约10秒后自动移除该用户,但上述客户端逻辑可以避免这段等待时间内的重复加入错误。
_cleanupAgoraEngine()方法统一处理引擎清理,确保在所有可能的退出场景下都能尝试调用leaveChannel()。
内容的提问来源于stack exchange,提问作者Marcel Dz
相关产品推荐
相关产品推荐

