如何为Flutter WebRTC通话实现类似Meet/WhatsApp的持久化通话通知(带挂断/静音控制)
我完全懂你的需求——那种和原生通话、Meet/WhatsApp一模一样的、粘在通知栏里不能随便划掉、带挂断/静音控制的通话通知对吧?你之前找的方向是对的,问题出在flutter_callkeep的配置没做全,因为这种系统级的通话通知必须依赖原生平台的通话框架(Android的ConnectionService,iOS的CallKit),普通的前台通知包(比如flutter_foreground_task、awesome_notifications)根本做不到那种“原生级持久化”的效果——它们的通知都是可被用户轻易关闭的,而系统通话框架的通知是和通话状态绑定的,只有结束通话才会消失。
下面是一步步的正确实现方案,基于flutter_callkeep(也就是你提到的webtrit_callkeep底层依赖的包),这是目前唯一能跨平台实现原生通话通知的方案:
一、核心原理先搞懂
Meet、WhatsApp和原生通话之所以用同一个通知模板,是因为它们都直接集成了系统的通话服务:
- Android:通过
ConnectionService注册为系统认可的通话应用,系统会自动生成标准的通话通知,并且这个通知是不可手动关闭的,只有通话结束才会消失。 - iOS:通过
CallKit将通话注册为系统通话,系统会显示原生的通话界面(包括锁屏上的控制和通知栏的持久化通知)。
flutter_callkeep就是对这两个原生框架的Flutter封装,所以只要配置正确,系统会自动帮你生成那种标准的通话通知,不需要额外的前台通知包。
二、flutter_callkeep的完整配置(解决你之前没显示通知的问题)
你之前用flutter_callkeep没出效果,大概率是AndroidManifest或iOS Info.plist的配置没做全,或者初始化/调用时机不对。
1. 依赖安装
先在pubspec.yaml里添加依赖:
dependencies: flutter_callkeep: ^latest_version
然后执行flutter pub get。
2. Android端配置(关键!)
Android需要注册ConnectionService,并申请必要的权限:
- 打开
android/app/src/main/AndroidManifest.xml,添加以下内容:<!-- 必要权限 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CALL" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.CALL_PHONE" /> <!-- 注册ConnectionService,这是让系统识别你为通话应用的核心 --> <application ...> <!-- 其他已有的activity/service --> <service android:name="com.ryanheise.callkeep.RNCallKeepConnectionService" android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"> <intent-filter> <action android:name="android.telecom.ConnectionService" /> </intent-filter> </service> </application> - 另外,针对Android 10+,还需要在代码中申请
POST_NOTIFICATIONS权限(如果你的targetSdk是33+)。
3. iOS端配置
- 打开
ios/Runner/Info.plist,添加以下内容:<key>UIBackgroundModes</key> <array> <string>voip</string> </array> <key>NSMicrophoneUsageDescription</key> <string>需要使用麦克风进行通话</string> - 确保你的Xcode项目中开启了“Voice over IP”后台模式(在项目设置的Signing & Capabilities -> Background Modes里勾选)。
三、Flutter端代码实现(从初始化到关联WebRTC逻辑)
1. 初始化CallKeep
在你的应用启动时(比如main函数里,或者进入通话页面时)初始化CallKeep:
import 'package:flutter_callkeep/flutter_callkeep.dart'; void initCallKeep() { final config = CallKeepConfig( appName: '你的应用名称', androidConfig: AndroidConfig( logo: 'ic_launcher', // 你的应用图标(放在android/res/mipmap下) notificationIcon: 'ic_launcher', color: '#000000', // 通知栏的背景色,对应原生通话通知的色调 incomingCallNotificationChannelName: '通话通知', ), iosConfig: IOSConfig( iconName: 'AppIcon', maximumCallGroups: 1, maximumCallsPerCallGroup: 1, ), ); CallKeep.instance.setup(config, (event) async { // 处理CallKeep的事件回调,比如用户点击挂断、静音按钮 switch (event.type) { case CallKeepEventType.callAccept: // 这里可以写你的WebRTC接听逻辑 break; case CallKeepEventType.callDecline: // 这里写你的WebRTC挂断逻辑 await endCall(event.callUuid!); break; case CallKeepEventType.callEnded: // 通话结束的回调,清理CallKeep状态 await CallKeep.instance.endCall(event.callUuid!); break; case CallKeepEventType.toggleMute: // 用户点击静音按钮的回调,切换你的WebRTC本地音频的静音状态 final isMuted = event.data!['muted'] as bool; await toggleMute(isMuted); break; // 其他事件比如扬声器切换等,按需处理 default: break; } }); }
2. 启动通话(触发系统通话通知)
当你的WebRTC通话连接成功后,调用CallKeep的startCall方法,这会让系统立刻显示原生的通话通知:
Future<void> startCall(String callId, String callerName) async { // 先检查权限(Android需要POST_NOTIFICATIONS,iOS需要麦克风权限) await requestPermissions(); // 启动CallKeep通话,这一步会触发系统的通话通知 await CallKeep.instance.startCall( uuid: callId, // 唯一标识这个通话的ID,用你自己的通话ID即可 handle: callerName, // 显示在通知栏的来电方名称 type: CallType.video, // 视频通话,也可以选audio ); // 如果是Android,还可以设置通话的额外状态(比如静音、扬声器状态) await CallKeep.instance.setMuted(callId, false); await CallKeep.instance.setSpeakerphoneEnabled(callId, false); }
3. 关联你的WebRTC控制逻辑
在CallKeep的事件回调里,你只需要把系统按钮的点击事件映射到自己的WebRTC方法即可:
- 比如挂断按钮:调用你自己的
hangupCall()方法,然后通知CallKeep结束通话 - 比如静音按钮:调用你自己的
toggleLocalMute()方法,然后同步CallKeep的静音状态
4. 结束通话(清理通知)
当通话结束时,必须调用CallKeep的endCall方法,系统才会自动移除通话通知:
Future<void> endCall(String callId) async { // 先执行你的WebRTC挂断逻辑 await webRtcHangup(); // 通知CallKeep结束通话,系统会自动关闭通话通知 await CallKeep.instance.endCall(callId); }
四、解决你之前遇到的坑
为什么之前用flutter_callkeep没显示通知?
- 90%的概率是AndroidManifest里没注册
ConnectionService,或者权限没申请全(比如FOREGROUND_SERVICE_CALL权限) - 另外,必须在通话建立后调用
startCall,不能提前调用,系统只会在“活跃通话”状态下显示通知
- 90%的概率是AndroidManifest里没注册
为什么普通前台通知包做不到这种持久化?
- 普通前台通知的优先级再高,也只是应用级的通知,用户可以手动关闭;而CallKeep的通知是系统通话框架生成的,属于“系统级通知”,只有结束通话才能关闭,这就是原生通话、Meet/WhatsApp的通知不会被划掉的原因
要不要结合flutter_foreground_task?
- 完全不需要!CallKeep本身已经会让你的通话进程保持活跃(因为系统会优先保活通话相关的进程),额外的前台任务包反而会冲突,这就是你之前组合使用时两个通知都不显示的原因
五、验证方案
按上面的步骤配置后,当你启动WebRTC通话并调用startCall,你会看到:
- Android:通知栏出现和原生通话一模一样的通知,带挂断、静音、扬声器按钮,不能手动划掉,只有调用
endCall才会消失 - iOS:锁屏和通知栏出现原生的通话控制界面,和系统通话完全一致
如果还是没效果,先检查:
- Android:有没有在设置里把你的应用设为“默认通话应用”?(部分国产ROM需要手动设置,比如小米、华为)
- iOS:有没有开启VoIP后台模式?
- 代码里有没有正确调用
startCall,并且传入了有效的uuid?
内容来源于stack exchange

