You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 10:24:05