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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 07:19:57