You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

iOS TestFlight构建无法获取Expo推送通知令牌(Expo Go环境正常)

iOS TestFlight构建无法获取Expo推送通知令牌(Expo Go环境正常)

我太懂这种 frustration 了——Expo Go里测试推送一切顺畅,令牌拿的稳稳的,结果到了TestFlight的生产包就掉链子,排查了一堆配置还是没头绪。结合你给出的代码和配置,我帮你梳理几个容易被忽略的关键点,试试能不能解决问题:

先复盘你的核心代码与配置

令牌注册函数

import { useState, useEffect, useRef } from 'react';
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import { Platform, Alert } from 'react-native';
import Constants from 'expo-constants';

function handleRegistrationError(errorMessage: string) {
  throw new Error(`Push notification registration failed: ${errorMessage}`);
}

export async function registerForPushNotificationsAsync() {
  if (Platform.OS === 'android') {
    Notifications.setNotificationChannelAsync('default', {
      name: 'default',
      importance: Notifications.AndroidImportance.MAX,
      vibrationPattern: [0, 250, 250, 250],
      lightColor: '#FF231F7C',
    });
  }

  if (Device.isDevice) {
    const { status: existingStatus } = await Notifications.getPermissionsAsync();
    let finalStatus = existingStatus;
    if (existingStatus !== 'granted') {
      const { status } = await Notifications.requestPermissionsAsync();
      finalStatus = status;
    }
    if (finalStatus !== 'granted') {
      handleRegistrationError(
        'Failed to get push token for push notification!'
      );
      return;
    }

    const projectId = Constants?.expoConfig?.extra?.eas?.projectId ?? Constants?.easConfig?.projectId;
    if (!projectId) {
      handleRegistrationError(
        'Project ID is not defined in the Expo configuration.'
      );
    }

    try {
      const token = (await Notifications.getExpoPushTokenAsync({
        projectId,
      })).data;
      console.log('Push notification token:', token);
      return token;
    } catch (error) {
      handleRegistrationError(
        `Failed to get push token: ${error instanceof Error ? error.message : String(error)}`
      );
    }
  }
}

你的app.json配置

{
  "expo": {
    "name": "test",
    "slug": "test",
    "version": "1.0.7",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "scheme": "app",
    "userInterfaceStyle": "automatic",
    "newArchEnabled": true,
    "ios": {
      "supportsTablet": false,
      "bundleIdentifier": "com.test.mynewapp",
      "entitlements": {
        "com.apple.developer.push-notifications": true
      },
      "infoPlist": {
        "ITSAppUsesNonExemptEncryption": false,
        "UIDeviceFamily": [1],
        "NSCameraUsageDescription": "This app does not use the camera. This is required for compliance due to included libraries."
      }
    },
    "android": {
      "icon": "./assets/icon.png",
      "adaptiveIcon": {
        "foregroundImage": "./assets/images/adaptive-icon.png",
        "backgroundColor": "#ffffff"
      },
      "edgeToEdgeEnabled": true,
      "package": "com.test.mynewapp"
    },
    "web": {
      "bundler": "metro",
      "output": "static",
      "favicon": "./assets/images/favicon.png"
    },
    "plugins": [
      "expo-router",
      [
        "expo-splash-screen",
        {
          "image": "./assets/icon.png",
          "imageWidth": 200,
          "resizeMode": "contain",
          "backgroundColor": "#ffffff"
        }
      ],
      [
        "expo-notifications",
        {
          "enableBackgroundRemoteNotifications": true
        }
      ]
    ],
    "experiments": {
      "typedRoutes": true
    },
    "extra": {
      "router": {},
      "eas": {
        "projectId": "8e56511a-ec55-7343-v31f-63g111019c98"
      }
    }
  }
}

重点排查方向

1. 给错误处理加可视化反馈,定位具体问题

你现在的handleRegistrationError是直接抛出错误,在生产环境中这个错误可能不会被你看到(TestFlight的控制台日志需要额外工具查看)。建议修改成弹出提示或者打印更清晰的日志:

function handleRegistrationError(errorMessage: string) {
  console.error('[Push Notification Error]', errorMessage);
  Alert.alert('推送通知配置失败', errorMessage);
}

这样你在TestFlight的测试设备上就能直接看到是权限问题、Project ID缺失,还是令牌获取时的网络/服务问题。

2. 确认生产环境下Project ID是否正确获取

在生产构建中,Constants的结构可能和Expo Go略有不同。可以在获取projectId后加一行日志:

const projectId = Constants?.expoConfig?.extra?.eas?.projectId ?? Constants?.easConfig?.projectId;
console.log('[Push Notification] Project ID:', projectId); // 新增这行
if (!projectId) {
  handleRegistrationError(
    'Project ID is not defined in the Expo configuration.'
  );
}

然后通过Xcode的设备日志或者expo logs --device查看这个值是否正确。有时候EAS Build在生产环境中,Constants.easConfig.projectId是可靠的,但如果你的app.jsonextra.eas.projectId配置正确,也应该能拿到。

3. 重新校验Provisioning Profile的推送权限

虽然你已经确认Provisioning Profile有效,但有时候EAS生成的Profile可能没有正确包含推送权限的entitlement。建议:

  • 去EAS Credentials页面,删除现有的Provisioning Profile
  • 重新生成一个新的Profile,确保生成过程中选择了包含推送权限
  • 重新触发EAS Build,用新的Profile打包

4. 检查iOS权限请求的边界情况

在iOS生产环境中,用户可能不小心拒绝了权限,或者权限状态变成了denied。你的代码里只判断了finalStatus !== 'granted'就抛出错误,但可以加一层逻辑引导用户去设置里开启权限:

import { Linking } from 'react-native'; // 记得导入Linking

// ... 其他代码 ...

if (finalStatus !== 'granted') {
  Alert.alert(
    '需要推送权限',
    '请在设置中开启通知权限以接收推送',
    [{ text: '去设置', onPress: () => Linking.openSettings() }]
  );
  return;
}

5. 确认Push Key的有效性

再检查一遍EAS Credentials里的Push Key:

  • 是不是对应正确的Bundle ID?
  • Push Key有没有过期?(苹果的Push Key有效期是10年,但如果中途撤销过就失效了)
  • 是不是把Push Key正确关联到了你的EAS项目?

最后一步:用TestFlight的设备日志排查

如果以上都没问题,建议用Xcode连接测试设备,查看实时日志:

  1. 打开Xcode,选择Window > Devices and Simulators
  2. 连接你的测试设备,选中设备后点击Open Console
  3. 安装TestFlight的APP,触发令牌注册逻辑,查看日志里的错误信息

这样就能精准定位到是哪一步出了问题啦!

内容来源于stack exchange

火山引擎 最新活动