基于实际项目的极光推送 Config Plugin 实践

Created on

前言

在 Expo 项目里集成极光推送,最棘手的问题不是 JS API,而是原生层的配置与幂等化。只要经历过一次 prebuild 或升级,你就会理解“把原生改动变成插件”的必要性。

本文基于真实项目里的实现,整理一篇可直接复用的 Config Plugin 实战思路,包括 Android/iOS 改动点、参数设计、插入策略与验证流程。

项目现状与目标

在实际项目中,极光推送插件作为一个独立 package 存在,路径为:

目标很明确:

插件使用方式

在应用侧(以 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_APPKEYJPUSH_CHANNEL 写入 manifestPlaceholders,插件在 app/build.gradle 内完成:

contents = addManifestPlaceholders(contents);

实现策略是:

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;

插件幂等性的关键点

这个项目里做了几件事保证幂等:

这能避免重复执行时污染原生工程。

验证流程

建议流程:

  1. expo prebuild 后检查 AndroidManifest、Gradle 与 iOS AppDelegate
  2. 本地或 EAS Build 编译
  3. 真机安装,确认推送 token 正常获取
  4. 后台推送验证

小结

在真实项目里,Config Plugin 的价值不只是“把原生改动自动化”,更重要的是让这些改动可追溯、可复用、可升级。这类 JPush 插件是一个可复用的工程化模板,建议所有需要推送能力的 Expo 项目都采用类似方式沉淀。