实现 RN 离线推送
更新时间: 2026/09/02 15:45:01
为了提高消息送达率,网易云信即时通讯 SDK(简称 NIM SDK)可以与 APNs、FCM 及 Android 手机厂商的系统推送服务配合,在应用进入后台、进程被回收或 SDK 连接中断时发送离线推送。
适用场景
通过集成各移动端设备厂商的原生推送 SDK,与 React Native NIM SDK 搭配使用,实现离线推送功能。
触发离线推送的典型场景包括:
- 应用被切换到后台,且 App 资源被系统回收时。
- 用户主动关闭 App 时。
- iOS 应用被切换到后台时。
- 网络不稳定导致 NIM SDK 无法与服务器保持正常连接时。
支持范围
| 平台 | 推送通道 | 接入方式 |
|---|---|---|
| Android | 小米、华为、vivo、OPPO、魅族、FCM、荣耀 | 集成网易云信 nimpush React Native 原生模块及对应厂商 SDK |
| iOS | APNs | 集成 @react-native-community/push-notification-ios |
本文所述 React Native 插件暂不包含 HarmonyOS 推送实现。
Demo 源码
点击 此处 访问 RN 离线推送 Demo 源码。
实现原理
React Native 离线推送由 JavaScript SDK、React Native 推送插件、厂商推送 SDK 和网易云信服务共同完成:
sequenceDiagram
participant App as 应用
participant SDK as NIM SDK
participant Plugin as 推送插件
participant Vendor as 厂商推送服务
participant Server as 网易云信服务器
App->>SDK: 1. 创建 NIM 实例,调用 setOfflinePushConfig
SDK->>Plugin: 2. 调用 init / getDeviceInfo
Plugin-->>SDK: 返回设备厂商信息
SDK->>Server: 3. 登录请求(携带设备信息)
Server-->>SDK: 4. 返回建议的推送通道
SDK->>Plugin: 5. 调用 onLogin
Plugin->>Vendor: 6. 获取推送 Token
Vendor-->>Plugin: 7. 返回 Token
Plugin-->>SDK: 8. 上报 Token + 证书名称
SDK->>Server: 9. 同步前后台状态
setOfflinePushConfig 必须在 V2NIMLoginService.login 之前调用。业务层不需要直接调用插件的 init、onLogin 或自行上报 Token。
前提条件
根据本文操作前,请确保您已经完成以下操作:
- 注册 IM 账号。
- 已创建可正常运行的 React Native Android 或 iOS 工程。
- 已在目标厂商推送平台创建应用,并确保 Android
applicationId、iOS Bundle Identifier、签名证书等信息与实际应用一致。 - 使用真机测试。模拟器通常无法完整验证厂商推送或 APNs。
上传推送证书
-
在对应移动端设备厂商的推送平台上,注册应用获取应用信息,并开启推送服务。
-
将获取到的应用信息和推送证书,上传至 网易云信控制台,完成移动端设备厂商推送服务与网易云信服务的通信。
具体步骤请参考以下文档:
引入推送资源
安装 RN NIM SDK
bashnpm install nim-web-sdk-ng@">=10"
React Native 工程需使用专用入口:
tsimport NIM from 'nim-web-sdk-ng/dist/v2/NIM_RN_SDK'
不要使用默认的浏览器入口 NIM_BROWSER_SDK。
Android 推送插件
-
解压后将
nimpush/目录放到 React Native 工程的android/目录下:text
your-react-native-app/ ├── android/ │ ├── app/ │ ├── nimpush/ │ │ ├── libs/ │ │ ├── src/ │ │ ├── build.gradle │ │ └── proguard-rules.pro │ └── settings.gradle └── package.json
npm install nim-web-sdk-ng安装的 npm 包 不包含nimpush原生工程,需单独下载。nimpush/libs包含rnpush.jar及小米、vivo、OPPO 的厂商 SDK。- 建议将 NIM SDK 与同一发布周期提供的推送插件配套使用。
iOS 推送插件
iOS 使用社区插件:
bashnpm install @react-native-community/push-notification-ios
cd ios && pod install && cd ..
配置 Android 工程
仅需接入实际使用的厂商通道。例如只面向海外 Android 设备时,可以只配置 FCM。
引入 nimpush 模块
-
在
android/settings.gradle中添加:groovyinclude ':nimpush' project(':nimpush').projectDir = new File(rootProject.projectDir, 'nimpush') -
将
android/nimpush/libs中的文件复制到android/app/libs,然后在android/app/build.gradle中添加:groovydependencies { implementation project(':nimpush') implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) }
rnpush.jar 是运行时依赖,必须保留。
注册 React Native Package
nimpush 采用手动 link,需要在 MainApplication 中注册 NIMPushPackage:
kotlinimport com.netease.nim.rn.push.NIMPushPackage
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// nimpush 没有 npm autolinking 配置,需要手动注册。
add(NIMPushPackage())
}
}
}
javaimport com.netease.nim.rn.push.NIMPushPackage;
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
// nimpush 没有 npm autolinking 配置,需要手动注册。
packages.add(new NIMPushPackage());
return packages;
}
添加厂商依赖
按需在 android/app/build.gradle 中添加:
| 通道 | 依赖 |
|---|---|
| 小米 | 插件包已提供 AAR |
| vivo | 插件包已提供 AAR |
| OPPO | 插件包已提供 AAR + Gson + Commons Codec |
| 魅族 | com.meizu.flyme.internet:push-internal:4.3.0 |
| FCM | play-services-base:18.5.0 + firebase-messaging:24.0.0 |
| 华为 | com.huawei.hms:push:6.12.0.300 |
| 荣耀 | com.hihonor.mcs:push:7.0.61.303 |
同时完成厂商配置文件接入:
| 通道 | 配置文件 | Gradle 插件 |
|---|---|---|
| FCM | google-services.json → android/app/ |
com.google.gms.google-services |
| 华为 | agconnect-services.json → android/app/ |
com.huawei.agconnect |
| 荣耀 | mcs-services.json → android/app/ |
添加 https://developer.hihonor.com/repo |
配置 AndroidManifest.xml
在 android/app/src/main/AndroidManifest.xml 中按需添加通道组件。
Android 13+ 需声明通知权限:
xml<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
xml<receiver
android:name="com.netease.nimlib.mixpush.mi.MiPushReceiver"
android:exported="true">
<intent-filter android:priority="0x7fffffff">
<action android:name="com.xiaomi.mipush.RECEIVE_MESSAGE" />
<action android:name="com.xiaomi.mipush.MESSAGE_ARRIVED" />
<action android:name="com.xiaomi.mipush.ERROR" />
</intent-filter>
</receiver>
在 <application> 外添加:
xml<uses-permission android:name="com.meizu.flyme.push.permission.RECEIVE" />
<permission
android:name="${applicationId}.push.permission.MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.push.permission.MESSAGE" />
<uses-permission android:name="com.meizu.c2dm.permission.RECEIVE" />
<permission
android:name="${applicationId}.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
在 <application> 内添加:
xml<receiver
android:name="com.netease.nimlib.mixpush.mz.MZPushReceiver"
android:exported="true"
android:permission="com.meizu.cloud.push.permission.MESSAGE">
<intent-filter android:priority="0x7fffffff">
<action android:name="com.meizu.flyme.push.intent.MESSAGE" />
<action android:name="com.meizu.flyme.push.intent.REGISTER.FEEDBACK" />
<action android:name="com.meizu.flyme.push.intent.UNREGISTER.FEEDBACK" />
<action android:name="com.meizu.c2dm.intent.REGISTRATION" />
<action android:name="com.meizu.c2dm.intent.RECEIVE" />
<category android:name="${applicationId}" />
</intent-filter>
</receiver>
xml<meta-data
android:name="com.vivo.push.api_key"
android:value="YOUR_VIVO_APP_KEY" />
<meta-data
android:name="com.vivo.push.app_id"
android:value="YOUR_VIVO_APP_ID" />
<receiver
android:name="com.netease.nimlib.mixpush.vivo.VivoPushReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.vivo.pushclient.action.RECEIVE" />
</intent-filter>
</receiver>
<service
android:name="com.vivo.push.sdk.service.CommandClientService"
android:exported="true"
android:permission="com.push.permission.UPSTAGESERVICE" />
在 <application> 外添加:
xml<uses-permission android:name="com.coloros.mcs.permission.RECIEVE_MCS_MESSAGE" />
<uses-permission android:name="com.heytap.mcs.permission.RECIEVE_MCS_MESSAGE" />
在 <application> 内添加:
xml<service
android:name="com.netease.nimlib.mixpush.oppo.OppoPushService"
android:exported="true"
android:permission="com.coloros.mcs.permission.SEND_MCS_MESSAGE">
<intent-filter>
<action android:name="com.coloros.mcs.action.RECEIVE_MCS_MESSAGE" />
</intent-filter>
</service>
<service
android:name="com.netease.nimlib.mixpush.oppo.OppoAppPushService"
android:exported="true"
android:permission="com.heytap.mcs.permission.SEND_PUSH_MESSAGE">
<intent-filter>
<action android:name="com.heytap.mcs.action.RECEIVE_MCS_MESSAGE" />
<action android:name="com.heytap.msp.push.RECEIVE_MCS_MESSAGE" />
</intent-filter>
</service>
<service
android:name="com.heytap.msp.push.service.CompatibleDataMessageCallbackService"
android:exported="true"
android:permission="com.coloros.mcs.permission.SEND_MCS_MESSAGE">
<intent-filter>
<action android:name="com.coloros.mcs.action.RECEIVE_MCS_MESSAGE" />
</intent-filter>
</service>
<service
android:name="com.heytap.msp.push.service.DataMessageCallbackService"
android:exported="true"
android:permission="com.heytap.mcs.permission.SEND_PUSH_MESSAGE">
<intent-filter>
<action android:name="com.heytap.mcs.action.RECEIVE_MCS_MESSAGE" />
<action android:name="com.heytap.msp.push.RECEIVE_MCS_MESSAGE" />
</intent-filter>
</service>
xml<service
android:name="com.netease.nimlib.mixpush.fcm.FCMTokenService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
xml<service
android:name="com.netease.nimlib.mixpush.hw.HWPushService"
android:exported="false">
<intent-filter>
<action android:name="com.huawei.push.action.MESSAGING_EVENT" />
</intent-filter>
</service>
xml<meta-data
android:name="com.hihonor.push.app_id"
android:value="YOUR_HONOR_APP_ID" />
<service
android:name="com.netease.nimlib.mixpush.honor.HonorPushService"
android:exported="false">
<intent-filter>
<action android:name="com.hihonor.push.action.MESSAGING_EVENT" />
</intent-filter>
</service>
请求 Android 通知权限
Android 13+ 需在运行时请求通知权限:
tsimport { PermissionsAndroid, Platform } from 'react-native'
export async function requestNotificationPermission(): Promise<boolean> {
if (Platform.OS !== 'android' || Platform.Version < 33) return true
const result = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
)
return result === PermissionsAndroid.RESULTS.GRANTED
}
修改原生配置后,必须重新构建 Android App。
配置 iOS 工程
启用推送能力
在 Xcode 的 Target > Signing & Capabilities 中启用 Push Notifications。如需后台通知,再启用 Background Modes > Remote notifications。
确保以下标识保持一致:
- Xcode Target 的 Bundle Identifier
- Apple Developer 中 App ID 的 Bundle ID
- APNs 证书对应的 App ID
- 网易云信控制台中上传的 APNs 推送证书
修改 AppDelegate
以下示例适用于 Objective-C/Objective-C++ AppDelegate。
-
在
AppDelegate.h中引入通知框架并实现UNUserNotificationCenterDelegate。-
React Native 0.71 及以上示例:
objc#import <UserNotifications/UNUserNotificationCenter.h> @interface AppDelegate : RCTAppDelegate <UNUserNotificationCenterDelegate> @end -
React Native 0.70 及以下版本通常使用以下声明:
objc@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, UNUserNotificationCenterDelegate> @end
-
-
在
AppDelegate.m或AppDelegate.mm中引入:objc#import <UserNotifications/UserNotifications.h> #import <RNCPushNotificationIOS.h> -
将以下委托方法合并到现有
AppDelegate实现中:objc// 将 APNs 注册成功事件转发给 React Native 推送插件。 - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } // 将 APNs 注册失败事件转发给 React Native 推送插件。 - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error]; } // 将后台远程通知转发给 React Native 推送插件。 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { [RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; } // 将用户点击通知的事件转发给 React Native 推送插件。 - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { [RNCPushNotificationIOS didReceiveNotificationResponse:response]; completionHandler(); } // 控制应用位于前台时的通知展示方式。 - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler { completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge); } -
在现有
application:didFinishLaunchingWithOptions:中设置通知中心代理,不要重复声明同名方法:objcUNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = self;
实现离线推送
提供平台对应的推送插件
利用 React Native 的平台文件解析机制创建两个文件。
-
PushPlugin.android.ts:tsimport { NativeModules } from 'react-native' export default NativeModules.NIMPushModule -
PushPlugin.ios.ts:tsimport PushNotificationIOS from '@react-native-community/push-notification-ios' export default PushNotificationIOS
业务代码统一引用 ./PushPlugin,React Native 会按当前平台选择对应文件。
创建 NIM 实例并设置推送配置
tsimport NIM from 'nim-web-sdk-ng/dist/v2/NIM_RN_SDK'
import PushPlugin from './PushPlugin'
const nim = NIM.getInstance(
{
appkey: 'YOUR_NIM_APP_KEY',
apiVersion: 'v2'
},
{
V2NIMLoginServiceConfig: {
// 建议保持同一运行期内的设备标识稳定,减少重复设备连接。
isFixedDeviceId: true,
// Android 填 applicationId,iOS 填 Bundle Identifier。
bundleName: 'YOUR_APPLICATION_ID_OR_BUNDLE_ID'
}
}
)
const offlinePushConfig = {
miPush: {
appId: 'YOUR_XIAOMI_APP_ID',
appKey: 'YOUR_XIAOMI_APP_KEY',
certificateName: 'YOUR_XIAOMI_CERTIFICATE_NAME'
},
hwPush: {
appId: 'YOUR_HUAWEI_APP_ID',
certificateName: 'YOUR_HUAWEI_CERTIFICATE_NAME'
},
vivoPush: {
appId: 'YOUR_VIVO_APP_ID',
appKey: 'YOUR_VIVO_APP_KEY',
certificateName: 'YOUR_VIVO_CERTIFICATE_NAME'
},
oppoPush: {
appId: 'YOUR_OPPO_APP_ID',
appKey: 'YOUR_OPPO_APP_KEY',
secret: 'YOUR_OPPO_APP_SECRET',
certificateName: 'YOUR_OPPO_CERTIFICATE_NAME'
},
mzPush: {
appId: 'YOUR_MEIZU_APP_ID',
appKey: 'YOUR_MEIZU_APP_KEY',
certificateName: 'YOUR_MEIZU_CERTIFICATE_NAME'
},
fcmPush: {
certificateName: 'YOUR_FCM_CERTIFICATE_NAME'
},
honorPush: {
appId: 'YOUR_HONOR_APP_ID',
appKey: 'YOUR_HONOR_APP_KEY',
certificateName: 'YOUR_HONOR_CERTIFICATE_NAME'
},
apns: {
certificateName: 'YOUR_APNS_CERTIFICATE_NAME'
}
}
// 必须在 NIM 实例创建后、登录前设置推送插件和证书配置。
nim.V2NIMSettingService.setOfflinePushConfig(PushPlugin, offlinePushConfig)
await nim.V2NIMLoginService.login('YOUR_ACCOUNT_ID', 'YOUR_ACCOUNT_TOKEN')
- 只保留实际接入的通道配置。
- 所有
certificateName必须与网易云信控制台中的推送证书名称 完全一致(包括大小写)。 - Android 插件从 JavaScript 配置读取小米、华为、OPPO、魅族的应用参数。vivo 和荣耀仍需要完成前文所述的 Android Manifest 或厂商配置文件接入,不能只填写 JavaScript 配置。
- 当前 React Native adapter 使用进程内存保存 SDK 的本地缓存。
isFixedDeviceId: true可以稳定同一运行期及重连期间的设备标识,但 不应将其视为跨应用冷启动永久不变的物理设备 ID。
前后台状态处理
React Native 入口会自动将 AppState 注入 NIM SDK,SDK 自动监听 active、inactive 和 background 状态并同步服务器。无需手动调用 setAppBackground。
发送可离线推送的消息
发送消息时,确保没有显式关闭推送。也可以通过 pushConfig 指定推送文案:
tsconst receiverAccountId = 'RECEIVER_ACCOUNT_ID'
const conversationId = nim.V2NIMConversationIdUtil.p2pConversationId(receiverAccountId)
const message = nim.V2NIMMessageCreator.createTextMessage('hello world')
await nim.V2NIMMessageService.sendMessage(message, conversationId, {
pushConfig: {
pushEnabled: true,
pushNickEnabled: true,
pushContent: '您有一条新消息'
}
})
如果会话或用户已开启免打扰,服务器会按照免打扰策略决定是否发送通知。
测试离线推送
建议准备两个 IM 账号和两台真机,按以下顺序验证:
- 接收端安装重新构建后的原生 App,并允许系统通知权限。
- 接收端登录 IM,确认 Android 日志出现插件初始化、设备信息和 Token 回调;iOS 确认已获得 APNs Device Token。
- Android 结束应用进程;iOS 先切到后台测试,再结束进程测试。
- 发送端发送启用了
pushEnabled的消息。 - 检查接收端是否收到系统通知,验证冷启动、后台和断网场景。
Android 关键日志:
textNIMPushModule: init
NIMPushModule: onPushToken
OfflinePushService:getDeviceInfo
OfflinePushService:: onLogin
日志中不应长期停留在 “获取设备信息” 或出现空 Token、空证书名称警告。
通知点击处理边界
Android nimpush 模块能识别通知产生的 Intent 并清理通知栏,但 未将点击 payload 暴露给 JavaScript。
如需 “点击通知后跳转到指定会话”,需额外扩展:
- 在
pushPayload中配置点击动作或 Intent。 - 在 Android 原生层解析 Intent。
- 扩展 Native Module,将会话参数发送到 JavaScript。
常见问题
| 问题 | 排查方向 |
|---|---|
NativeModules.NIMPushModule 为 undefined |
检查 nimpush 目录是否存在、settings.gradle 是否包含 :nimpush、MainApplication 是否注册 NIMPushPackage |
Android 登录日志显示 getDeviceInfo timeout |
插件未正确注册、Native Module 版本不匹配,或 getDeviceInfo 未返回合法 JSON |
| Android 未获得厂商 Token | 检查厂商依赖是否打入 APK、Manifest 配置是否完整、厂商应用信息是否正确、配置文件是否位于 android/app/ |
| 日志提示证书名称为空 | offlinePushConfig 中对应通道的 certificateName 为空或与控制台不一致 |
华为提示 Failed to check the Fingerprint |
检查 keystore 是否与华为控制台及 agconnect-services.json 对应 |
| iOS 无法获得 Device Token | 检查是否使用真机、Xcode 是否启用 Push Notifications、Provisioning Profile 是否包含推送权限、AppDelegate 是否正确转发事件 |
| 一条消息出现重复通知 | 检查业务是否在收到在线消息时又调用了本地通知,同时服务器也发送了厂商推送 |




