Flutter SIP通话应用:推送唤醒后关联Call对象的方案咨询
解决dart-sip-ua推送唤醒后无法关联通话的方案
针对你遇到的推送唤醒后拿不到Call对象、无法关联通话的问题,下面是几个可行的实践方案:
1. 用SIP标准Call-ID替代客户端生成的CallUUID
客户端生成的CallUUID是内存级标识,进程重启后就会丢失,且服务器无法提前获取。但SIP通话的Call-ID是协议层面的唯一标识,服务器在发起/转接通话时就能拿到,完全可以放到推送payload里发给客户端:
- 推送消息中携带Call-ID、通话方向(呼入/呼出)、对方号码等元数据;
- 应用唤醒后,先把Call-ID存在本地存储(比如SharedPreferences);
- 初始化SIPUAHelper并主动触发注册(而非等待自动重连),注册成功后:
- 呼入场景:服务器会重新发送INVITE请求,客户端触发
onCallReceived事件,此时对比新Call对象的Call-ID和本地存储的ID,匹配后绑定到CallScreen; - 呼出场景:遍历
sipUAHelper.getCalls()列表,找到Call-ID匹配的Call对象即可。
- 呼入场景:服务器会重新发送INVITE请求,客户端触发
2. 先渲染临时UI,后台自动关联Call对象
不用等Call对象完全获取再显示CallScreen,先利用推送携带的元数据渲染基础UI(比如显示对方号码、“连接中”状态),让用户有即时反馈,后台再默默处理SIP重连和Call对象关联:
- 收到推送后直接跳转到CallScreen,用推送里的元数据初始化UI状态;
- 在CallScreen的
initState中主动调用sipUAHelper.register()加速重连; - 监听SIP注册成功事件和通话事件,一旦匹配到对应的Call对象,立即更新UI,替换临时状态并绑定通话操作(接听/挂断)。
3. 持久化通话关键信息到本地
在通话初始化(发起/收到)时,把Call的Call-ID、UUID、通话状态、对方信息等持久化到本地数据库或SharedPreferences:
- 发起通话时:创建Call对象后立刻存储其关键信息;
- 收到通话时:在
onCallReceived触发时存储Call对象信息; - 应用唤醒后:先从本地读取这些信息,待SIP重连完成后,用
SIPUAHelper.findCall()或遍历通话列表匹配,找到对应的Call对象; - 通话结束后,及时清理本地存储的通话信息,避免后续误匹配。
代码示例:推送唤醒后的Call对象关联
// 处理推送消息 void handlePushNotification(Map<String, dynamic> payload) { final String callId = payload['call_id']; final String remoteNumber = payload['remote_number']; // 存储待关联的Call-ID SharedPreferences.getInstance().then((prefs) { prefs.setString('pending_call_id', callId); prefs.setString('pending_remote_number', remoteNumber); }); // 直接跳转到CallScreen Navigator.push( context, MaterialPageRoute(builder: (_) => CallScreen()) ); } // CallScreen实现 class CallScreen extends StatefulWidget { @override _CallScreenState createState() => _CallScreenState(); } class _CallScreenState extends State<CallScreen> { Call? _activeCall; String? _pendingCallId; String? _remoteNumber; late SharedPreferences _prefs; @override void initState() { super.initState(); _initLocalData(); _setupSipListeners(); // 主动触发SIP注册,加速重连 sipUAHelper.register(); } void _initLocalData() async { _prefs = await SharedPreferences.getInstance(); setState(() { _pendingCallId = _prefs.getString('pending_call_id'); _remoteNumber = _prefs.getString('pending_remote_number'); }); } void _setupSipListeners() { // 监听呼入通话事件 sipUAHelper.onCallReceived.listen((Call call) { if (call.id == _pendingCallId) { _bindActiveCall(call); } }); // 监听SIP注册成功事件 sipUAHelper.onRegistered.listen((_) { // 遍历现有通话,匹配Call-ID for (final call in sipUAHelper.getCalls()) { if (call.id == _pendingCallId) { _bindActiveCall(call); break; } } }); // 监听通话结束事件,清理本地数据 sipUAHelper.onCallEnded.listen((Call call) { if (call.id == _pendingCallId) { _clearPendingCallData(); } }); } void _bindActiveCall(Call call) { setState(() { _activeCall = call; }); // 绑定成功后可执行接听等操作 } void _clearPendingCallData() { _prefs.remove('pending_call_id'); _prefs.remove('pending_remote_number'); setState(() { _pendingCallId = null; _remoteNumber = null; _activeCall = null; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('通话')), body: Center( child: _activeCall == null ? Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('正在连接: $_remoteNumber'), const SizedBox(height: 20), const CircularProgressIndicator(), ], ) : Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('通话对象: ${_activeCall?.remoteIdentity?.uri.user}'), const SizedBox(height: 20), ElevatedButton( onPressed: () => _activeCall?.answer(), child: const Text('接听'), ), const SizedBox(height: 10), ElevatedButton( onPressed: () { _activeCall?.hangup(); _clearPendingCallData(); }, style: ElevatedButton.styleFrom(backgroundColor: Colors.red), child: const Text('挂断'), ), ], ), ), ); } }
额外优化:缩短SIP重连时间
默认的自动重连间隔可能较长,你可以主动调用sipUAHelper.register()触发即时注册,或者在初始化SIPUAHelper时设置更短的重连超时参数,进一步减少等待时间。
内容的提问来源于stack exchange,提问作者Aidar
相关产品推荐
相关产品推荐

