Expo 推送通知实战指南:expo-notifications 全解析与常见坑
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
在 React Native 里做推送,几乎每个团队都绕不开这几件事:权限弹窗被用户随手点了拒绝、拿到的令牌不知道往哪儿存、Android 和 iOS 行为对不上、应用在前台时通知表现又不一样。这篇文章不追求"从零到精通"的宏大叙事,只解决一个具体问题——如何把一条消息可靠地推到用户设备上,并在用户点击后跳到对应页面。
推送通知的工作链路:令牌、EAS 与前台后台
动手写代码前,先把整条链路看清楚,后面写代码心里才有数。
推送的核心载体是设备令牌(ExpoPushToken)。每台安装了 App 的设备都会向系统申请一个推送令牌:iOS 找 APNs,Android 找 FCM。Expo 的做法是用expo-notifications把这两套体系封装成同一个接口,你拿到的是一串ExponentPushToken[...]格式的统一令牌,格式里还带了平台前缀(如device1://、device2://),服务端不需要关心底下走的是哪条通道。
消息的送达由EAS(Expo Application Services)推送服务负责。你的后端只需把令牌和消息内容 POST 给 Expo 推送服务(https://exp.host/--/api/v2/push/send),EAS 会解析令牌前缀,路由到 FCM 或 APNs,最终把通知交给系统展示。也就是说你不需要自建推送服务器,也不用自己管理 Firebase 和 Apple 证书的细节。
至于前后台表现不同,原因在系统层:应用处于后台或关闭状态时,通知直接交给系统渲染,展示什么、响不响铃由系统按令牌绑定的凭据决定;应用在前台时,系统把事件"递"给你的 JS 代码,由setNotificationHandler里的策略决定要不要弹横幅、响铃、改角标。所以推送代码天然是两条路径,这也是后面要注册两种监听器的原因。
把一条用户消息推送到 App 并支持点击跳转
下面用一个具体场景串起完整链路:服务器推送一条新消息,用户点击后进入消息详情。
先装依赖并注册插件
用官方安装命令引入expo-notifications和expo-constants(后者用来读projectId):
npx expo install expo-notifications expo-constants然后在app.json的expo.plugins数组里加上"expo-notifications",配置插件会替你处理两端原生工程里的通知相关设置,不用手改 AndroidManifest 或 Info.plist。
如何申请权限并拿到推送令牌
权限和令牌是同一件事的两半:没有权限就拿不到令牌。Android 上先建好通知渠道,再检查/请求权限,最后生成令牌:
async function registerForPushNotificationsAsync() { // Android 8.0+ 必须有渠道,否则通知会被静默归到默认渠道 if (Platform.OS === 'android') { await Notifications.setNotificationChannelAsync('default', { name: 'default', importance: Notifications.AndroidImportance.MAX, }); } // 先查状态再弹窗,避免重复打扰用户 const { status } = await Notifications.getPermissionsAsync(); if (status !== 'granted') { await Notifications.requestPermissionsAsync(); } // 令牌绑定 projectId,换账号、改名都不会变 const projectId = Constants.expoConfig?.extra?.eas?.projectId ?? Constants.easConfig?.projectId; return (await Notifications.getExpoPushTokenAsync({ projectId })).data; }拿到令牌后别只打个日志就丢了——把它随用户账号存到你的后端数据库,这样"给某个用户发消息"才有目标可寻。本地存储只能当缓存,不能作为唯一来源。
发送一条测试通知
开发阶段最简单的验证方式是直接调 Expo 推送接口,把令牌填进to字段:
await fetch('https://exp.host/--/api/v2/push/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ to: expoPushToken, sound: 'default', title: '一条新消息', body: '点击查看详情', data: { screen: 'message-detail', id: '42' }, // 点击后要用到的跳转信息 }), });关键是data字段:它不会展示在通知上,但会原样随通知下发,是你做点击跳转的"暗号"。也可以用 Expo 的推送工具(expo.dev/notifications)网页版发测试,适合没有后端接口的纯调试阶段。
监听前台通知与点击事件
两个监听器各管一段:addNotificationReceivedListener在应用前台收到通知时触发,让你更新 UI 或做静默处理;addNotificationResponseReceivedListener在用户点击通知时触发,是跳转的入口:
useEffect(() => { // 前台收到通知时的策略:弹横幅但不响铃 Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowBanner: true, shouldShowList: true, shouldPlaySound: false, shouldSetBadge: true, }), }); const onReceive = Notifications.addNotificationReceivedListener(n => setMessages(prev => [n, ...prev]) ); const onClick = Notifications.addNotificationResponseReceivedListener(r => { const { screen, id } = r.notification.request.content.data; if (screen === 'message-detail') { router.push('/message-detail', { id }); // 用 data 里的信息做深度链接 } }); return () => { onReceive.remove(); onClick.remove(); }; }, []);点击监听器在应用冷启动时被点开的通知同样会触发,所以在入口组件注册它,跳转链路就完整了。
真实项目里最容易踩的四个坑
权限被拒绝。现象:getPermissionsAsync()返回denied,后续所有令牌逻辑短路。排查:确认自己调的是requestPermissionsAsync()而不是重复弹窗;权限一旦被拒,系统不会再次弹出询问,需要引导用户去系统设置里手动打开(iOS 上还可以用expo-application打开设置页)。
Android 通知渠道差异。现象:通知能收到,但无声无震动,甚至归到"其他"渠道。排查:Android 8.0+ 下通知行为完全由渠道决定,用setNotificationChannelAsync显式建渠道并设置importance;注意渠道属性创建后用户可在系统设置里覆盖,别假设MAX一定响。
模拟器上收不到推送。现象:真机正常,模拟器死活等不到通知。排查:推送需要真实凭据链路,Expo 官方明确不支持在纯 Expo Go 模拟器环境测试——要么用带 Google Play 服务的 Android 模拟器 / Xcode 14+ 的 iOS 模拟器,要么直接用eas build出的开发构建跑真机,这是最省事的路线。
projectId 缺失或错配。现象:调getExpoPushTokenAsync报 "Project ID not found",或令牌发出去石沉大海。排查:projectId是令牌归属项目的 UUID,用expo-constants从配置里读(见上面的注册函数);如果手动配置,确认app.json里extra.eas.projectId与 EAS 后台一致。
下一步可以走的方向
跑通这条链路后,三个方向值得排进迭代:自定义通知声音(把音频文件放进assets后在 payload 的sound字段引用)、更精细的深度链接(在data里传完整路由参数)、以及 iOS 角标管理(setBadgeCountAsync控制未读数)。
延伸阅读建议直接看仓库里的官方文档:
- 推送通知总览
- 推送通知完整配置
- 接收与处理通知
- 从服务端发送通知
- FAQ 与故障排查
仓库里的 apps/notification-tester/ 是一个完整的通知测试工程,从令牌注册、任务注册到通知分类一应俱全,翻它的源码比自己造轮子快得多。
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考