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

React Native iOS Live Activity更新间歇性失效问题排查

问题描述

我正在开发一款使用Live Activities的React Native iOS应用,通过Objective-C/Swift原生模块对接iOS底层API实现该功能,Live Activity的更新由APNs推送通知触发。目前遇到间歇性更新失效问题:有时推送能正常触发更新并展示新信息,有时设备已收到推送(可通过控制台日志或测试工具确认),但Live Activity无变化,且失效无固定规律。

已完成的排查操作

  • APNs配置验证:反复检查配置描述文件、证书及推送payload,payload包含aps字典且content-available设为1,测试时添加了alert字段,确认设备能稳定接收推送。
  • 原生模块代码检查:全面审查处理Live Activity更新的代码,使用Activity.update更新活动属性,日志显示即使更新失效时该函数也会被调用,问题可能出在Activity.update调用本身或更新时机。
  • 后台模式配置:在Xcode中开启「Background Modes」能力,勾选「Background fetch」和「Remote notifications」选项。
  • 多设备/版本测试:在不同物理设备、模拟器及iOS版本上测试,间歇性失效问题仍存在。

相关代码

JavaScript(React Native 推送处理)

messaging().setBackgroundMessageHandler(async (remoteMessage) => {
  console.log('Notification.js: Your message was handled in background');
  if (DynamicIslandModule && Platform.OS === "ios" && Platform.Version >= 16.1) {
    console.log("Notification.js true", remoteMessage);
    console.log(DynamicIslandModule, 'DynamicIslandModule');
    console.log(remoteMessage?.data?.order, 'order id from background notification');

    if (["void", "cancel", "bad", "complete"].includes(remoteMessage?.data?.title.toLowerCase())) {
      await DynamicIslandModule.endNotificationActivity();
    } else {
      await DynamicIslandModule.updateNotificationActivityWithOrderStatus(
        remoteMessage?.data?.message,
        remoteMessage?.data?.order
      );
    }
  }
  if (remoteMessage?.data?.notificationId) {
    console.log('Your message was handled in background');
    let notificationId = remoteMessage?.data?.notificationId;
    await store.getLastSavedToken();
    await singleton.markNotificationReceived({ _id: notificationId });
    console.log(
      'Message handled in the background!',
      remoteMessage?.data?.notificationId
    );
  }
});

Swift(原生模块更新逻辑)

//DynamicIslandModule.swift

@objc(updateNotificationActivityWithOrderStatus:withOrderID:withOrderMessage:withTitle:withResolve:withReject:)
func updateNotificationActivity(
    orderStatus: NSString,
    orderID: NSString,
    orderMessage: NSString,
    title: NSString,
    resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
) {
    let status = orderStatus as String
    let orderIDString = orderID as String

    guard let storedIDs = UserDefaults.standard.array(forKey: "liveActivityIDs") as? [String],
          storedIDs.contains(orderIDString) else {
        print("ERROR: Live Activity ID not found in UserDefaults")
        return
    }

    guard let liveActivity = Activity<NotificationAttributes>.activities.first(where: { activity in
        if let contentState = activity.contentState as? NotificationAttributes.ContentState {
            return contentState.orderID == orderIDString
        }
        return false
    }) else {
        print("ERROR: Live Activity not found for ID: \(orderIDString)")
        return
    }

    guard let currentContentState = liveActivity.contentState as? NotificationAttributes.ContentState else {
        print("ERROR: Failed to retrieve current content state")
        return
    }

    let updatedContentState = NotificationAttributes.ContentState(
        orderStatus: status,
        orderID: orderIDString,
        pickupLocation: currentContentState.pickupLocation,
        dropoffLocation: currentContentState.dropoffLocation,
        serviceMethod: currentContentState.serviceMethod,
        orderMessage: orderMessage as String,
        title: title as String
    )

    if #available(iOS 16.1, *) {
        print("Updating Live Activity...")
        Task {
            do {
                try await liveActivity.update(using: updatedContentState)
                resolve("Live Activity updated successfully for order #\(orderIDString)")
                print("Live Activity updated successfully for order #\(orderIDString) status:\(status)")
            } catch let error {
                reject("ERROR", "Failed to update live activity", error)
                print("Error updating live activity: \(error.localizedDescription)")
            }
        }
    } else {
        reject("ERROR", "iOS version not supported", nil)
    }
}

Objective-C(AppDelegate 推送回调)

//AppDelegate.mm

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    if (@available(iOS 16.1, *)) {
        NSString *orderID = userInfo[@"data"][@"order"];
        NSString *orderMessage = userInfo[@"data"][@"message"];
        NSString *orderStatus = userInfo[@"data"][@"status"];
        NSString *orderTitle = userInfo[@"data"][@"title"];
        NSLog(@"didReceiveRemoteNotification triggered");

        NSArray *endTitles = @[@"complete", @"void", @"bad", @"cancel"];

        if ([endTitles containsObject:orderStatus.lowercaseString]) {
            [LiveActivityHelper endActivityWithIdWithOrderID:orderID];
        } else {
            [LiveActivityHelper updateActivityWithOrderID:orderID orderStatus:orderStatus orderMessage:orderMessage title:orderTitle];
        }

        completionHandler(UIBackgroundFetchResultNewData);
    } else {
        completionHandler(UIBackgroundFetchResultNewData);
    }
}

问题分析与解决方案

一、可能的失效原因

1. APNs Payload 未使用Live Activity专属格式

当前使用普通后台推送(content-available:1)触发应用唤醒后更新Live Activity,但这种方式依赖系统给应用分配后台执行时间,系统会根据电池、应用活跃度等因素限制或延迟唤醒,导致更新不稳定。Live Activity有专属的更新推送格式,由系统直接处理更新,无需应用唤醒,可靠性更高。

2. 后台异步任务被系统终止

在didReceiveRemoteNotification或React Native的后台消息处理器中,用Task异步执行Activity.update,但应用后台执行窗口通常只有几秒,若异步任务未完成就被系统回收,更新会失败。

3. Activity实例查询存在竞态

依赖Activity.activities.first(where:)查询实例时,可能出现推送到达时Activity已被系统回收(如超过8小时生命周期)、状态未同步的情况,导致找不到有效实例。

4. 推送逻辑重复处理

同时在React Native的BackgroundMessageHandler和原生didReceiveRemoteNotification中处理推送,可能引发重复更新或状态覆盖,导致Live Activity显示异常。

二、关键注意事项

1. 必须使用Live Activity专属APNs Payload

正确的更新推送payload需包含aps下的activity字段,示例:

{
  "aps": {
    "activity": {
      "identifier": "YOUR_ACTIVITY_ID",
      "updates": [
        {
          "timestamp": 1690000000,
          "content-state": {
            "orderStatus": "preparing",
            "orderID": "12345",
            "orderMessage": "Your order is being prepared"
          }
        }
      ]
    },
    "content-available": 1
  }
}

这种推送由系统直接更新Live Activity,不依赖应用唤醒,稳定性大幅提升。

2. 后台执行避免异步延迟

在didReceiveRemoteNotification中,确保更新逻辑在completionHandler调用前完成;若必须异步执行,需用BGTaskScheduler申请额外后台执行时间,避免被系统终止。

3. 正确管理Activity生命周期

  • 不要依赖UserDefaults存储的Activity ID,每次更新前通过Activity.activities实时查询,并确认Activity的state为.active。
  • Live Activity默认生命周期为8小时,超过后会自动转为.ended,此时无法更新,需提前处理结束逻辑。

4. 统一推送处理入口

选择原生层(AppDelegate)或React Native层处理推送,避免重复逻辑,建议在原生层处理Live Activity更新,减少跨层调用的不确定性。

三、进一步调试建议

  1. 添加详细状态日志:查询Activity时,打印所有Activity.activities的identifier、state和contentState,确认更新时Activity是否处于活跃状态,以及查询是否命中正确实例。
  2. 捕获完整错误信息:将Activity.update的错误打印从localizedDescription改为完整的error对象(包括code和userInfo),iOS系统会返回具体失败原因(如Activity已结束、权限不足等)。
  3. 测试专属推送格式:用Apple官方工具发送符合Live Activity格式的推送,对比普通推送的成功率。
  4. 监控后台执行时长:在didReceiveRemoteNotification中记录开始和结束时间,用ProcessInfo.processInfo.systemUptime计算执行时长,确认是否因系统限制导致任务未完成。
  5. 监听Activity状态变化:添加Activity.stateDidChange监听,记录Activity的状态变化,确认推送到达前Activity是否已被系统终止。

内容的提问来源于stack exchange,提问作者Zammad Nasir

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 14:05:59