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

React Native iOS VoIP应用接听来电后无法启动问题求助

React Native iOS VoIP应用在iPhone 12上Killed/Background状态接听后无法启动的问题

我正在开发一款基于iOS的React Native VoIP通话应用,使用RNCallKeep和RNVoipPushNotification实现核心功能。目前应用在前台、后台状态下均可正常处理通话,但当应用处于**Killed(已杀死)或Background(后台)**状态时,接收到VoIP来电并点击接听后,通话已在后台被接受,但应用无法启动。

该应用在Android端运行正常,在iPhone 8、iPhone 6及iPhone SE设备上也能正常工作,但仅在iPhone 12上出现此异常。


AppDelegate文件

#import "AppDelegate.h"
#import <Firebase.h>
#import "RNNotifications.h"
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import "RNCallKeep.h"
#import <PushKit/PushKit.h>                 
#import "RNVoipPushNotificationManager.h"

#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>

static void InitializeFlipper(UIApplication *application) {
  FlipperClient *client = [FlipperClient sharedClient];
  SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
  [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
  [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
  [client addPlugin:[FlipperKitReactPlugin new]];
  [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
  [client start];
}
#endif

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
 if ([FIRApp defaultApp] == nil) {
      [FIRApp configure];
    }
#ifdef FB_SONARKIT_ENABLED
  InitializeFlipper(application);
#endif

  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];

  [RNCallKeep setup:@{@"appName": @"Med360Doctors", @"maximumCallGroups": @3, @"maximumCallsPerCallGroup": @1, @"supportsVideo": @YES}];

  [RNVoipPushNotificationManager voipRegistration];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Med360Doctors" initialProperties:nil];

  if (@available(iOS 13.0, *)) {
      rootView.backgroundColor = [UIColor systemBackgroundColor];
  } else {
      rootView.backgroundColor = [UIColor whiteColor];
  }

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  [RNNotifications startMonitorNotifications];
  return YES;
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  [RNNotifications didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
  [RNNotifications didFailToRegisterForRemoteNotificationsWithError:error];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
  [RNNotifications didReceiveBackgroundNotification:userInfo withCompletionHandler:completionHandler];
}


- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];

#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

 - (BOOL)application:(UIApplication *)application
 continueUserActivity:(NSUserActivity *)userActivity
   restorationHandler:(void(^)(NSArray<id<UIUserActivityRestoring>> * __nullable restorableObjects))restorationHandler
 {
   return [RNCallKeep application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
 }

 - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
  // Register VoIP push token (a property of PKPushCredentials) with server
  [RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:(NSString *)type];
}

- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type
{
  // --- The system calls this method when a previously provided push token is no longer valid for use. No action is necessary on your part to reregister the push type. Instead, use this method to notify your server not to send push notifications using the matching push token.
}

- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {

  NSString *uuid = payload.dictionaryPayload[@"uuid"];
  NSString *displayName = [NSString stringWithFormat:@"%@ Calling from Med360", payload.dictionaryPayload[@"displayName"]];
  NSString *handle = payload.dictionaryPayload[@"handle"];
  // --- this is optional, only required if you want to call `completion()` on the js side
  [RNVoipPushNotificationManager addCompletionHandler:uuid completionHandler:completion];

  // --- Process the received push
  [RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type];
//  NSDictionary *extra = [payload.dictionaryPayload valueForKeyPath:@"custom.path.to.data"];

  [RNCallKeep reportNewIncomingCall: uuid handle: handle handleType: @"generic" hasVideo: YES localizedCallerName: displayName supportsHolding: YES supportsDTMF: YES supportsGrouping: YES supportsUngrouping: YES fromPushKit: YES payload: nil withCompletionHandler: completion];
  
  // --- You don't need to call it if you stored `completion()` and will call it on the js side.
  completion();
}
@end

RNVoipPushNotification控制器文件

import React, { useEffect } from 'react';
import { useNavigation } from '@react-navigation/native';
import Incomingvideocall from './components/incomingCallUi/Incomingvideocall';
import VoipPushNotification from "react-native-voip-push-notification";

const NotificationController = () => {
  const navigation = useNavigation();
  useEffect(() => {
    VoipPushNotification.registerVoipToken();
    VoipPushNotification.addEventListener("notification", (notification) => {
     
      const { type } = notification;
      if (type === "Video") {
        const incomingCallAnswer = ({ callUUID }) => {
          navigation.navigate('McoVideoCall', {
            appointment: Object.assign({}, notification),
          });
          Incomingvideocall.endIncomingcallAnswer(callUUID);
          clearTimeout(timer);
          Incomingvideocall.endAllCall();
        };
        const endIncomingCall = () => {
          Incomingvideocall.endAllCall();
        };
        Incomingvideocall.configure(incomingCallAnswer, endIncomingCall);
      } else if (type === "DISCONNECTED") {
        Incomingvideocall.endAllCall();
      }
      VoipPushNotification.onVoipNotificationCompleted(notification.uuid);
    });

    VoipPushNotification.addEventListener("didLoadWithEvents", (events) => {
      const type = events.length != 0 && events[1]?.data?.type
      if (type === "Video") {
       
        Incomingvideocall.endAllCall();
        navigation.navigate('VideoScreen', {
          appointment: Object.assign({}, events.length != 0 ? events[1]?.data : {}),
        });

        const incomingCallAnswer = ({ callUUID }) => {
          navigation.navigate('VideoScreen', {
            appointment: Object.assign({}, events),
          });
          Incomingvideocall.endAllCall();
          Incomingvideocall.endIncomingcallAnswer(callUUID);
        };

        const endIncomingCall = () => {
          Incomingvideocall.endAllCall();
        };

        Incomingvideocall.configure(incomingCallAnswer, endIncomingCall);
      } else if (type === "DISCONNECTED") {
        Incomingvideocall.endAllCall();
      }

    });

    return () => {
      VoipPushNotification.removeEventListener("didLoadWithEvents");
      VoipPushNotification.removeEventListener("register");
      VoipPushNotification.removeEventListener("notification");
    };
  }, []);
  return null;
};
export default NotificationController;

package.json依赖部分

"dependencies": {
    "@react-native-firebase/app": "^16.4.6",
    "@react-native-firebase/messaging": "^16.4.6",
    "react": "18.2.0",
    "react-native": "0.71.3",
    "react-native-callkeep": "^4.3.12",
    "react-native-voip-push-notification": "^3.3.2",
    "react-native-webrtc": "^1.94.0"
  },

注意事项

  • Android端运行正常;
  • 在iPhone 8、iPhone 6及iPhone SE上正常工作;
  • 在iPhone 12上出现异常;
  • 所有状态下均可收到来电,但Killed/Background状态下接听后应用无法启动。

内容的提问来源于stack exchange,提问作者Shivo'ham

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 10:19:51