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
Expo 通知基于expo-notifications模块,在 React Native 项目内同时覆盖 Android 与 iOS 的本地通知和远程推送。本文给出一套可落地的实践路径:从依赖安装、app.json插件配置、权限请求、Expo 推送令牌获取,到 FCM 与 APNs 凭据配置、EAS Build 构建、前/后台与关闭状态的行为控制,以及基于data字段的深度链接。
能力边界:本地通知与远程推送的差异
expo-notifications把两套能力装进同一套 API:
- 本地通知:由应用进程自行调度,依赖
expo-notify之外的定时或事件触发,不需要推送令牌,应用被系统终止后无法继续投递。 - 远程推送:消息从服务端经 FCM 或 APNs 下发到设备,应用可以不在前台,甚至处于关闭状态;需要 Expo 推送令牌(
ExponentPushToken[...]格式)作为投递地址。
应用不同状态下的行为差异(状态矩阵):
| 应用状态 | Android | iOS |
|---|---|---|
| 前台 | 系统横幅 + 触发addNotificationReceivedListener | 默认无横幅、无声音,仅触发addNotificationReceivedListener;setNotificationHandler可开启横幅与声音 |
| 后台 | 系统横幅 + 通知中心 | 系统横幅 + 通知中心 |
| 已关闭 | 系统投递到通知中心,点击触发addNotificationResponseReceivedListener | 系统直接投递到锁屏与通知中心,点击后冷启动应用并触发响应监听器 |
统一 API 覆盖了setNotificationHandler、requestPermissionsAsync、getExpoPushTokenAsync、setNotificationChannelAsync等调用,上层代码无需区分平台;只有凭据准备和渠道行为仍需按平台处理。
跑通第一条推送通知:依赖安装、插件配置与最小代码
在一个现成的 Expo 应用里,先用以下命令补齐三个依赖:
npx expo install expo-notifications expo-device expo-constantsexpo-notifications负责通知的注册、调度与展示,expo-device用于区分真机与模拟器,expo-constants用来读取projectId。接着在app.json的expo.plugins中加入"expo-notifications",prebuild 时原生侧的通知配置会被自动写入,不需要手工改AndroidManifest.xml或 iOS 的Entitlements文件。
最小可运行的注册与发送逻辑如下,包含权限请求、Expo 推送令牌获取、监听器和通过 Expo 推送接口发送测试消息:
import { useEffect, useState } from 'react'; import { View, Text, Button } from 'react-native'; import * as Device from 'expo-device'; import * as Notifications from 'expo-notifications'; import Constants from 'expo-constants'; Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowBanner: true, shouldShowList: true, shouldPlaySound: true, shouldSetBadge: true, }), }); async function getToken() { const { status } = await Notifications.getPermissionsAsync(); if (status !== 'granted') { const r = await Notifications.requestPermissionsAsync(); if (r.status !== 'granted') return null; } const projectId = Constants.expoConfig?.extra?.eas?.projectId ?? Constants.easConfig?.projectId; const token = await Notifications.getExpoPushTokenAsync({ projectId }); return token.data; } async function send(token) { await fetch('https://exp.host/--/api/v2/push/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ to: token, title: '测试标题', body: '来自 Expo 推送的第一条通知', data: { screen: 'home' }, }), }); } export default function App() { const [token, setToken] = useState(''); const [last, setLast] = useState(null); useEffect(() => { if (!Device.isDevice) return; getToken().then(setToken); const onReceive = Notifications.addNotificationReceivedListener( (n) => setLast(n) ); const onTap = Notifications.addNotificationResponseReceivedListener( (r) => console.log('clicked', r) ); return () => { onReceive.remove(); onTap.remove(); }; }, []); return ( <View style={{ flex: 1, justifyContent: 'center', padding: 24 }}> <Text>{token}</Text> <Text>{last?.request.content.body}</Text> <Button title="发送测试通知" onPress={() => send(token)} disabled={!token} /> </View> ); }验证方式:在真机上运行应用,确认界面打印出ExponentPushToken[...],随后点按按钮触发发送。前台会走handleNotification逻辑弹出横幅;把应用切到后台再发送,则走系统横幅与通知中心。
生产部署:FCM 与 APNs 凭据配置和 EAS Build 构建
Android 侧走 Firebase Cloud Messaging:先在 Firebase 控制台创建项目,拿到google-services.json,再通过eas credentials把该文件上传为项目的 Android 凭据。构建脚本参考 FCM 凭据官方配置文档。
iOS 侧走 APNs:在 Apple Developer 账号下为应用标识开启 Push Notifications,导出.p8格式的 APNs 密钥,同样通过eas credentials配置为项目的 iOS 凭据,或写入eas.json的构建配置中。
两侧凭据就绪后执行eas build生成包含原生推送配置的应用包。需要注意两个限制:Expo 推送令牌只能在真机上获取,模拟器不产生有效令牌;推送通道的完整验证(前后台投递、锁屏展示)也必须在真机完成,模拟器上不会收到远程推送。
通知生命周期:从发起到点击,监听器与深度链接
按时间线拆解一次推送的完整过程:
- 发起:服务端
POST到https://exp.host/--/api/v2/push/send,payload 至少包含to(推送令牌)、title、body,可选sound、badge与自定义data对象。 - 到达前台:系统把消息交给应用,
Notifications.setNotificationHandler返回的布尔值决定shouldShowBanner、shouldShowList、shouldPlaySound、shouldSetBadge;同时addNotificationReceivedListener回调,参数notification.request.content包含title、body、data。 - 后台/关闭:系统直接渲染横幅或锁屏通知,JS 侧不执行任何代码;用户点击后,
addNotificationResponseReceivedListener被触发(应用已运行)或先冷启动再触发。 - 点击响应:响应对象的
notification.request.content.data携带发起时写入的自定义数据,是深度链接的入口:
Notifications.addNotificationResponseReceivedListener((response) => { const { screen, id } = response.notification.request.content.data ?? {}; if (screen) router.navigate(screen, { id }); });应用从杀死状态被点击拉起时,getLastNotificationResponseAsync会返回最近一次点击的通知,用它可以在冷启动路径上恢复同样的页面跳转。
排错清单:通知不显示、令牌失败与渠道问题
通知不显示原因:系统级通知开关关闭、权限状态非granted、Android 渠道未创建,或发送请求实际返回了错误。 解决:核对系统设置与getPermissionsAsync返回值;在 Android 上先执行setNotificationChannelAsync;打印fetch响应体确认推送接口返回data: []而非错误对象。
令牌获取失败原因:在模拟器上调用getExpoPushTokenAsync,或未传入正确的projectId。 解决:确认Device.isDevice为true再执行注册流程;projectId从Constants.expoConfig?.extra?.eas?.projectId或Constants.easConfig?.projectId读取,两者都缺失时检查eas.json与app.json的关联配置。
Android 通知不震动或静默送达原因:渠道的AndroidImportance设置过低,或根本未创建渠道。 解决:显式调用setNotificationChannelAsync并设置importance(如MAX),必要时配置vibrationPattern与lightColor;Android 8.0 及以上所有通知都必须归属某个渠道。
iOS 始终收不到远程推送原因:APNs 证书未配置、证书与应用签名 Bundle ID 不一致。 解决:重新核对eas credentials中上传的 APNs 密钥,确认其对应的 App ID 与构建包签名完全一致。
点击通知无法跳转到目标页原因:data字段未写入,或冷启动路径未处理。 解决:发送时始终携带data;冷启动分支里补一次getLastNotificationResponseAsync检查。
参考文档
更完整的平台差异、凭据细节与边界情况,见 推送通知设置文档、接收通知文档 与 推送通知 FAQ。
【免费下载链接】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),仅供参考