feat(push): 添加多厂商推送集成支持

- 实现了华为 HMS 推送服务集成
- 实现了小米推送服务集成
- 实现了 OPPO 推送服务集成
- 实现了 vivo 推送服务集成
- 实现了荣耀推送服务集成
- 实现了 FCM 推送服务集成
- 添加了统一的厂商推送接口和检测机制
- 添加了推送配置 API 和存储管理
- 添加了推送令牌管理和设备注册功能
- 添加了模拟器环境的推送测试用例
这个提交包含在:
XuqmGroup 2026-05-05 17:54:59 +08:00
父节点 19e7b27d6e
当前提交 ea693d5c66
共有 21 个文件被更改,包括 698 次插入98 次删除

查看文件

@ -76,8 +76,11 @@ class PushSdkTest {
val expected = when (manufacturer) {
"HUAWEI" -> PushVendor.HUAWEI
"XIAOMI" -> PushVendor.XIAOMI
"REDMI", "POCO" -> PushVendor.XIAOMI
"OPPO" -> PushVendor.OPPO
"REALME", "ONEPLUS" -> PushVendor.OPPO
"VIVO" -> PushVendor.VIVO
"IQOO" -> PushVendor.VIVO
"HONOR" -> PushVendor.HONOR
else -> PushVendor.FCM
}
@ -102,11 +105,17 @@ class PushSdkTest {
// 1. initializeVendors 不应抛出任何异常
PushSDK.initializeVendors(appCtx)
// 2. 模拟器上 FCM token 通常为 null
val reg = PushSDK.currentRegistration(appCtx)
// 2. 模拟器上 FCM token 通常为 null;真机只校验厂商映射和流程不崩溃
val vendor = PushSDK.detectVendor()
// 不做 pushToken 非空断言emulator 无 Firebase,仅验证 vendor 正确
assertEquals("模拟器应检测到 FCM", PushVendor.FCM, vendor)
val expected = when (Build.MANUFACTURER.uppercase()) {
"HUAWEI" -> PushVendor.HUAWEI
"XIAOMI", "REDMI", "POCO" -> PushVendor.XIAOMI
"OPPO", "REALME", "ONEPLUS" -> PushVendor.OPPO
"VIVO", "IQOO" -> PushVendor.VIVO
"HONOR" -> PushVendor.HONOR
else -> PushVendor.FCM
}
assertEquals("MANUFACTURER=${Build.MANUFACTURER} 应检测到 $expected", expected, vendor)
// 3. setReceivePush 调用不应崩溃
PushSDK.setReceivePush(appCtx, USER_A, enabled = false)

查看文件

@ -11,6 +11,9 @@ android {
defaultConfig {
minSdk = libs.versions.minSdk.get().toInt()
consumerProguardFiles("consumer-rules.pro")
manifestPlaceholders["XUQM_VIVO_APP_ID"] = ""
manifestPlaceholders["XUQM_VIVO_APP_KEY"] = ""
manifestPlaceholders["XUQM_HONOR_APP_ID"] = ""
}
compileOptions {
@ -25,14 +28,12 @@ android {
dependencies {
api(project(":sdk-core"))
api(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.messaging)
// Optional vendor push SDKs — add the ones you need in your app module.
// These are NOT declared here because they require proprietary Maven repos:
// Huawei: com.huawei.hms:push (via Huawei Maven repo)
// Xiaomi: com.xiaomi.mipush:mipush (via Xiaomi Maven repo)
// OPPO: com.heytap.mcs:push (via OPPO Maven repo)
// vivo: com.vivo.pushsdk:pushsdk (via vivo Maven repo)
// Honor: com.hihonor.mcs:push (via Honor Maven repo)
api("com.huawei.hms:push:6.12.0.300")
api("com.hihonor.mcs:push:7.0.41.301")
api("io.github.hebeiliang.mipush:Push:2.0.0")
api("com.umeng.umsdk:oppo-push:3.0.0")
api("com.umeng.umsdk:vivo-push:4.0.6.0")
}

查看文件

@ -9,6 +9,10 @@
# ── Firebase Messaging Service Android resolves it by class name ────────────
-keep class com.xuqm.sdk.push.fcm.XuqmFirebaseMessagingService { *; }
-keep class com.xuqm.sdk.push.huawei.XuqmHuaweiPushService { *; }
-keep class com.xuqm.sdk.push.honor.XuqmHonorPushService { *; }
-keep class com.xuqm.sdk.push.vivo.XuqmVivoPushReceiver { *; }
-keep class com.xuqm.sdk.push.xiaomi.XuqmXiaomiPushReceiver { *; }
# ── Vendor push services instantiated via reflection inside PushSDK ─────────
-keep class com.xuqm.sdk.push.vendor.** { *; }

20
sdk-push/libs/README.md 普通文件
查看文件

@ -0,0 +1,20 @@
Vendor push SDK binaries can live here when a vendor only provides a console
download.
The Xuqm push SDK owns the vendor dependency graph. Huawei, Honor, Xiaomi,
OPPO, and vivo dependencies are declared by `sdk-push/build.gradle.kts`, so app
integrators do not need to add vendor SDKs themselves.
If a vendor SDK must be pinned to an official console-only binary for a tenant,
put the vendor `*.aar` or `*.jar` here before publishing the Xuqm push SDK.
Those binaries are picked up by `api(fileTree(...))`.
Xiaomi's official current AAR download page requires a Xiaomi Push console login
session. If an app must use that exact official package, download it from the
tenant's Xiaomi console and publish this SDK with the AAR in this directory.
`sdk-push` owns the Android manifest integration: permissions, push services,
notification click activity, vendor receivers and metadata placeholders are
declared in `src/main/AndroidManifest.xml`. Host apps should integrate the Xuqm
push SDK; vendor credentials are loaded from the tenant platform when the vendor
SDK supports runtime configuration.

查看文件

@ -1,6 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="com.coloros.mcs.permission.RECIEVE_MCS_MESSAGE" />
<uses-permission android:name="com.heytap.mcs.permission.RECIEVE_MCS_MESSAGE" />
<uses-permission android:name="com.hihonor.push.permission.READ_PUSH_NOTIFICATION_INFO" />
<uses-permission android:name="${applicationId}.permission.MIPUSH_RECEIVE" />
<permission
android:name="${applicationId}.permission.MIPUSH_RECEIVE"
android:protectionLevel="signature" />
<permission
android:name="${applicationId}.hihonor.permission.PROCESS_PUSH_MSG"
android:protectionLevel="signatureOrSystem" />
<queries>
<package android:name="com.huawei.hwid" />
<package android:name="com.huawei.hms" />
<package android:name="com.huawei.android.pushagent" />
<package android:name="com.hihonor.id" />
<package android:name="com.hihonor.appmarket" />
<intent>
<action android:name="com.hihonor.push.action.BIND_PUSH_SERVICE" />
</intent>
</queries>
<application>
<meta-data
android:name="push_kit_auto_init_enabled"
android:value="true" />
<meta-data
android:name="com.vivo.push.app_id"
android:value="${XUQM_VIVO_APP_ID}" />
<meta-data
android:name="com.vivo.push.api_key"
android:value="${XUQM_VIVO_APP_KEY}" />
<meta-data
android:name="com.hihonor.push.app_id"
android:value="${XUQM_HONOR_APP_ID}" />
<meta-data
android:name="com.hihonor.push.sdk_version"
android:value="7.0.41.301" />
<service
android:name="com.xuqm.sdk.push.fcm.XuqmFirebaseMessagingService"
android:exported="false">
@ -8,5 +52,102 @@
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service
android:name="com.xuqm.sdk.push.huawei.XuqmHuaweiPushService"
android:exported="false">
<intent-filter>
<action android:name="com.huawei.push.action.MESSAGING_EVENT" />
</intent-filter>
</service>
<service
android:name="com.xuqm.sdk.push.honor.XuqmHonorPushService"
android:exported="false">
<intent-filter>
<action android:name="com.hihonor.push.action.MESSAGING_EVENT" />
</intent-filter>
</service>
<service
android:name="com.xiaomi.push.service.XMPushService"
android:enabled="true"
android:process=":pushservice" />
<service
android:name="com.xiaomi.push.service.XMJobService"
android:enabled="true"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE"
android:process=":pushservice" />
<service
android:name="com.xiaomi.mipush.sdk.PushMessageHandler"
android:enabled="true"
android:exported="true"
android:permission="com.xiaomi.xmsf.permission.MIPUSH_RECEIVE" />
<service
android:name="com.xiaomi.mipush.sdk.MessageHandleService"
android:enabled="true" />
<receiver
android:name="com.xiaomi.push.service.receivers.PingReceiver"
android:exported="false"
android:process=":pushservice">
<intent-filter>
<action android:name="com.xiaomi.push.PING_TIMER" />
</intent-filter>
</receiver>
<activity
android:name="com.xiaomi.mipush.sdk.NotificationClickedActivity"
android:enabled="true"
android:excludeFromRecents="true"
android:exported="true"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<meta-data
android:name="supportStyle"
android:value="scene|voip" />
</activity>
<receiver
android:name="com.xuqm.sdk.push.xiaomi.XuqmXiaomiPushReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.xiaomi.mipush.RECEIVE_MESSAGE" />
</intent-filter>
<intent-filter>
<action android:name="com.xiaomi.mipush.MESSAGE_ARRIVED" />
</intent-filter>
<intent-filter>
<action android:name="com.xiaomi.mipush.ERROR" />
</intent-filter>
</receiver>
<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>
<service
android:name="com.vivo.push.sdk.service.CommandClientService"
android:exported="true" />
<receiver
android:name="com.xuqm.sdk.push.vivo.XuqmVivoPushReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.vivo.pushclient.action.RECEIVE" />
</intent-filter>
</receiver>
</application>
</manifest>

查看文件

@ -9,8 +9,10 @@ import com.xuqm.sdk.XuqmSDK
import com.xuqm.sdk.core.ServiceEndpointRegistry
import com.xuqm.sdk.network.ApiClient
import com.xuqm.sdk.push.api.PushApi
import com.xuqm.sdk.push.api.PushConfigApi
import com.xuqm.sdk.push.model.PushRegistrationSnapshot
import com.xuqm.sdk.push.model.PushVendor
import com.xuqm.sdk.push.model.PushVendorConfig
import com.xuqm.sdk.push.storage.PushRegistrationStore
import com.xuqm.sdk.push.vendor.FcmPushService
import com.xuqm.sdk.push.vendor.HonorPushService
@ -29,8 +31,11 @@ import java.util.concurrent.atomic.AtomicReference
object PushSDK {
private val api: PushApi get() = ApiClient.create(PushApi::class.java, ServiceEndpointRegistry.pushBaseUrl)
private val configApi: PushConfigApi get() = ApiClient.create(PushConfigApi::class.java, ServiceEndpointRegistry.controlBaseUrl)
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private val registeredUserId = AtomicReference<String?>(null)
@Volatile
private var cachedVendorConfig: PushVendorConfig? = null
fun currentRegistration(context: Context): PushRegistrationSnapshot? {
XuqmSDK.requireInit()
@ -59,6 +64,14 @@ object PushSDK {
registerDevice(context, userId)
}
fun refreshNativePushToken(
context: Context,
vendor: PushVendor = detectVendor(),
) {
XuqmSDK.requireInit()
ensureNativePushToken(context.applicationContext, vendor)
}
fun unbindImUser(userId: String) {
unregisterDevice(userId)
}
@ -77,6 +90,7 @@ object PushSDK {
api.setReceivePush(
appId = XuqmSDK.appKey,
userId = resolvedUserId,
deviceId = DeviceUtils.getDeviceId(context),
enabled = enabled,
)
if (enabled) {
@ -107,6 +121,11 @@ object PushSDK {
userId = userId,
vendor = vendor.name,
token = pushToken,
deviceId = DeviceUtils.getDeviceId(context),
brand = Build.MANUFACTURER.orEmpty(),
model = Build.MODEL.orEmpty(),
osVersion = DeviceUtils.getOsVersion(),
appVersion = appVersion(context),
)
registeredUserId.set(userId)
store(context).updateLastUserId(userId)
@ -126,7 +145,12 @@ object PushSDK {
deviceId = DeviceUtils.getDeviceId(XuqmSDK.appContext),
fallbackVendor = detectVendor(),
)
api.unregisterDevice(XuqmSDK.appKey, userId, reg?.vendor?.name ?: detectVendor().name)
api.unregisterDevice(
XuqmSDK.appKey,
userId,
reg?.vendor?.name ?: detectVendor().name,
DeviceUtils.getDeviceId(XuqmSDK.appContext),
)
registeredUserId.compareAndSet(userId, null)
store(XuqmSDK.appContext).updateLastUserId(null)
}
@ -146,8 +170,11 @@ object PushSDK {
return when (Build.MANUFACTURER.uppercase()) {
"HUAWEI" -> PushVendor.HUAWEI
"XIAOMI" -> PushVendor.XIAOMI
"REDMI", "POCO" -> PushVendor.XIAOMI
"OPPO" -> PushVendor.OPPO
"REALME", "ONEPLUS" -> PushVendor.OPPO
"VIVO" -> PushVendor.VIVO
"IQOO" -> PushVendor.VIVO
"HONOR" -> PushVendor.HONOR
else -> PushVendor.FCM
}
@ -156,14 +183,17 @@ object PushSDK {
fun initializeVendors(context: Context) {
val detectedVendor = detectVendor()
Log.i("XuqmPushSDK", "Detected push vendor: ${detectedVendor.name}")
vendorServices.forEach { service ->
if (service.vendor == detectedVendor && service.isAvailable(context)) {
Log.i("XuqmPushSDK", "Initializing push vendor: ${service.vendor.name}")
service.register(context)
scope.launch {
val config = loadVendorConfig()
vendorServices.forEach { service ->
if (service.vendor == detectedVendor && service.isAvailable(context)) {
Log.i("XuqmPushSDK", "Initializing push vendor: ${service.vendor.name}")
service.register(context, config)
}
}
if (detectedVendor == PushVendor.FCM) {
ensureNativePushToken(context)
}
}
if (detectedVendor == PushVendor.FCM) {
ensureNativePushToken(context)
}
}
@ -189,7 +219,13 @@ object PushSDK {
)?.receivePush ?: true
private fun ensureNativePushToken(context: Context) {
val vendor = detectVendor()
ensureNativePushToken(context, detectVendor())
}
private fun ensureNativePushToken(
context: Context,
vendor: PushVendor,
) {
val registration = currentRegistration(context)
if (registration?.pushToken?.isNotBlank() == true) return
@ -216,7 +252,9 @@ object PushSDK {
} else {
val service = vendorServices.firstOrNull { it.vendor == vendor }
if (service != null && service.isAvailable(context)) {
service.register(context)
scope.launch {
service.register(context, loadVendorConfig())
}
} else {
Log.w(
"XuqmPushSDK",
@ -244,4 +282,37 @@ object PushSDK {
}
}
}
private fun appVersion(context: Context): String? =
runCatching {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
"${packageInfo.versionName ?: ""}(${packageInfo.longVersionCode})"
} else {
@Suppress("DEPRECATION")
"${packageInfo.versionName ?: ""}(${packageInfo.versionCode})"
}
}.getOrNull()
private suspend fun loadVendorConfig(): PushVendorConfig {
cachedVendorConfig?.let { return it }
val loaded = runCatching {
val pushConfig = configApi.sdkConfig(XuqmSDK.appKey).data?.pushConfig
PushVendorConfig(
huaweiAppId = pushConfig?.getAsJsonObject("huawei")?.get("appId")?.asString.orEmpty(),
xiaomiAppId = pushConfig?.getAsJsonObject("xiaomi")?.get("appId")?.asString.orEmpty(),
xiaomiAppKey = pushConfig?.getAsJsonObject("xiaomi")?.get("appKey")?.asString.orEmpty(),
oppoAppKey = pushConfig?.getAsJsonObject("oppo")?.get("appKey")?.asString.orEmpty(),
oppoAppSecret = pushConfig?.getAsJsonObject("oppo")?.get("masterSecret")?.asString.orEmpty(),
vivoAppId = pushConfig?.getAsJsonObject("vivo")?.get("appId")?.asString.orEmpty(),
vivoAppKey = pushConfig?.getAsJsonObject("vivo")?.get("appKey")?.asString.orEmpty(),
honorAppId = pushConfig?.getAsJsonObject("honor")?.get("appId")?.asString.orEmpty(),
)
}.getOrElse { error ->
Log.w("XuqmPushSDK", "Unable to load tenant push config: ${error.message}")
PushVendorConfig()
}
cachedVendorConfig = loaded
return loaded
}
}

查看文件

@ -12,6 +12,12 @@ interface PushApi {
@Query("userId") userId: String,
@Query("vendor") vendor: String,
@Query("token") token: String,
@Query("deviceId") deviceId: String,
@Query("brand") brand: String,
@Query("model") model: String,
@Query("osVersion") osVersion: String,
@Query("platform") platform: String = "ANDROID",
@Query("appVersion") appVersion: String? = null,
)
@DELETE("api/push/unregister")
@ -19,12 +25,14 @@ interface PushApi {
@Query("appId") appId: String,
@Query("userId") userId: String,
@Query("vendor") vendor: String,
@Query("deviceId") deviceId: String? = null,
)
@POST("api/push/receive-push")
suspend fun setReceivePush(
@Query("appId") appId: String,
@Query("userId") userId: String,
@Query("deviceId") deviceId: String? = null,
@Query("enabled") enabled: Boolean,
)
}

查看文件

@ -0,0 +1,21 @@
package com.xuqm.sdk.push.api
import com.google.gson.JsonObject
import retrofit2.http.GET
import retrofit2.http.Query
interface PushConfigApi {
@GET("api/sdk/config")
suspend fun sdkConfig(
@Query("appId") appId: String,
@Query("platform") platform: String = "ANDROID",
): SdkConfigResponse
}
data class SdkConfigResponse(
val data: SdkConfigData? = null,
)
data class SdkConfigData(
val pushConfig: JsonObject? = null,
)

查看文件

@ -0,0 +1,23 @@
package com.xuqm.sdk.push.honor
import android.util.Log
import com.hihonor.push.sdk.HonorMessageService
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
class XuqmHonorPushService : HonorMessageService() {
override fun onNewToken(token: String?) {
super.onNewToken(token)
if (token.isNullOrBlank()) return
runCatching {
PushSDK.updateNativePushToken(applicationContext, PushVendor.HONOR, token)
}.onFailure { error ->
Log.w(TAG, "Unable to persist Honor push token: ${error.message}")
}
}
companion object {
private const val TAG = "XuqmHonorPush"
}
}

查看文件

@ -0,0 +1,39 @@
package com.xuqm.sdk.push.huawei
import android.os.Bundle
import android.util.Log
import com.huawei.hms.push.HmsMessageService
import com.huawei.hms.push.RemoteMessage
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
class XuqmHuaweiPushService : HmsMessageService() {
override fun onNewToken(token: String?) {
super.onNewToken(token)
persistToken(token)
}
override fun onNewToken(token: String?, bundle: Bundle?) {
super.onNewToken(token, bundle)
persistToken(token)
}
override fun onMessageReceived(message: RemoteMessage?) {
super.onMessageReceived(message)
Log.d(TAG, "Huawei push message received")
}
private fun persistToken(token: String?) {
if (token.isNullOrBlank()) return
runCatching {
PushSDK.updateNativePushToken(applicationContext, PushVendor.HUAWEI, token)
}.onFailure { error ->
Log.w(TAG, "Unable to persist Huawei push token: ${error.message}")
}
}
companion object {
private const val TAG = "XuqmHuaweiPush"
}
}

查看文件

@ -0,0 +1,12 @@
package com.xuqm.sdk.push.model
data class PushVendorConfig(
val huaweiAppId: String = "",
val xiaomiAppId: String = "",
val xiaomiAppKey: String = "",
val oppoAppKey: String = "",
val oppoAppSecret: String = "",
val vivoAppId: String = "",
val vivoAppKey: String = "",
val honorAppId: String = "",
)

查看文件

@ -4,6 +4,7 @@ import android.content.Context
import android.util.Log
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
import com.xuqm.sdk.push.model.PushVendorConfig
/**
* FCM (Firebase Cloud Messaging) push integration.
@ -23,7 +24,7 @@ class FcmPushService : PushVendorInterface {
}.getOrDefault(false)
}
override fun register(context: Context) {
override fun register(context: Context, config: PushVendorConfig) {
runCatching {
val fcmClass = Class.forName("com.google.firebase.messaging.FirebaseMessaging")
val instance = fcmClass.getMethod("getInstance").invoke(null)

查看文件

@ -4,6 +4,8 @@ import android.content.Context
import android.util.Log
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
import com.xuqm.sdk.push.model.PushVendorConfig
import java.lang.reflect.Proxy
/**
* 荣耀推送集成框架
@ -31,13 +33,21 @@ class HonorPushService : PushVendorInterface {
}.getOrDefault(false)
}
override fun register(context: Context) {
override fun register(context: Context, config: PushVendorConfig) {
runCatching {
val honorPushClass = Class.forName("com.hihonor.push.sdk.HonorPushClient")
val instance = honorPushClass.getMethod("getInstance").invoke(null)
// HonorPushClient.getInstance().init(context, true)
val supported = runCatching {
honorPushClass.getMethod("checkSupportHonorPush", Context::class.java)
.invoke(instance, context) as? Boolean
}.getOrDefault(true)
if (supported != true) {
Log.w(TAG, "Honor push is not supported on this device")
return
}
honorPushClass.getMethod("init", Context::class.java, Boolean::class.javaPrimitiveType)
.invoke(instance, context, true)
.invoke(instance, context, false)
requestToken(context, honorPushClass, instance)
Log.i(TAG, "Honor push registration requested")
}.onFailure { error ->
Log.w(TAG, "Honor push registration failed: ${error.message}")
@ -58,4 +68,36 @@ class HonorPushService : PushVendorInterface {
companion object {
private const val TAG = "HonorPushService"
}
private fun requestToken(
context: Context,
honorPushClass: Class<*>,
instance: Any,
) {
runCatching {
val callbackClass = Class.forName("com.hihonor.push.sdk.HonorPushCallback")
val callback = Proxy.newProxyInstance(
callbackClass.classLoader,
arrayOf(callbackClass),
) { _, method, args ->
when (method.name) {
"onSuccess" -> {
val token = args?.firstOrNull() as? String
if (!token.isNullOrBlank()) {
PushSDK.updateNativePushToken(context, vendor, token)
Log.i(TAG, "Honor push token acquired")
}
}
"onFailure" -> {
Log.w(TAG, "Honor push token failed: ${args?.joinToString()}")
}
}
null
}
honorPushClass.getMethod("getPushToken", callbackClass)
.invoke(instance, callback)
}.onFailure { error ->
Log.w(TAG, "Honor push token request failed: ${error.message}")
}
}
}

查看文件

@ -4,9 +4,10 @@ import android.content.Context
import android.util.Log
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
import com.xuqm.sdk.push.model.PushVendorConfig
/**
* 华为推送集成框架
* Android 华为 HMS 推送集成框架
*
* 需要添加 HMS Push SDK 依赖
* ```groovy
@ -36,12 +37,12 @@ class HuaweiPushService : PushVendorInterface {
}.getOrDefault(false)
}
override fun register(context: Context) {
override fun register(context: Context, config: PushVendorConfig) {
runCatching {
val hmsClass = Class.forName("com.huawei.hms.aaid.HmsInstanceId")
val instance = hmsClass.getMethod("getInstance", Context::class.java)
.invoke(null, context)
val appId = getAppId(context)
val appId = config.huaweiAppId.ifBlank { getAppId(context) }
if (appId.isNotBlank()) {
val token = hmsClass.getMethod(
"getToken",

查看文件

@ -6,21 +6,11 @@ import android.os.Bundle
import android.util.Log
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
import com.xuqm.sdk.push.model.PushVendorConfig
import java.lang.reflect.Proxy
/**
* OPPO 推送集成框架
*
* 需要添加 OPPO Push SDK 依赖
* ```groovy
* implementation 'com.heytap.mcs:push:3.x.x'
* ```
*
* `AndroidManifest.xml` `<application>` 下配置
* ```xml
* <meta-data android:name="XUQM_OPPO_APP_KEY" android:value="xxxxxxxx" />
* <meta-data android:name="XUQM_OPPO_APP_SECRET" android:value="xxxxxxxx" />
* ```
* OPPO 推送集成OPPO Push 依赖由 sdk-push 自身声明注册参数优先来自租户 PUSH 配置
*/
class OppoPushService : PushVendorInterface {
@ -28,49 +18,26 @@ class OppoPushService : PushVendorInterface {
override fun isAvailable(context: Context): Boolean {
return runCatching {
Class.forName("com.heytap.mcssdk.PushManager")
resolvePushManagerClass()
true
}.getOrDefault(false)
}
override fun register(context: Context) {
override fun register(context: Context, config: PushVendorConfig) {
runCatching {
val pushManagerClass = Class.forName("com.heytap.mcssdk.PushManager")
val instance = pushManagerClass.getMethod("getInstance").invoke(null)
val meta = context.packageManager.getApplicationInfo(
context.packageName, PackageManager.GET_META_DATA
).metaData ?: Bundle.EMPTY
val appKey = meta.getString("XUQM_OPPO_APP_KEY", "")
val appSecret = meta.getString("XUQM_OPPO_APP_SECRET", "")
val appKey = config.oppoAppKey.ifBlank { meta.getString("XUQM_OPPO_APP_KEY", "") }
val appSecret = config.oppoAppSecret.ifBlank { meta.getString("XUQM_OPPO_APP_SECRET", "") }
if (appKey.isNotBlank() && appSecret.isNotBlank()) {
val callbackClass = Class.forName("com.heytap.mcssdk.callback.PushCallback")
val proxy = Proxy.newProxyInstance(
callbackClass.classLoader,
arrayOf(callbackClass)
) { _, method, args ->
when (method.name) {
"onRegister" -> {
val regId = args?.getOrNull(1) as? String
if (!regId.isNullOrBlank()) {
PushSDK.updateNativePushToken(context, vendor, regId)
Log.i(TAG, "OPPO push token acquired")
}
}
}
null
if (!registerWithHeytapMsp(context, appKey, appSecret)) {
registerWithLegacyMcs(context, appKey, appSecret)
}
pushManagerClass.getMethod(
"register",
Context::class.java,
String::class.java,
String::class.java,
callbackClass,
).invoke(instance, context, appKey, appSecret, proxy)
Log.i(TAG, "OPPO push registration requested")
} else {
Log.w(TAG, "OPPO appKey/appSecret not configured in meta-data, skipping registration")
Log.w(TAG, "OPPO appKey/appSecret not configured, skipping registration")
}
}.onFailure { error ->
Log.w(TAG, "OPPO push registration failed: ${error.message}")
@ -79,10 +46,13 @@ class OppoPushService : PushVendorInterface {
override fun unregister(context: Context) {
runCatching {
val pushManagerClass = Class.forName("com.heytap.mcssdk.PushManager")
val instance = pushManagerClass.getMethod("getInstance").invoke(null)
pushManagerClass.getMethod("unRegister", Context::class.java)
.invoke(instance, context)
val pushManagerClass = resolvePushManagerClass()
if (pushManagerClass.name == "com.heytap.msp.push.HeytapPushManager") {
pushManagerClass.getMethod("unRegister").invoke(null)
} else {
val instance = pushManagerClass.getMethod("getInstance").invoke(null)
pushManagerClass.getMethod("unRegister", Context::class.java).invoke(instance, context)
}
Log.i(TAG, "OPPO push unregistered")
}.onFailure { error ->
Log.w(TAG, "OPPO push unregistration failed: ${error.message}")
@ -91,5 +61,90 @@ class OppoPushService : PushVendorInterface {
companion object {
private const val TAG = "OppoPushService"
private fun resolvePushManagerClass(): Class<*> {
return runCatching {
Class.forName("com.heytap.msp.push.HeytapPushManager")
}.getOrElse {
Class.forName("com.heytap.mcssdk.PushManager")
}
}
}
private fun registerWithHeytapMsp(
context: Context,
appKey: String,
appSecret: String,
): Boolean {
val pushManagerClass = runCatching {
Class.forName("com.heytap.msp.push.HeytapPushManager")
}.getOrNull() ?: return false
runCatching {
pushManagerClass.getMethod("init", Context::class.java, java.lang.Boolean.TYPE)
.invoke(null, context, false)
}
val supported = runCatching {
pushManagerClass.getMethod("isSupportPush", Context::class.java)
.invoke(null, context) as? Boolean
}.getOrElse {
runCatching { pushManagerClass.getMethod("isSupportPush").invoke(null) as? Boolean }
.getOrDefault(true)
} ?: true
if (!supported) {
Log.w(TAG, "OPPO push is not supported on this device")
return true
}
val callbackClass = Class.forName("com.heytap.msp.push.callback.ICallBackResultService")
val callback = buildCallback(context.applicationContext, callbackClass)
pushManagerClass.getMethod(
"register",
Context::class.java,
String::class.java,
String::class.java,
callbackClass,
).invoke(null, context, appKey, appSecret, callback)
runCatching { pushManagerClass.getMethod("requestNotificationPermission").invoke(null) }
return true
}
private fun registerWithLegacyMcs(
context: Context,
appKey: String,
appSecret: String,
) {
val pushManagerClass = Class.forName("com.heytap.mcssdk.PushManager")
val instance = pushManagerClass.getMethod("getInstance").invoke(null)
val callbackClass = Class.forName("com.heytap.mcssdk.callback.PushCallback")
val callback = buildCallback(context.applicationContext, callbackClass)
pushManagerClass.getMethod(
"register",
Context::class.java,
String::class.java,
String::class.java,
callbackClass,
).invoke(instance, context, appKey, appSecret, callback)
}
private fun buildCallback(context: Context, callbackClass: Class<*>): Any {
return Proxy.newProxyInstance(
callbackClass.classLoader,
arrayOf(callbackClass)
) { _, method, args ->
if (method.name == "onRegister") {
val code = args?.getOrNull(0) as? Int
val regId = args?.getOrNull(1) as? String
if ((code == null || code == 0) && !regId.isNullOrBlank()) {
PushSDK.updateNativePushToken(context, vendor, regId)
Log.i(TAG, "OPPO push token acquired")
} else {
Log.w(TAG, "OPPO push register callback code=$code")
}
}
null
}
}
}

查看文件

@ -2,6 +2,7 @@ package com.xuqm.sdk.push.vendor
import android.content.Context
import com.xuqm.sdk.push.model.PushVendor
import com.xuqm.sdk.push.model.PushVendorConfig
/**
* 厂商推送服务接口
@ -22,7 +23,7 @@ interface PushVendorInterface {
/**
* 注册厂商推送服务触发获取推送 Token 的流程
*/
fun register(context: Context)
fun register(context: Context, config: PushVendorConfig = PushVendorConfig())
/**
* 注销厂商推送服务

查看文件

@ -4,6 +4,8 @@ import android.content.Context
import android.util.Log
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
import com.xuqm.sdk.push.model.PushVendorConfig
import java.lang.reflect.Proxy
/**
* vivo 推送集成框架
@ -31,13 +33,33 @@ class VivoPushService : PushVendorInterface {
}.getOrDefault(false)
}
override fun register(context: Context) {
override fun register(context: Context, config: PushVendorConfig) {
runCatching {
val pushClientClass = Class.forName("com.vivo.push.PushClient")
val instance = pushClientClass.getMethod("getInstance", Context::class.java)
.invoke(null, context)
// PushClient.getInstance(context).initialize()
pushClientClass.getMethod("initialize").invoke(instance)
val listenerClass = Class.forName("com.vivo.push.IPushActionListener")
val listener = Proxy.newProxyInstance(
listenerClass.classLoader,
arrayOf(listenerClass),
) { _, method, args ->
if (method.name == "onStateChanged") {
val state = args?.firstOrNull() as? Int
if (state == 0) {
persistRegId(context, pushClientClass, instance)
} else {
Log.w(TAG, "Vivo push turnOnPush state=$state")
}
}
null
}
runCatching {
pushClientClass.getMethod("turnOnPush", listenerClass)
.invoke(instance, listener)
}.onFailure {
persistRegId(context, pushClientClass, instance)
}
Log.i(TAG, "Vivo push registration requested")
}.onFailure { error ->
Log.w(TAG, "Vivo push registration failed: ${error.message}")
@ -59,4 +81,18 @@ class VivoPushService : PushVendorInterface {
companion object {
private const val TAG = "VivoPushService"
}
private fun persistRegId(
context: Context,
pushClientClass: Class<*>,
instance: Any,
) {
val regId = runCatching {
pushClientClass.getMethod("getRegId").invoke(instance) as? String
}.getOrNull()
if (!regId.isNullOrBlank()) {
PushSDK.updateNativePushToken(context, vendor, regId)
Log.i(TAG, "Vivo push token acquired")
}
}
}

查看文件

@ -6,26 +6,13 @@ import android.os.Bundle
import android.util.Log
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
import com.xuqm.sdk.push.model.PushVendorConfig
/**
* 小米推送集成框架
*
* 需要添加 MiPush SDK 依赖
* ```groovy
* implementation 'com.xiaomi.mipush:mipush:5.x.x'
* ```
*
* `AndroidManifest.xml` `<application>` 下配置
* ```xml
* <meta-data android:name="XUQM_XIAOMI_APP_ID" android:value="288230376xxxxxxxx" />
* <meta-data android:name="XUQM_XIAOMI_APP_KEY" android:value="xxxxxxxxxxxx" />
* ```
*
* 并在自定义 `PushMessageReceiver` `onReceiveRegisterResult()` 回调中获取 token
* 然后调用
* ```kotlin
* PushSDK.updateNativePushToken(context, PushVendor.XIAOMI, token)
* ```
* MiPush AAR 需要随 sdk-push 发布注册参数优先来自租户 PUSH 配置
* 旧版本 manifest meta-data 仅作为兼容兜底
*/
class XiaomiPushService : PushVendorInterface {
@ -38,14 +25,14 @@ class XiaomiPushService : PushVendorInterface {
}.getOrDefault(false)
}
override fun register(context: Context) {
override fun register(context: Context, config: PushVendorConfig) {
runCatching {
val miPushClass = Class.forName("com.xiaomi.mipush.sdk.MiPushClient")
val meta = context.packageManager.getApplicationInfo(
context.packageName, PackageManager.GET_META_DATA
).metaData ?: Bundle.EMPTY
val appId = meta.getString("XUQM_XIAOMI_APP_ID", "")
val appKey = meta.getString("XUQM_XIAOMI_APP_KEY", "")
val appId = config.xiaomiAppId.ifBlank { meta.getString("XUQM_XIAOMI_APP_ID", "") }
val appKey = config.xiaomiAppKey.ifBlank { meta.getString("XUQM_XIAOMI_APP_KEY", "") }
if (appId.isNotBlank() && appKey.isNotBlank()) {
miPushClass.getMethod(
"registerPush",
@ -54,8 +41,9 @@ class XiaomiPushService : PushVendorInterface {
String::class.java,
).invoke(null, context, appId, appKey)
Log.i(TAG, "Xiaomi push registration requested")
pollRegId(context, miPushClass)
} else {
Log.w(TAG, "Xiaomi appId/appKey not configured in meta-data, skipping registration")
Log.w(TAG, "Xiaomi appId/appKey not configured, skipping registration")
}
}.onFailure { error ->
Log.w(TAG, "Xiaomi push registration failed: ${error.message}")
@ -73,6 +61,24 @@ class XiaomiPushService : PushVendorInterface {
}
}
private fun pollRegId(context: Context, miPushClass: Class<*>) {
Thread {
repeat(8) {
val regId = runCatching {
miPushClass.getMethod("getRegId", Context::class.java)
.invoke(null, context) as? String
}.getOrNull()
if (!regId.isNullOrBlank()) {
PushSDK.updateNativePushToken(context, vendor, regId)
Log.i(TAG, "Xiaomi push token acquired")
return@Thread
}
Thread.sleep(1000)
}
Log.w(TAG, "Xiaomi push token not ready after registration")
}.start()
}
companion object {
private const val TAG = "XiaomiPushService"
}

查看文件

@ -0,0 +1,47 @@
package com.xuqm.sdk.push.vivo
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
class XuqmVivoPushReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
if (intent == null) return
val appContext = context.applicationContext
val regId = extractRegId(intent) ?: currentRegId(appContext)
if (regId.isNullOrBlank()) return
runCatching {
PushSDK.updateNativePushToken(appContext, PushVendor.VIVO, regId)
}.onFailure { error ->
Log.w(TAG, "Unable to persist vivo push token: ${error.message}")
}
}
private fun extractRegId(intent: Intent): String? {
val extras = intent.extras ?: return null
for (key in extras.keySet()) {
val value = extras.get(key) as? String ?: continue
if (key.contains("reg", ignoreCase = true) || key.contains("token", ignoreCase = true)) {
return value.takeIf { it.isNotBlank() }
}
}
return null
}
private fun currentRegId(context: Context): String? {
return runCatching {
val pushClientClass = Class.forName("com.vivo.push.PushClient")
val instance = pushClientClass.getMethod("getInstance", Context::class.java)
.invoke(null, context)
pushClientClass.getMethod("getRegId").invoke(instance) as? String
}.getOrNull()
}
companion object {
private const val TAG = "XuqmVivoPush"
}
}

查看文件

@ -0,0 +1,60 @@
package com.xuqm.sdk.push.xiaomi
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
class XuqmXiaomiPushReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
if (intent == null) return
val appContext = context.applicationContext
val regId = extractRegId(intent)
if (!regId.isNullOrBlank()) {
runCatching {
PushSDK.updateNativePushToken(appContext, PushVendor.XIAOMI, regId)
}.onFailure { error ->
Log.w(TAG, "Unable to persist Xiaomi push token: ${error.message}")
}
return
}
runCatching {
PushSDK.refreshNativePushToken(appContext, PushVendor.XIAOMI)
}.onFailure { error ->
Log.d(TAG, "Xiaomi push callback ignored before SDK init: ${error.message}")
}
}
private fun extractRegId(intent: Intent): String? {
val extras = intent.extras ?: return null
for (key in extras.keySet()) {
val value = extras.get(key) ?: continue
if (value is String && key.contains("reg", ignoreCase = true)) {
return value.takeIf { it.isNotBlank() }
}
if (value.javaClass.name == "com.xiaomi.mipush.sdk.MiPushCommandMessage") {
val resultCode = runCatching {
value.javaClass.getMethod("getResultCode").invoke(value) as? Long
}.getOrNull()
val command = runCatching {
value.javaClass.getMethod("getCommand").invoke(value) as? String
}.getOrNull()
val arguments = runCatching {
@Suppress("UNCHECKED_CAST")
value.javaClass.getMethod("getCommandArguments").invoke(value) as? List<String>
}.getOrNull()
if ((resultCode == null || resultCode == 0L) && command.equals("register", ignoreCase = true)) {
return arguments?.firstOrNull { it.isNotBlank() }
}
}
}
return null
}
companion object {
private const val TAG = "XuqmMiPushReceiver"
}
}

查看文件

@ -1,6 +1,7 @@
pluginManagement {
repositories {
maven(url = "https://nexus.xuqinmin.com/repository/android/")
maven(url = "https://developer.hihonor.com/repo")
google()
mavenCentral()
gradlePluginPortal()
@ -11,6 +12,7 @@ dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
maven(url = "https://nexus.xuqinmin.com/repository/android/")
maven(url = "https://developer.hihonor.com/repo")
google()
mavenCentral()
}