React Native + Expo 推送通知完整指南

目录

  1. 概述
  2. 技术架构
  3. Android推送实现
  4. iOS推送实现
  5. 推送服务实现
  6. 数据格式和解析
  7. 权限管理
  8. 设备兼容性
  9. 测试和调试
  10. 最佳实践

概述

本项目使用 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证书
  1. 登录 Apple Developer账号
  2. 前往 Certificates, Identifiers & Profiles
  3. 创建 APNs Key (.p8文件)
  4. 下载并保存到项目中
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,
  },
});

总结

推送通知是移动应用的核心功能之一,本指南涵盖了:

  1. 完整实现: Android (FCM) 和 iOS (APNs) 的完整实现
  2. 权限管理: 通知权限的请求和管理
  3. 数据解析: 跨平台推送数据的统一解析
  4. 设备兼容: OPPO、三星等特殊设备的适配
  5. 测试调试: 完整的测试和调试工具
  6. 最佳实践: 生产环境的最佳实践建议

通过遵循本指南,可以实现一个稳定、可靠的跨平台推送通知系统。


注意: 本文档中的敏感信息(如项目ID、API Key等)已脱敏处理,实际使用时请替换为真实配置。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐