基于实际项目的极光推送 Config Plugin 实践
前言
在 Expo 项目里集成极光推送,最棘手的问题不是 JS API,而是原生层的配置与幂等化。只要经历过一次 prebuild 或升级,你就会理解“把原生改动变成插件”的必要性。
本文基于真实项目里的实现,整理一篇可直接复用的 Config Plugin 实战思路,包括 Android/iOS 改动点、参数设计、插入策略与验证流程。
项目现状与目标
在实际项目中,极光推送插件作为一个独立 package 存在,路径为:
packages/push-notification/plugin/withJPushAndroid.jspackages/push-notification/plugin/withJPushIOS.js
目标很明确:
- 用配置参数注入 AppKey 与 Channel
- 自动生成 Android 通知小图标
- iOS 自动补齐 Info.plist、Entitlements、AppDelegate 逻辑
- 保证插件幂等,可重复执行
插件使用方式
在应用侧(以 apps/customer/app.config.ts 为例),插件这样接入:
[
"@repo/push-notification/plugin",
{
appKey: process.env.EXPO_PUBLIC_JPUSH_APPKEY,
channel: "customer",
notificationIcon: "android-icon-monochrome",
},
],
这意味着插件的职责必须是“可重复、无副作用、配置可参数化”。
Android: Gradle + Manifest + 图标
1) manifestPlaceholders
JPush 需要 JPUSH_APPKEY 与 JPUSH_CHANNEL 写入 manifestPlaceholders,插件在 app/build.gradle 内完成:
contents = addManifestPlaceholders(contents);
实现策略是:
- 若已有
manifestPlaceholders,合并并保持幂等 - 若不存在,在
defaultConfig内插入
2) gradle.properties 注入
插件将 AppKey 与 Channel 写入 gradle.properties,便于统一管理:
addOrUpdateProperty(properties, "JPUSH_APPKEY", props.appKey || "");
addOrUpdateProperty(properties, "JPUSH_CHANNEL", props.channel || "default");
3) AndroidManifest meta-data
使用 Expo 提供的工具函数向 application 节点注入 meta-data:
addMetaDataItemToMainApplication(
mainApplication,
"JPUSH_APPKEY",
"${JPUSH_APPKEY}",
"value"
);
同时,插件补齐必要权限并移除不合规的 QUERY_ALL_PACKAGES 权限:
removePermissions(config.modResults, ["android.permission.QUERY_ALL_PACKAGES"]);
addRemovePermission(
config.modResults.manifest,
"android.permission.QUERY_ALL_PACKAGES"
);
4) 自动生成通知小图标
插件会寻找源图标,并按 mdpi/xhdpi 等多 DPI 生成资源:
await writeNotificationIconImageFilesAsync(
iconSourcePath,
platformProjectRoot,
projectRoot,
"jpush_notification_icon"
);
这块与 expo-notifications 的实现保持一致,避免运行时找不到图标。
iOS: Info.plist + Bridging Header + AppDelegate
1) Info.plist
插件会确保 UIBackgroundModes 包含 remote-notification:
const backgroundModes = ensureArrayKey(infoPlist, "UIBackgroundModes");
if (!backgroundModes.includes("remote-notification")) {
backgroundModes.push("remote-notification");
}
2) Bridging Header
若项目是 Swift,插件会自动向 Bridging Header 注入 JPush 头文件:
//JPush Need
#import "JPUSHService.h"
#import "RCTJPushModule.h"
这样可以确保 JPush 的 native API 被正确暴露到 RN 层。
3) AppDelegate 注入
插件通过 withAppDelegate 注入 JPush 的核心回调,例如:
public override func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
JPUSHService.registerDeviceToken(deviceToken)
}
以及 JPUSHRegisterDelegate 的扩展,用于处理前台通知、点击通知与授权变化。
4) Entitlements
通过 withEntitlementsPlist 自动补齐 aps-environment:
const apsEnvironment =
process.env.EXPO_PUBLIC_APS_ENVIRONMENT ||
(process.env.NODE_ENV === "production" ? "production" : "development");
entitlements["aps-environment"] = apsEnvironment;
插件幂等性的关键点
这个项目里做了几件事保证幂等:
- 插入前先判断已有内容
- manifestPlaceholders 支持多块合并
- AppDelegate 只插一次,并用标识判断
这能避免重复执行时污染原生工程。
验证流程
建议流程:
expo prebuild后检查 AndroidManifest、Gradle 与 iOS AppDelegate- 本地或 EAS Build 编译
- 真机安装,确认推送 token 正常获取
- 后台推送验证
小结
在真实项目里,Config Plugin 的价值不只是“把原生改动自动化”,更重要的是让这些改动可追溯、可复用、可升级。这类 JPush 插件是一个可复用的工程化模板,建议所有需要推送能力的 Expo 项目都采用类似方式沉淀。