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.9
和jcore-react-native: 2.3.0
- Android 端集成很顺利,按照官方文档即可完成
- iOS 端集成失败 - 原因是极光官方提供的示例代码还停留在 Objective-C 时代,而新版 React Native 已经全面转向 Swift,当然也可以切换回 oc 但是我避他锋芒?/滑稽
解决方案概述
经过与极光推送技术团队的深入沟通,获得了他们提供的 React Native Swift 版本代码。然后通过 AI 辅助,将这些代码成功转换为 Expo Config Plugin 格式,最终实现了集成。
本文将详细记录整个集成过程,包括:
- Expo Config Plugin 的 Swift 代码注入
- 冷启动通知的处理机制
- 完整的事件监听体系
- 故障排查经验
环境准备
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: '需要麦克风权限用于语音通话',
}
}
}
核心问题分析
为什么官方示例不能直接用?
- 语言差异:官方示例基于 Objective-C,新版 RN 使用 Swift
- 架构变化:RN 0.79+ 的新架构改变了原生模块的实现方式
- 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. 发送测试推送
在极光推送控制台发送测试消息:
- 登录极光推送控制台
- 选择你的应用
- 进入"推送" -> "发送通知"
- 选择目标(可以用注册ID定向推送)
- 设置通知内容和附加字段(extras)
3. 测试场景覆盖
- 前台通知:应用在前台时收到推送
- 后台通知:应用在后台时收到推送
- 冷启动:应用完全关闭,点击通知启动
- 自定义消息:测试透传消息
- 页面跳转:通过 extras 中的 url 字段实现深度链接
4. 调试技巧
javascript
// 在 Xcode 控制台查看日志
// 搜索关键字:📱、JPush、推送
// JavaScript 端调试
jpushHelper.checkConnectionStatus() // 检查连接状态
常见问题与故障排查
1. iOS 推送证书问题
问题:iOS 设备无法收到推送
解决方案:
- 检查 Xcode -> Signing & Capabilities -> Push Notifications 是否开启
- 确认极光控制台已上传正确的推送证书(Development/Production)
- 验证 Bundle ID 与极光控制台完全匹配
- 检查 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. 冷启动通知丢失
问题:冷启动时通知数据丢失
解决方案: 确保按照正确顺序初始化:
- 先设置事件监听器
- 初始化 JPush
- 调用 markModuleReady()
5. 前台通知不显示
问题:应用在前台时收不到通知提醒
解决方案:
swift
// 确保在 completionHandler 中设置了正确的选项
completionHandler([.banner, .sound, .badge])
与 Android 的对比
Android 集成要点
Android 端的集成相对简单,主要区别:
- 配置方式:主要通过 Gradle 配置
- 无需桥接头文件:Java/Kotlin 直接调用
- 权限处理:在 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
如果这篇文章对你有帮助,请点赞支持!