React Native CallKeep+FCM集成:点击接听卡顿,如何跳过VoiceConnectionService?
问题描述
我正在做react-native-callkeep与Firebase FCM通知的集成,已完成CallKeep基础集成,触发FCM通知时能正常收到来电通知。但在真实安卓设备上,当应用处于杀死/退出或后台状态时,点击接听按钮会导致应用卡顿。
调试日志如下:
D/RNCallKeep: [RNCallKeepModule][onReceive] ACTION_ANSWER_CALL V/RNCallKeep: [RNCallKeepModule] sendEventToJS, eventName: RNCallKeepPerformAnswerCallAction, bound: true, hasListeners: true args : { NativeMap: {"withVideo":false,"callUUID":"b52560f4-2360-4c69-8b05-7530a1b7b2fe"} } D/RNCallKeep: [RNCallKeepModule][onReceive] ACTION_AUDIO_SESSION V/RNCallKeep: [RNCallKeepModule] sendEventToJS, eventName: RNCallKeepDidActivateAudioSession, bound: true, hasListeners: true args : null I/ReactNativeJS: 'answerCall', 'b52560f4-2360-4c69-8b05-7530a1b7b2fe' D/RNCallKeep: [RNCallKeepModule] backToForeground, app isOpened ?true I/TelecomFramework: VoiceConnectionService: answer TC@13_1: CS.an->H.CS.an@AAA D/RNCallKeep: [VoiceConnection] onAnswer(int) executed D/RNCallKeep: [VoiceConnection] onAnswer called, videoState: 0, answered: true D/RNCallKeep: [VoiceConnection] onAnswer() executed D/RNCallKeep: [VoiceConnection] onAnswer called, videoState: 0, answered: true D/RNCallKeep: [VoiceConnection] onStateChanged called, state : 6 D/RNCallKeep: [VoiceConnection] onDisconnect executed D/RNCallKeep: [VoiceConnectionService] deinitConnection:b52560f4-2360-4c69-8b05-7530a1b7b2fe D/RNCallKeep: [VoiceConnectionService] stopForegroundService
推测是点击接听后应用与TELECOM_CONNECTION_SERVICE通信,找不到对应连接导致卡顿。当前核心集成代码:
FCM后台消息处理:
messaging().setBackgroundMessageHandler(async remoteMessage => { console.log('Message handled in the background!', remoteMessage); displayIncomingCall(remoteMessage); });
来电通知展示:
export const displayIncomingCall = remoteMessage => { const {data} = remoteMessage; const {customer_name, telephone} = data; const uuid = uuidv4(); RNCallKeep.displayIncomingCall( uuid, `${telephone}`, `${customer_name}`, 'generic', true, ); };
接听事件监听:
export const answerCall = ({callUUID}) => { console.log('answerCall', callUUID); invokeApp({data: notificationData}); };
需求:跳过VoiceConnectionService步骤,点击接听后直接让应用回到前台,同时在HeadlessJS唤起应用时传递相关来电数据。
解决方案
1. 修改安卓Native层,拦截接听事件跳过Telecom服务
直接修改安卓项目中RNCallKeepModule.java的onReceive方法,拦截ACTION_ANSWER_CALL事件,跳过VoiceConnectionService的调用,直接处理应用唤起和数据传递:
// 注释掉原有和VoiceConnectionService绑定的代码 // Intent answerIntent = new Intent(context, VoiceConnectionService.class); // answerIntent.setAction(RNCallKeepModule.ACTION_ANSWER_CALL); // answerIntent.putExtra(RNCallKeepModule.EXTRA_CALL_UUID, callUUID); // context.startService(answerIntent); // 替换为直接唤起应用的逻辑 Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()); launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); // 传递来电核心数据(可提前从FCM消息中存储到SharedPreferences,或直接从Intent参数获取) launchIntent.putExtra("callUUID", callUUID); launchIntent.putExtra("customer_name", "存储的客户名称"); launchIntent.putExtra("telephone", "存储的电话号码"); context.startActivity(launchIntent); // 保留JS层事件通知 sendEventToJS("RNCallKeepPerformAnswerCallAction", params);
注:如果是HeadlessJS场景,建议在处理FCM后台消息时,将来电数据存入SharedPreferences,避免Intent传递数据丢失。
2. 优化JS层接听事件逻辑
修改answerCall方法,直接处理来电通知收尾和应用唤起,不再依赖CallKeep的默认连接逻辑:
export const answerCall = ({callUUID}) => { console.log('answerCall', callUUID); // 1. 关闭CallKeep来电通知,避免界面残留 RNCallKeep.endCall(callUUID); // 2. 唤起应用前台(需提前配置App Scheme) Linking.openURL('your-app-scheme://').catch(err => console.error('唤起应用失败:', err)); // 3. 存储来电数据到本地,供应用唤起后读取 AsyncStorage.setItem('incomingCallData', JSON.stringify(notificationData)); };
3. 应用启动/唤起时读取来电数据
在App根组件中监听应用状态变化,读取传递的来电数据并处理:
import { AppState, Linking } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; useEffect(() => { // 处理应用冷启动时的Intent参数 Linking.getInitialURL().then(url => { if (url) { const params = new URLSearchParams(url.split('?')[1]); const callData = { callUUID: params.get('callUUID'), customer_name: params.get('customer_name'), telephone: params.get('telephone') }; // 跳转到通话页面或处理来电逻辑 navigateToCallScreen(callData); } }); // 监听应用从后台回到前台 const handleAppStateChange = (nextAppState) => { if (nextAppState === 'active') { AsyncStorage.getItem('incomingCallData').then(data => { if (data) { const callData = JSON.parse(data); navigateToCallScreen(callData); AsyncStorage.removeItem('incomingCallData'); // 用完清除数据 } }); } }; AppState.addEventListener('change', handleAppStateChange); return () => { AppState.removeEventListener('change', handleAppStateChange); }; }, []);
4. 禁用CallKeep的Telecom服务注册
在安卓AndroidManifest.xml中注释掉VoiceConnectionService的注册,避免系统自动触发相关逻辑:
<!-- 注释该服务注册 --> <!-- <service android:name="io.wazo.callkeep.VoiceConnectionService" android:label="Wazo CallKeep" android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"> <intent-filter> <action android:name="android.telecom.ConnectionService" /> </intent-filter> </service> -->
关键注意事项
- 安卓12及以上需申请
POST_NOTIFICATIONS权限,确保来电通知正常展示 - 需在
AndroidManifest.xml的MainActivity中配置App Scheme,用于唤起应用:<intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="your-app-scheme" /> </intent-filter> - HeadlessJS环境下,避免在后台处理时调用依赖UI的API,优先用Native层存储数据
内容的提问来源于stack exchange,提问作者Mankesh
相关产品推荐
相关产品推荐

