expo sdk53+ 集成极光推送消息推送 ios swift

Expo SDK 53+ 集成极光推送消息推送 iOS Swift

前言

在移动应用开发中,消息推送是提升用户体验和活跃度的重要功能。本文将分享如何在 Expo SDK 53+ 环境下成功集成极光推送(JPush)的 iOS Swift 版本,这是一个充满挑战但最终成功的技术实践。

背景故事

最近在开发一个 App 时,需要集成极光推送来实现实时消息通知。使用的技术栈是:

  • Expo SDK 53
  • React Native 0.79.5
  • React 19
  • 新架构(New Architecture)启用

在集成过程中遇到了一个棘手的问题:

  • 使用 jpush-react-native: 3.1.9jcore-react-native: 2.3.0
  • Android 端集成很顺利,按照官方文档即可完成
  • iOS 端集成失败 - 原因是极光官方提供的示例代码还停留在 Objective-C 时代,而新版 React Native 已经全面转向 Swift,当然也可以切换回 oc 但是我避他锋芒?/滑稽

解决方案概述

经过与极光推送技术团队的深入沟通,获得了他们提供的 React Native Swift 版本代码。然后通过 AI 辅助,将这些代码成功转换为 Expo Config Plugin 格式,最终实现了集成。

本文将详细记录整个集成过程,包括:

  1. Expo Config Plugin 的 Swift 代码注入
  2. 冷启动通知的处理机制
  3. 完整的事件监听体系
  4. 故障排查经验

环境准备

1. 安装依赖包

bash 复制代码
# 安装极光推送相关包
pnpm add jpush-react-native@3.1.9
pnpm add jcore-react-native@^2.3.0

2. 项目配置要求

确保你的 app.config.js 中启用了新架构:

javascript 复制代码
{
  newArchEnabled: true,
  ios: {
    supportsTablet: true,
    bundleIdentifier: 'com.your.app',
    infoPlist: {
      // 添加推送相关权限说明
      NSCameraUsageDescription: '需要相机权限用于视频通话',
      NSMicrophoneUsageDescription: '需要麦克风权限用于语音通话',
    }
  }
}

核心问题分析

为什么官方示例不能直接用?

  1. 语言差异:官方示例基于 Objective-C,新版 RN 使用 Swift
  2. 架构变化:RN 0.79+ 的新架构改变了原生模块的实现方式
  3. Expo 特殊性:Expo 需要通过 Config Plugin 注入原生代码

Android vs iOS 的差异

javascript 复制代码
// Android 端:配置相对简单,主要是 Gradle 配置
// iOS 端:需要处理 Swift/OC 混编、AppDelegate 修改、桥接头文件等

Expo Config Plugin 实现

完整的插件代码(plugins/jpush-react-native.js)

这是整个集成的核心,通过 Config Plugin 自动化修改原生代码:

javascript 复制代码
const {
  AndroidConfig,
  withAppBuildGradle,
  withSettingsGradle,
  withAndroidManifest,
  withAppDelegate,
  withXcodeProject,
  IOSConfig,
  withInfoPlist,
} = require('expo/config-plugins')

let JPUSH_APPKEY = 'appKey'
let JPUSH_CHANNEL = 'channel'

function withJPush(config, props) {
  if (!props || !props.appKey || !props.channel)
    throw new Error('[JPush] 请传入参数 appKey & channel')

  JPUSH_APPKEY = props.appKey
  JPUSH_CHANNEL = props.channel

  // Android 配置
  config = setAndroidManifest(config)
  config = setAppBuildGradle(config)
  config = setSettingsGradle(config)

  // iOS 配置 - 重点部分
  config = setInfoPlist(config)
  config = setAppDelegate(config)
  config = setiOSBridgingHeader(config)

  return config
}

// iOS AppDelegate 修改 - 注入 Swift 代码
function setAppDelegate(config) {
  return withAppDelegate(config, (config) => {
    console.log('\n[JPush] 配置 AppDelegate JPush 集成 ...')

    const appDelegateContent = config.modResults.contents

    // 1. 添加必要的导入
    if (!appDelegateContent.includes('import UserNotifications')) {
      config.modResults.contents = appDelegateContent.replace(
        /import React/,
        'import React\nimport UserNotifications'
      )
    }

    // 2. 添加 JPUSHRegisterDelegate 协议
    if (!appDelegateContent.includes('JPUSHRegisterDelegate')) {
      config.modResults.contents = config.modResults.contents.replace(
        /public class AppDelegate: ExpoAppDelegate/,
        'public class AppDelegate: ExpoAppDelegate, JPUSHRegisterDelegate'
      )
    }

    // 3. 在 didFinishLaunchingWithOptions 中添加 JPush 初始化
    if (!appDelegateContent.includes('JPUSHService.register')) {
      const jpushInit = `
    // JPush 注册配置
    let entity = JPUSHRegisterEntity()
    if #available(iOS 12.0, *) {
      entity.types = Int(UNAuthorizationOptions.alert.rawValue |
                        UNAuthorizationOptions.sound.rawValue |
                        UNAuthorizationOptions.badge.rawValue |
                        UNAuthorizationOptions.provisional.rawValue)
    } else {
      entity.types = Int(UNAuthorizationOptions.alert.rawValue |
                        UNAuthorizationOptions.sound.rawValue |
                        UNAuthorizationOptions.badge.rawValue)
    }
    JPUSHService.register(forRemoteNotificationConfig: entity, delegate: self)

    // 开启调试模式
    JPUSHService.setDebugMode()

    // 初始化 JPush
    JPUSHService.setup(withOption: launchOptions,
                       appKey: "${JPUSH_APPKEY}",
                       channel: "${JPUSH_CHANNEL}",
                       apsForProduction: false)

    // 监听自定义消息
    NotificationCenter.default.addObserver(
      self,
      selector: #selector(self.networkDidReceiveMessage(_:)),
      name: NSNotification.Name.jpfNetworkDidReceiveMessage,
      object: nil
    )
`

      config.modResults.contents = config.modResults.contents.replace(
        /(return super\.application\(application, didFinishLaunchingWithOptions: launchOptions\))/,
        `${jpushInit}\n    $1`
      )
    }

    // 4. 添加推送相关方法
    if (!appDelegateContent.includes('didRegisterForRemoteNotificationsWithDeviceToken')) {
      const pushMethods = `
  // MARK: - Remote Notification Methods

  public override func application(_ application: UIApplication,
                                  didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    print("🎉 成功获取 deviceToken: \\(deviceToken)")

    // 将 deviceToken 转换为字符串格式
    let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
    let token = tokenParts.joined()
    print("📱 deviceToken (String): \\(token)")

    // 注册到 JPush
    JPUSHService.registerDeviceToken(deviceToken)

    return super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
  }

  public override func application(_ application: UIApplication,
                                  didFailToRegisterForRemoteNotificationsWithError error: Error) {
    print("❌ 注册推送通知失败: \\(error.localizedDescription)")
    return super.application(application, didFailToRegisterForRemoteNotificationsWithError: error)
  }
`

      // 插入推送方法
      config.modResults.contents = config.modResults.contents.replace(
        /(return super\.application\(app, open: url, options: options\).*\n.*\})/,
        `$1${pushMethods}`
      )
    }

    // 5. 添加 JPUSHRegisterDelegate extension
    if (!appDelegateContent.includes('extension AppDelegate')) {
      const jpushExtension = `
// MARK: - JPUSHRegisterDelegate

extension AppDelegate {

  public func jpushNotificationCenter(_ center: UNUserNotificationCenter,
                                     willPresent notification: UNNotification,
                                     withCompletionHandler completionHandler: @escaping (Int) -> Void) {
    let userInfo = notification.request.content.userInfo

    if notification.request.trigger is UNPushNotificationTrigger {
      // 处理远程推送
      JPUSHService.handleRemoteNotification(userInfo)
      print("iOS10 收到远程通知: \\(userInfo)")
      NotificationCenter.default.post(
        name: NSNotification.Name("J_APNS_NOTIFICATION_ARRIVED_EVENT"),
        object: userInfo
      )
    }

    // 在前台显示通知
    let presentationOptions = UNNotificationPresentationOptions.badge.rawValue |
                             UNNotificationPresentationOptions.sound.rawValue |
                             UNNotificationPresentationOptions.alert.rawValue
    completionHandler(Int(presentationOptions))
  }

  public func jpushNotificationCenter(_ center: UNUserNotificationCenter,
                                     didReceive response: UNNotificationResponse,
                                     withCompletionHandler completionHandler: @escaping () -> Void) {
    let userInfo = response.notification.request.content.userInfo

    if response.notification.request.trigger is UNPushNotificationTrigger {
      // 处理远程推送点击
      JPUSHService.handleRemoteNotification(userInfo)
      print("iOS10 用户点击了远程通知: \\(userInfo)")
      NotificationCenter.default.post(
        name: NSNotification.Name("J_APNS_NOTIFICATION_OPENED_EVENT"),
        object: userInfo
      )
    }

    completionHandler()
  }

  // 自定义消息处理
  @objc func networkDidReceiveMessage(_ notification: Notification) {
    let userInfo = notification.userInfo
    guard let _ = userInfo else { return }

    print("收到自定义消息: \\(userInfo!)")
    NotificationCenter.default.post(
      name: NSNotification.Name("J_CUSTOM_NOTIFICATION_EVENT"),
      object: userInfo
    )
  }
}
`

      config.modResults.contents = config.modResults.contents.replace(
        /(class ReactNativeDelegate)/,
        `${jpushExtension}\n$1`
      )
    }

    return config
  })
}

// 配置桥接头文件
function setiOSBridgingHeader(config) {
  return withXcodeProject(config, (config) => {
    console.log('\n[JPush] 配置 Bridging Header ...')

    const xcodeProject = config.modResults

    // 设置桥接头文件路径
    const bridgingHeaderPath = '"$(SRCROOT)/$(TARGET_NAME)/$(TARGET_NAME)-Bridging-Header.h"'
    xcodeProject.addBuildProperty('SWIFT_OBJC_BRIDGING_HEADER', bridgingHeaderPath)

    // 实际创建/修改桥接头文件内容
    const fs = require('fs')
    const path = require('path')
    const iosDir = path.join(config._internal.projectRoot, 'ios')
    const bridgingHeaderFile = path.join(iosDir, 'app', 'app-Bridging-Header.h')

    if (fs.existsSync(bridgingHeaderFile)) {
      let bridgingContent = fs.readFileSync(bridgingHeaderFile, 'utf8')

      // 添加 JPush 相关导入
      const jpushImports = `
// JPush 相关导入
#import <JPUSHService.h>
#import <RCTJPushModule.h>
#import <RCTJPushEventQueue.h>
`

      if (!bridgingContent.includes('JPUSHService.h')) {
        bridgingContent = bridgingContent.trim() + jpushImports
        fs.writeFileSync(bridgingHeaderFile, bridgingContent)
        console.log('[JPush] 已更新 Bridging Header 文件')
      }
    }

    return config
  })
}

module.exports = withJPush

冷启动通知处理(创新亮点)

冷启动是推送通知最具挑战性的场景。当应用完全关闭时,用户点击通知启动应用,需要确保通知数据能够正确传递到 JavaScript 层。

1. Expo 模块架构

创建了一个专门的 Expo 模块来处理冷启动:

bash 复制代码
modules/jpush-coldstart-bridge/
├── ios/
│   ├── JPushAppDelegateSubscriber.swift
│   ├── JPushNotificationCache.swift
│   └── JpushColdstartBridgeModule.swift
└── src/
    └── JpushColdstartBridgeModule.ts

2. JPushAppDelegateSubscriber.swift

这个类订阅应用生命周期事件,负责捕获冷启动通知:

swift 复制代码
import Foundation
import UIKit
import UserNotifications
import ExpoModulesCore

/// 极光推送AppDelegate事件订阅者
public class JPushAppDelegateSubscriber: ExpoAppDelegateSubscriber {

    private let notificationCache = JPushNotificationCache.shared

    /// 应用启动完成
    public func application(_ application: UIApplication,
                          didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        print("📱 JPush AppDelegate订阅者 - 应用启动")

        // 检查是否通过推送通知启动(冷启动)
        if let remoteNotification = launchOptions?[.remoteNotification] as? [AnyHashable: Any] {
            print("📱 检测到冷启动推送通知: \(remoteNotification)")

            // 缓存启动通知,等待模块就绪后触发
            notificationCache.cacheLaunchNotification(remoteNotification)
        }

        return true
    }

    /// 推送通知注册成功
    public func application(_ application: UIApplication,
                          didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        print("📱 推送注册成功")

        let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
        let token = tokenParts.joined()
        print("📱 Device Token: \(token)")

        let tokenData: [String: Any] = [
            "deviceToken": token,
            "timestamp": Date().timeIntervalSince1970
        ]

        notificationCache.sendNotificationEvent(eventName: "deviceTokenReceived", notification: tokenData)
    }
}

// MARK: - UNUserNotificationCenterDelegate

@available(iOS 10.0, *)
extension JPushAppDelegateSubscriber: UNUserNotificationCenterDelegate {

    /// 前台收到推送通知
    public func userNotificationCenter(_ center: UNUserNotificationCenter,
                                      willPresent notification: UNNotification,
                                      withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        let userInfo = notification.request.content.userInfo

        if notification.request.trigger is UNPushNotificationTrigger {
            print("📱 前台收到远程推送: \(userInfo)")
            notificationCache.sendNotificationEvent(eventName: "received", notification: userInfo)
        }

        // 在前台显示通知
        if #available(iOS 14.0, *) {
            completionHandler([.banner, .sound, .badge])
        } else {
            completionHandler([.alert, .sound, .badge])
        }
    }

    /// 用户点击推送通知
    public func userNotificationCenter(_ center: UNUserNotificationCenter,
                                      didReceive response: UNNotificationResponse,
                                      withCompletionHandler completionHandler: @escaping () -> Void) {
        let userInfo = response.notification.request.content.userInfo

        print("📱 用户点击通知: \(userInfo)")
        notificationCache.sendNotificationEvent(eventName: "opened", notification: userInfo)

        completionHandler()
    }
}

3. JPushNotificationCache.swift

缓存管理类,确保通知数据在合适的时机传递给 JavaScript:

swift 复制代码
import Foundation

/// 极光推送通知缓存管理类
class JPushNotificationCache {
    static let shared = JPushNotificationCache()

    /// 缓存的启动通知数据
    private var launchNotification: [AnyHashable: Any]?

    /// 模块是否已准备就绪
    private var isModuleReady = false

    /// 事件发送回调
    private var eventSender: ((_ eventName: String, _ payload: [String: Any]?) -> Void)?

    /// 设置事件发送器
    func setEventSender(_ sender: @escaping (_ eventName: String, _ payload: [String: Any]?) -> Void) {
        self.eventSender = sender
        print("📱 事件发送器已设置")

        // 如果已经有缓存的通知且模块已就绪,立即触发
        triggerCachedNotificationIfReady()
    }

    /// 缓存冷启动通知
    func cacheLaunchNotification(_ notification: [AnyHashable: Any]) {
        self.launchNotification = notification
        print("📱 缓存冷启动通知: \(notification)")

        // 尝试立即触发
        triggerCachedNotificationIfReady()
    }

    /// 标记模块已准备就绪
    func markModuleReady() {
        self.isModuleReady = true
        print("📱 模块标记为就绪")

        // 立即尝试发送缓存的通知
        triggerCachedNotificationIfReady()
    }

    /// 如果条件满足,触发缓存的通知
    private func triggerCachedNotificationIfReady() {
        guard let notification = launchNotification,
              isModuleReady,
              eventSender != nil else {
            return
        }

        print("📱 条件满足,触发缓存的冷启动通知")

        // 延迟一点确保JS端完全准备好
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            self.sendNotificationEvent(eventName: "launched", notification: notification)

            // 清空缓存,防止重复触发
            self.launchNotification = nil
            print("📱 缓存通知已清空")
        }
    }

    /// 发送通知事件到JS端
    func sendNotificationEvent(eventName: String, notification: [AnyHashable: Any]) {
        guard let sender = eventSender else {
            print("⚠️ 事件发送器未设置")
            return
        }

        let payload = convertNotificationToPayload(notification)
        sender(eventName, payload)
        print("📱 发送通知事件: \(eventName)")
    }

    /// 转换通知数据为JS可用的格式
    private func convertNotificationToPayload(_ notification: [AnyHashable: Any]) -> [String: Any] {
        var payload: [String: Any] = [:]

        for (key, value) in notification {
            let stringKey = "\(key)"

            if let stringValue = value as? String {
                payload[stringKey] = stringValue
            } else if let numberValue = value as? NSNumber {
                payload[stringKey] = numberValue
            } else if let dictValue = value as? [AnyHashable: Any] {
                payload[stringKey] = convertNotificationToPayload(dictValue)
            } else if let arrayValue = value as? [Any] {
                payload[stringKey] = arrayValue
            } else {
                payload[stringKey] = "\(value)"
            }
        }

        payload["timestamp"] = Date().timeIntervalSince1970
        payload["source"] = "coldstart"

        return payload
    }
}

4. JpushColdstartBridgeModule.swift

Expo 模块定义,桥接 Swift 和 JavaScript:

swift 复制代码
import ExpoModulesCore

/// JPush冷启动桥接模块
public class JpushColdstartBridgeModule: Module {

    private let notificationCache = JPushNotificationCache.shared

    public func definition() -> ModuleDefinition {
        // 模块名称
        Name("JpushColdstartBridge")

        // 定义可发送到JavaScript的事件
        Events(
            "launched",           // 冷启动通知事件
            "received",          // 前台收到通知事件
            "opened",           // 用户点击通知事件
            "backgroundReceived", // 后台收到通知事件
            "deviceTokenReceived" // 设备令牌获取事件
        )

        // 模块初始化时的回调
        OnCreate {
            print("📱 JPushColdstartBridge模块已创建")
            self.setupNotificationCache()
        }

        // 标记模块已准备就绪的函数
        Function("markModuleReady") {
            print("📱 标记JPush模块为就绪状态")
            self.notificationCache.markModuleReady()
        }

        // 获取模块状态的函数
        Function("getModuleStatus") { () -> [String: Any] in
            return [
                "isReady": true,
                "timestamp": Date().timeIntervalSince1970,
                "platform": "ios"
            ]
        }
    }

    /// 设置通知缓存
    private func setupNotificationCache() {
        // 设置事件发送器
        notificationCache.setEventSender { [weak self] eventName, payload in
            self?.sendEvent(eventName, payload ?? [:])
        }
    }
}

JavaScript/TypeScript 集成

1. JPushHelper 工具类

封装极光推送的初始化和事件管理:

typescript 复制代码
import JPush from 'jpush-react-native'
import { Platform } from 'react-native'

export class JPushHelper {
  private static instance: JPushHelper
  private isInitialized = false
  private isConnected = false
  private listeners: any[] = []

  static getInstance(): JPushHelper {
    if (!JPushHelper.instance) {
      JPushHelper.instance = new JPushHelper()
    }
    return JPushHelper.instance
  }

  /**
   * 初始化极光推送
   */
  async init(config: JPushConfig): Promise<void> {
    if (this.isInitialized) {
      console.log('JPush already initialized')
      return
    }

    return new Promise((resolve, reject) => {
      try {
        // 设置调试模式
        JPush.setLoggerEnable(__DEV__)

        // 统一初始化流程
        console.log(`[${Platform.OS}] 开始初始化极光推送...`)
        JPush.init({
          appKey: config.appKey,
          channel: config.channel,
          production: !!config.production,
        })

        this.isInitialized = true
        console.log('JPush initialized successfully')

        // 添加连接事件监听器
        JPush.addConnectEventListener((result) => {
          console.log(`[${Platform.OS}] JPush 连接状态变化:`, result)
          this.isConnected = result.connectEnable ?? false

          if (this.isConnected) {
            console.log(`✅ JPush 连接成功`)
          }
          else {
            console.warn(`⚠️ JPush 连接失败`)
          }
        })

        resolve()
      }
      catch (error) {
        console.error('JPush initialization failed:', error)
        reject(error)
      }
    })
  }

  /**
   * 获取注册 ID
   */
  async getRegistrationID(): Promise<string> {
    return new Promise((resolve, reject) => {
      console.log(`[${Platform.OS}] 尝试获取注册 ID...`)

      JPush.getRegistrationID((idResult: any) => {
        const id = idResult.registerID || idResult.registrationId || idResult

        if (id && typeof id === 'string' && id.length > 0) {
          console.log(`[${Platform.OS}] 获取到注册ID:`, id)
          resolve(id)
        }
        else {
          // 等待连接后再试
          const listener = JPush.addConnectEventListener(() => {
            JPush.getRegistrationID((secondResult: any) => {
              const secondId = secondResult.registerID || secondResult
              if (secondId) {
                listener() // 移除监听器
                resolve(secondId)
              }
            })
          })

          // 设置超时
          setTimeout(() => {
            listener()
            reject(new Error('获取注册ID超时'))
          }, 10000)
        }
      })
    })
  }
}

2. JPushProvider 全局配置

在应用根组件中配置推送服务:

tsx 复制代码
import { router } from 'expo-router'
import React, { useEffect } from 'react'
import JPushColdstartBridge from '@/modules/jpush-coldstart-bridge'
import { jpushHelper } from '@/utils/jpush-helper'

export function JPushProvider({ children }: { children: ReactNode }) {
  useEffect(() => {
    const initJPushWithEvents = async () => {
      try {
        console.log('[JPush Provider] 开始设置极光推送...')

        // 1. 设置冷启动桥接模块的事件监听
        const launchedSub = JPushColdstartBridge.addListener('launched', (data) => {
          console.log('📱 收到冷启动通知:', data)
          handleNotificationNavigation(data?.url, 'coldstart')
        })

        const openedSub = JPushColdstartBridge.addListener('opened', (data) => {
          console.log('📱 用户点击通知:', data)
          handleNotificationNavigation(data?.url, 'notification')
        })

        // 2. 初始化 JPush
        await jpushHelper.init({
          appKey: '你的AppKey',
          channel: 'prod',
          production: false,
        })

        // 3. 标记冷启动模块为就绪
        JPushColdstartBridge.markModuleReady()

        // 4. 添加 JPush 事件监听
        jpushHelper.addEventListeners({
          onConnect: (result) => {
            console.log('JPush 连接成功')
          },
          onNotification: (result) => {
            console.log('收到推送通知:', result)
            // 处理通知逻辑
          },
          onCustomMessage: (result) => {
            console.log('收到自定义消息:', result)
          },
        })

        // 5. 获取注册ID(用于测试)
        const regId = await jpushHelper.getRegistrationID()
        console.log('设备注册ID:', regId)
      }
      catch (error) {
        console.error('JPush 初始化失败:', error)
      }
    }

    initJPushWithEvents()

    // 清理函数
    return () => {
      jpushHelper.cleanup()
    }
  }, [])

  return <>{children}</>
}

// 处理通知跳转
function handleNotificationNavigation(url: string | undefined, source: string) {
  if (!url)
    return

  try {
    const [path, queryString] = url.split('?')
    let finalPath = path

    if (queryString) {
      finalPath = `${path}?${queryString}`
    }

    if (!finalPath.startsWith('/')) {
      finalPath = `/${finalPath}`
    }

    router.push(finalPath as any)
    console.log(`[${source}] 页面跳转成功: ${finalPath}`)
  }
  catch (error) {
    console.error(`[${source}] 页面跳转失败:`, error)
  }
}

测试与验证

1. 获取注册 ID

成功集成后,首先验证能否获取到注册 ID:

typescript 复制代码
const registrationId = await jpushHelper.getRegistrationID()
console.log('注册ID:', registrationId)
// 输出类似:180976fa8a94bb3f149

2. 发送测试推送

在极光推送控制台发送测试消息:

  1. 登录极光推送控制台
  2. 选择你的应用
  3. 进入"推送" -> "发送通知"
  4. 选择目标(可以用注册ID定向推送)
  5. 设置通知内容和附加字段(extras)

3. 测试场景覆盖

  • 前台通知:应用在前台时收到推送
  • 后台通知:应用在后台时收到推送
  • 冷启动:应用完全关闭,点击通知启动
  • 自定义消息:测试透传消息
  • 页面跳转:通过 extras 中的 url 字段实现深度链接

4. 调试技巧

javascript 复制代码
// 在 Xcode 控制台查看日志
// 搜索关键字:📱、JPush、推送

// JavaScript 端调试
jpushHelper.checkConnectionStatus() // 检查连接状态

常见问题与故障排查

1. iOS 推送证书问题

问题:iOS 设备无法收到推送

解决方案

  1. 检查 Xcode -> Signing & Capabilities -> Push Notifications 是否开启
  2. 确认极光控制台已上传正确的推送证书(Development/Production)
  3. 验证 Bundle ID 与极光控制台完全匹配
  4. 检查 apsForProduction 参数(开发环境设为 false)

2. Swift/Objective-C 混编问题

问题:编译错误 "Cannot find 'JPUSHService' in scope"

解决方案

objc 复制代码
// 确保 Bridging Header 文件包含以下内容:
#import <JPUSHService.h>
#import <RCTJPushModule.h>
#import <RCTJPushEventQueue.h>

3. 注册 ID 获取失败

问题:一直获取不到注册 ID

可能原因

  • 网络连接问题
  • AppKey 配置错误
  • iOS 推送权限未授予
  • 证书配置问题

排查步骤

javascript 复制代码
// 1. 检查网络
// 3. 检查权限
import { PermissionsIOS } from 'react-native'

console.log('检查网络连接...')

// 2. 验证配置
console.log('AppKey:', config.appKey)
console.log('Channel:', config.channel)
const permission = await PermissionsIOS.check('notification')
console.log('通知权限:', permission)

4. 冷启动通知丢失

问题:冷启动时通知数据丢失

解决方案: 确保按照正确顺序初始化:

  1. 先设置事件监听器
  2. 初始化 JPush
  3. 调用 markModuleReady()

5. 前台通知不显示

问题:应用在前台时收不到通知提醒

解决方案

swift 复制代码
// 确保在 completionHandler 中设置了正确的选项
completionHandler([.banner, .sound, .badge])

与 Android 的对比

Android 集成要点

Android 端的集成相对简单,主要区别:

  1. 配置方式:主要通过 Gradle 配置
  2. 无需桥接头文件:Java/Kotlin 直接调用
  3. 权限处理:在 AndroidManifest.xml 中声明
javascript 复制代码
// Android 特定配置
function setAppBuildGradle(config) {
  return withAppBuildGradle(config, (config) => {
    // 添加 manifestPlaceholders
    config.modResults.contents = config.modResults.contents.replace(
      /defaultConfig([\s\S]*)versionName(.*)\n/,
      `$&        manifestPlaceholders = [
            JPUSH_APPKEY: "${JPUSH_APPKEY}",
            JPUSH_CHANNEL: "${JPUSH_CHANNEL}"
        ]\n`
    )
    return config
  })
}

平台差异处理

typescript 复制代码
import { Platform } from 'react-native'

if (Platform.OS === 'ios') {
  // iOS 特定逻辑
  JPushColdstartBridge.markModuleReady()
}
else {
  // Android 特定逻辑
  // Android 不需要冷启动桥接
}

结语

通过这次实践,我们不仅解决了技术难题,还深入理解了:

  • React Native 新架构下的原生模块开发
  • Swift 与 Objective-C 的混编技巧
  • Expo Config Plugin 的强大能力

希望这篇文章能帮助到遇到类似问题的开发者。如果你有任何问题或建议,欢迎在评论区交流!


相关资源

项目信息

  • Expo SDK: 53+
  • React Native: 0.79.5
  • jpush-react-native: 3.1.9
  • jcore-react-native: 2.3.0

如果这篇文章对你有帮助,请点赞支持!

相关推荐
猪哥帅过吴彦祖2 小时前
Flutter 系列教程:布局基础 (上) - `Container`, `Row`, `Column`, `Flex`
前端·flutter·ios
lifejump2 小时前
DVWA | XSS 跨站脚本注入
前端·xss
gplitems1232 小时前
Tripfery - Travel & Tour Booking WordPress Theme Tested
前端
流星稍逝2 小时前
前端&后端解决跨域的方法
前端·后端
白水清风2 小时前
【基础】关于函数式编程的知识
前端·javascript·面试
蓝莓味的口香糖2 小时前
【JS】JS基础-对象处理方法整合
开发语言·前端·javascript
sanx183 小时前
一站式电竞平台解决方案:数据、直播、源码,助力业务飞速启航
前端·数据库·apache·数据库开发·时序数据库
余防3 小时前
存储型XSS,反射型xss
前端·安全·web安全·网络安全·xss
ObjectX前端实验室3 小时前
从零到一:系统化掌握大模型应用开发【目录】
前端·llm·agent