React Native + Expo Android&iOS推送通知完整指南
·
React Native + Expo 推送通知完整指南
目录
概述
本项目使用 Expo Notifications API 实现跨平台推送通知功能,支持 Android (FCM) 和 iOS (APNs) 两个平台。
核心功能
- 远程推送: 支持服务器发送的远程推送通知
- 本地通知: 支持应用内生成的本地通知
- 锁屏显示: 支持在锁屏状态下显示通知
- 多渠道管理: Android支持多个通知渠道配置
- 数据解析: 自动解析推送数据并导航到对应页面
- 离线消息: 支持离线消息缓存和展示
技术架构
整体架构
┌─────────────────────────────────────┐
│ 推送通知服务架构 │
├─────────────────────────────────────┤
│ │
│ ┌────────────┐ ┌─────────────┐│
│ │ Firebase │ │ APNs ││
│ │ FCM │ │ ││
│ │ (Android) │ │ (iOS) ││
│ └─────┬──────┘ └──────┬──────┘│
│ │ │ │
│ └──────────┬───────┘ │
│ │ │
│ ┌──────────▼─────────┐ │
│ │ Expo Push Service │ │
│ └──────────┬─────────┘ │
│ │ │
│ ┌──────────▼─────────┐ │
│ │ Notification │ │
│ │ Service Layer │ │
│ └──────────┬─────────┘ │
│ │ │
│ ┌──────────▼─────────┐ │
│ │ Application │ │
│ │ (React Native) │ │
│ └────────────────────┘ │
│ │
└─────────────────────────────────────┘
关键组件
- NotificationService: 推送通知核心服务类
- Token管理: 设备Token的获取和管理
- 权限管理: 通知权限的请求和状态管理
- 数据解析: 推送数据的解析和处理
- 导航处理: 根据推送内容自动导航
Android推送实现
1. Firebase配置
google-services.json配置
{
"project_info": {
"project_id": "your-project-id",
"firebase_url": "https://your-project.firebaseio.com",
"project_number": "123456789000",
"storage_bucket": "your-project.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:123456789000:android:xxxxx",
"android_client_info": {
"package_name": "com.example.app"
}
},
"api_key": [
{
"current_key": "YOUR_ANDROID_API_KEY"
}
]
}
]
}
app.json配置
{
"expo": {
"android": {
"package": "com.example.app",
"googleServicesFile": "./google-services.json",
"permissions": [
"RECEIVE_BOOT_COMPLETED",
"VIBRATE",
"POST_NOTIFICATIONS"
]
},
"pushNotifications": {
"android": {
"senderId": "123456789000"
}
}
}
}
2. Android通知渠道
// 高优先级通知渠道
async createHighPriorityChannel() {
await Notifications.setNotificationChannelAsync('high-priority', {
name: '高优先级通知',
description: '重要的订单和消息通知',
importance: Notifications.AndroidImportance.HIGH,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
lockscreenVisibility: Notifications.AndroidNotificationVisibility.PUBLIC,
bypassDnd: true,
showBadge: true,
enableLights: true,
enableVibrate: true,
sound: 'default',
});
}
// 锁屏通知渠道
async createLockScreenChannel() {
await Notifications.setNotificationChannelAsync('lock-screen', {
name: '锁屏通知',
description: '在锁屏状态下显示的重要通知',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 500, 250, 500, 250, 500],
lightColor: '#FF231F7C',
lockscreenVisibility: Notifications.AndroidNotificationVisibility.PUBLIC,
bypassDnd: true,
showBadge: true,
enableLights: true,
enableVibrate: true,
sound: 'default',
});
}
3. Android权限请求
async requestAndroidPermissions() {
const { status } = await Notifications.requestPermissionsAsync({
android: {
allowAlert: true,
allowBadge: true,
allowSound: true,
allowVibrate: true,
allowLights: true,
allowShowWhenLocked: true, // 锁屏显示
allowDisplayOnLockScreen: true, // 锁屏上显示
},
});
return status === 'granted';
}
4. FCM Token获取
async getAndroidFCMToken() {
try {
// 检查Google Play Services
const isGooglePlayServicesAvailable = await this.checkGooglePlayServices();
if (!isGooglePlayServicesAvailable) {
console.log('Google Play Services不可用');
return null;
}
// 获取FCM Token
const fcmToken = await Notifications.getDevicePushTokenAsync();
if (fcmToken.data) {
console.log('FCM Token获取成功:', fcmToken.data);
return fcmToken.data;
}
return null;
} catch (error) {
console.error('获取FCM Token失败:', error);
return null;
}
}
iOS推送实现
1. APNs配置
生成APNs证书
- 登录 Apple Developer账号
- 前往 Certificates, Identifiers & Profiles
- 创建 APNs Key (.p8文件)
- 下载并保存到项目中
app.json配置
{
"expo": {
"ios": {
"bundleIdentifier": "com.example.app",
"buildNumber": "1.0.0",
"infoPlist": {
"NSRemoteNotificationUsageDescription": "需要推送权限以接收订单通知"
}
}
}
}
2. iOS权限请求
async requestIOSPermissions() {
const { status } = await Notifications.requestPermissionsAsync({
ios: {
allowAlert: true,
allowBadge: true,
allowSound: true,
allowDisplayInCarPlay: true,
allowCriticalAlerts: true,
provideAppNotificationSettings: true,
allowProvisional: false,
},
});
return status === 'granted';
}
3. APNs Token获取
async getIOSAPNsToken() {
try {
// 获取原生APNs Token
const apnsToken = await Notifications.getDevicePushTokenAsync();
if (apnsToken.data) {
// 验证和清理Token格式
const validatedToken = this.validateAndCleanIOSToken(apnsToken.data);
if (validatedToken) {
console.log('APNs Token获取成功:', validatedToken);
return validatedToken;
}
}
return null;
} catch (error) {
console.error('获取APNs Token失败:', error);
return null;
}
}
// Token验证和清理
validateAndCleanIOSToken(token: string): string | null {
if (!token) return null;
// 移除所有空格、连字符和特殊字符
const cleanedToken = token.replace(/[\s\-<>]/g, '');
// 检查是否为64位十六进制字符串
const hexPattern = /^[0-9a-fA-F]{64}$/;
if (hexPattern.test(cleanedToken)) {
return cleanedToken.toLowerCase();
}
console.error('iOS Token格式验证失败');
return null;
}
4. iOS通知数据格式
iOS推送通知的标准格式:
{
"aps": {
"alert": {
"title": "订单通知",
"body": "您有新的订单 #ORDER123"
},
"badge": 1,
"sound": "default"
},
"data": {
"orderId": "ORDER123",
"templateId": "1",
"messageType": "order"
}
}
推送服务实现
1. 核心服务类
class NotificationService {
private static instance: NotificationService;
private isInitialized = false;
private pushToken: string | null = null;
// 单例模式
static getInstance(): NotificationService {
if (!NotificationService.instance) {
NotificationService.instance = new NotificationService();
}
return NotificationService.instance;
}
// 初始化
async initialize(): Promise<boolean> {
try {
// 1. 设置通知处理器
this.setupNotificationHandler();
// 2. 检查设备支持
if (!Device.isDevice) {
console.warn('推送通知只在真实设备上工作');
return false;
}
// 3. 请求权限
const permissionStatus = await this.requestPermissions();
if (permissionStatus !== 'granted') {
console.warn('推送通知权限未授予');
return false;
}
// 4. 获取Token
const token = await this.getPushToken();
if (!token) {
console.error('无法获取推送token');
return false;
}
// 5. 注册到服务器
await this.registerTokenToServer(token);
// 6. 设置监听器
this.setupNotificationListeners();
this.isInitialized = true;
return true;
} catch (error) {
console.error('推送通知服务初始化失败:', error);
return false;
}
}
}
2. 通知处理器
// 设置通知处理器
setupNotificationHandler() {
Notifications.setNotificationHandler({
handleNotification: async (notification) => {
// Android高优先级通知渠道
if (Platform.OS === 'android') {
await this.createHighPriorityChannel();
}
return {
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
shouldShowBanner: true,
shouldShowList: true,
priority: Platform.OS === 'android'
? Notifications.AndroidNotificationPriority.HIGH
: undefined,
};
},
});
}
3. 通知监听
// 设置通知监听器
setupNotificationListeners() {
// 监听收到的通知
Notifications.addNotificationReceivedListener((notification) => {
console.log('收到推送通知:', notification);
this.handleNotificationReceived(notification);
});
// 监听用户点击通知
Notifications.addNotificationResponseReceivedListener((response) => {
console.log('用户点击通知:', response);
this.handleNotificationResponse(response);
});
}
数据格式和解析
1. 推送数据格式
Android数据格式
{
"to": "FCM_TOKEN",
"notification": {
"title": "订单通知",
"body": "您有新的订单"
},
"data": {
"orderId": "ORDER123",
"templateId": "1",
"messageType": "order",
"url": "/ride-detail"
}
}
iOS数据格式
{
"aps": {
"alert": {
"title": "订单通知",
"body": "您有新的订单 #ORDER123"
},
"badge": 1,
"sound": "default"
},
"data": {
"orderId": "ORDER123",
"templateId": "1",
"messageType": "order",
"url": "/ride-detail"
}
}
2. 数据解析实现
// 跨平台数据解析
debugNotificationDataParsing(rawData: any, platform: 'ios' | 'android') {
let parsedData = {};
if (platform === 'ios') {
// iOS数据解析
if (rawData.data && typeof rawData.data === 'object') {
// 从data字段解析
parsedData = {
messageId: rawData.data.messageId,
orderId: rawData.data.orderId,
templateId: rawData.data.templateId,
messageType: rawData.data.messageType,
type: rawData.data.type,
url: rawData.data.url,
};
}
// iOS特殊处理:从body中提取orderId
if (!parsedData.orderId && rawData.aps?.alert?.body) {
const bodyOrderId = this.extractOrderIdFromBody(rawData.aps.alert.body);
if (bodyOrderId) {
parsedData.orderId = bodyOrderId;
}
}
} else {
// Android数据解析
parsedData = {
messageId: rawData.messageId || rawData.message_id,
orderId: rawData.orderId || rawData.order_id,
templateId: rawData.templateId || rawData.template_id,
messageType: rawData.messageType || rawData.message_type,
type: rawData.type,
url: rawData.url,
};
// 如果顶层没有,从data字段获取
if (!parsedData.orderId && rawData.data) {
parsedData.orderId = rawData.data.orderId;
parsedData.templateId = rawData.data.templateId;
}
}
return { parsedData };
}
3. iOS Body解析
// 从iOS通知body中提取orderId
extractOrderIdFromBody(body: string): string | null {
if (!body) return null;
// 匹配 #开始到数字结尾的字符串
// 例如:#ORDER123 或 #CO20250929Q001104
const match = body.match(/#([^#]*\d+)/);
if (match && match[1]) {
return match[1]; // 不包含#号
}
return null;
}
权限管理
1. 权限状态检查
// 获取通知权限状态
async getPermissionStatus(): Promise<'granted' | 'denied' | 'undetermined'> {
try {
const { status } = await Notifications.getPermissionsAsync();
return status;
} catch (error) {
console.error('获取通知权限状态失败:', error);
return 'denied';
}
}
2. 请求权限
// 请求通知权限
async requestPermissions(): Promise<'granted' | 'denied' | 'undetermined'> {
try {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
if (Platform.OS === 'android') {
const { status } = await Notifications.requestPermissionsAsync({
android: {
allowAlert: true,
allowBadge: true,
allowSound: true,
allowVibrate: true,
allowLights: true,
allowShowWhenLocked: true,
allowDisplayOnLockScreen: true,
},
});
finalStatus = status;
} else {
const { status } = await Notifications.requestPermissionsAsync({
ios: {
allowAlert: true,
allowBadge: true,
allowSound: true,
allowDisplayInCarPlay: true,
allowCriticalAlerts: true,
provideAppNotificationSettings: true,
allowProvisional: false,
},
});
finalStatus = status;
}
}
// 为Android创建通知渠道
if (Platform.OS === 'android') {
await this.createHighPriorityChannel();
await this.createLockScreenChannel();
}
return finalStatus;
} catch (error) {
console.error('请求通知权限失败:', error);
return 'denied';
}
}
设备兼容性
1. Android特殊处理
OPPO设备优化
// OPPO设备检测
isOPPODevice(): boolean {
if (Platform.OS !== 'android') return false;
const brand = Device.brand?.toLowerCase();
return brand?.includes('oppo') || brand?.includes('realme');
}
// OPPO设备推送设置指引
showOPPONotificationGuide() {
Alert.alert(
'OPPO设备推送设置',
'为确保正常接收通知,请按以下步骤设置:\n\n' +
'1. 设置 > 通知 > 本应用 > 允许通知\n' +
'2. 设置 > 通知 > 锁屏通知 > 开启\n' +
'3. 设置 > 电池 > 电池优化 > 本应用 > 不优化\n' +
'4. 设置 > 应用管理 > 本应用 > 自启动 > 开启',
[
{ text: '知道了', style: 'cancel' },
{ text: '去设置', onPress: () => Linking.openSettings() }
]
);
}
三星设备优化
// 三星设备检测
isSamsungDevice(): boolean {
if (Platform.OS !== 'android') return false;
const brand = Device.brand?.toLowerCase();
return brand?.includes('samsung');
}
// 三星设备电池优化检查
async checkSamsungBatteryOptimization() {
const deviceBrand = Device.brand || 'Unknown';
const isSamsung = deviceBrand.toLowerCase().includes('samsung');
if (isSamsung) {
console.log('检测到三星设备,需要检查以下设置:');
console.log('1. 设置 > 电池 > 电池优化 > 选择"不优化"');
console.log('2. 设置 > 通知 > 允许通知 > 开启"在锁屏上显示"');
console.log('3. 设置 > 应用程序 > 电池 > 允许后台活动');
console.log('4. 设置 > 智能管理器 > 电池 > 应用程序省电 > 关闭');
}
}
2. iOS特殊处理
Release模式检测
// 检测是否为Release模式
isReleaseMode(): boolean {
const appEnv = process.env.APP_ENV;
const isReleaseBuild = __DEV__ === false;
return appEnv === 'production' || isReleaseBuild;
}
Token格式验证
// 验证iOS Token格式
validateIOSToken(token: string): boolean {
if (!token) return false;
// iOS APNs Token应该是64位十六进制字符串
const hexPattern = /^[0-9a-fA-F]{64}$/;
return hexPattern.test(token);
}
测试和调试
1. 本地通知测试
// 发送本地测试通知
async sendTestNotification() {
try {
// Android需要先创建通知渠道
if (Platform.OS === 'android') {
await this.createHighPriorityChannel();
}
await Notifications.scheduleNotificationAsync({
content: {
title: '测试通知',
body: '这是一条测试通知消息',
data: {
orderId: 'TEST123',
templateId: '1',
messageType: 'order'
},
sound: 'default',
priority: Platform.OS === 'android'
? Notifications.AndroidNotificationPriority.HIGH
: undefined,
},
trigger: null, // 立即发送
});
console.log('测试通知已发送');
} catch (error) {
console.error('发送测试通知失败:', error);
}
}
2. Token调试
// 获取并显示Token信息
async debugTokenInfo() {
const deviceInfo = await this.getDeviceInfo();
const tokenInfo = await this.getCachedTokenInfo();
console.log('设备信息:', {
平台: deviceInfo.platform,
设备类型: deviceInfo.deviceType,
设备名称: deviceInfo.deviceName,
系统版本: deviceInfo.osVersion,
应用版本: deviceInfo.appVersion,
});
console.log('Token信息:', {
Token类型: tokenInfo?.tokenType,
原生Token: tokenInfo?.nativeDeviceToken,
Expo Token: tokenInfo?.expoToken,
时间戳: tokenInfo?.timestamp,
});
}
3. 推送数据解析测试
// 测试推送数据解析
testNotificationDataParsing() {
const testData = {
aps: {
alert: {
title: '测试通知',
body: '订单 #ORDER123 已更新'
},
badge: 1
},
data: {
orderId: 'ORDER123',
templateId: '1'
}
};
const result = this.debugNotificationDataParsing(testData, 'ios');
console.log('解析结果:', result);
}
最佳实践
1. Token管理
- 缓存Token: 将获取的Token缓存到本地,避免频繁请求
- Token刷新: 应用启动时验证Token有效性,必要时刷新
- 多平台支持: 优先使用原生Token(FCM/APNs),Expo Token作为备选
2. 通知处理
- 优先级设置: 为重要通知设置高优先级
- 渠道管理: Android使用不同渠道管理不同类型的通知
- 锁屏显示: 确保通知可以在锁屏状态下显示
3. 数据解析
- 统一格式: 定义统一的推送数据格式
- 容错处理: 处理各种可能的数据结构
- 平台差异: 针对iOS和Android的数据格式差异做适配
4. 用户体验
- 及时反馈: 收到通知后及时给用户反馈
- 智能导航: 根据通知类型自动导航到相应页面
- 消息管理: 提供消息列表查看历史通知
5. 错误处理
- 权限检查: 使用前检查通知权限
- 降级方案: Token获取失败时的备选方案
- 日志记录: 记录关键操作和错误信息
6. 性能优化
- 批量处理: 批量处理多条通知
- 缓存优化: 合理使用缓存减少重复操作
- 内存管理: 及时清理不需要的通知数据
常见问题
1. Android通知不显示
可能原因:
- 通知权限未授予
- 通知渠道未创建或被禁用
- Google Play Services不可用
- 电池优化限制
解决方案:
// 检查并修复通知设置
async fixAndroidNotifications() {
// 1. 检查权限
const hasPermission = await this.requestPermissions();
if (!hasPermission) {
Alert.alert('需要通知权限', '请在设置中开启通知权限');
return;
}
// 2. 创建通知渠道
await this.createHighPriorityChannel();
await this.createLockScreenChannel();
// 3. 检查Google Play Services
const hasGPS = await this.checkGooglePlayServices();
if (!hasGPS) {
Alert.alert('需要Google Play Services', '请安装或更新Google Play Services');
return;
}
// 4. 提示用户检查电池优化
Alert.alert(
'通知设置',
'请在设置中将本应用添加到电池优化白名单',
[
{ text: '取消', style: 'cancel' },
{ text: '去设置', onPress: () => Linking.openSettings() }
]
);
}
2. iOS推送无法收到
可能原因:
- APNs证书配置错误
- Token格式不正确
- Development/Production环境不匹配
- iOS权限未授予
解决方案:
// 检查iOS推送配置
async debugIOSPush() {
// 1. 检查权限
const status = await this.getPermissionStatus();
console.log('iOS通知权限:', status);
// 2. 检查Token
const token = await this.getIOSAPNsToken();
if (!token) {
console.error('无法获取APNs Token');
return;
}
// 3. 验证Token格式
const isValid = this.validateIOSToken(token);
console.log('Token格式验证:', isValid ? '通过' : '失败');
// 4. 检查环境
const isRelease = this.isReleaseMode();
console.log('当前环境:', isRelease ? 'Release' : 'Development');
}
3. 锁屏不显示通知
Android:
// 确保通知渠道设置正确
await Notifications.setNotificationChannelAsync('lock-screen', {
name: '锁屏通知',
importance: Notifications.AndroidImportance.MAX,
lockscreenVisibility: Notifications.AndroidNotificationVisibility.PUBLIC,
bypassDnd: true,
});
iOS:
// 确保请求了正确的权限
const { status } = await Notifications.requestPermissionsAsync({
ios: {
allowAlert: true,
allowBadge: true,
allowSound: true,
allowCriticalAlerts: true,
},
});
总结
推送通知是移动应用的核心功能之一,本指南涵盖了:
- 完整实现: Android (FCM) 和 iOS (APNs) 的完整实现
- 权限管理: 通知权限的请求和管理
- 数据解析: 跨平台推送数据的统一解析
- 设备兼容: OPPO、三星等特殊设备的适配
- 测试调试: 完整的测试和调试工具
- 最佳实践: 生产环境的最佳实践建议
通过遵循本指南,可以实现一个稳定、可靠的跨平台推送通知系统。
注意: 本文档中的敏感信息(如项目ID、API Key等)已脱敏处理,实际使用时请替换为真实配置。
更多推荐


所有评论(0)