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.json里extra.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连接测试设备,查看实时日志:
- 打开Xcode,选择
Window > Devices and Simulators - 连接你的测试设备,选中设备后点击
Open Console - 安装TestFlight的APP,触发令牌注册逻辑,查看日志里的错误信息
这样就能精准定位到是哪一步出了问题啦!
内容来源于stack exchange




