feat(push): 完成 VIVO 推送集成 + Xiaomi 凭据强校验

- XuqmVivoPushReceiver 从 BroadcastReceiver 改为继承 OpenClientPushMessageReceiver,通过 onReceiveRegId 回调获取 regId(修复之前 extractRegId 的脆弱 key 匹配)
- VIVO receiver android:exported 从 false 改为 true(跨进程广播必须可导出)
- VivoPushService 失败日志级别从 Log.w 提升为 Log.e
- Xiaomi/VIVO 凭据必须来自控制台,获取不到时抛出异常(移除 meta-data 本地兜底)
- PushSDK.loadVendorConfig 失败时向上抛异常,不再 swallow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-06-25 16:57:18 +08:00
父节点 4f4f8564df
当前提交 344efbb0aa
共有 5 个文件被更改,包括 29 次插入54 次删除

查看文件

@ -151,7 +151,7 @@
android:exported="true" /> android:exported="true" />
<receiver <receiver
android:name="com.xuqm.sdk.push.vivo.XuqmVivoPushReceiver" android:name="com.xuqm.sdk.push.vivo.XuqmVivoPushReceiver"
android:exported="false"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="com.vivo.pushclient.action.RECEIVE" /> <action android:name="com.vivo.pushclient.action.RECEIVE" />
</intent-filter> </intent-filter>

查看文件

@ -304,8 +304,8 @@ object PushSDK {
honorAppId = pushConfig?.getAsJsonObject("honor")?.get("appId")?.asString.orEmpty(), honorAppId = pushConfig?.getAsJsonObject("honor")?.get("appId")?.asString.orEmpty(),
) )
}.getOrElse { error -> }.getOrElse { error ->
Log.w("XuqmPushSDK", "Unable to load push vendor config: ${error.message}") Log.e("XuqmPushSDK", "Failed to load push vendor config from control plane: ${error.message}")
PushVendorConfig() throw IllegalStateException("Push vendor config unavailable: ${error.message}", error)
} }
cachedVendorConfig = loaded cachedVendorConfig = loaded
cachedConfigAt = now cachedConfigAt = now

查看文件

@ -62,7 +62,7 @@ class VivoPushService : PushVendorInterface {
} }
Log.i(TAG, "Vivo push registration requested") Log.i(TAG, "Vivo push registration requested")
}.onFailure { error -> }.onFailure { error ->
Log.w(TAG, "Vivo push registration failed: ${error.message}") Log.e(TAG, "Vivo push registration failed: ${error.message}")
} }
} }

查看文件

@ -1,18 +1,10 @@
package com.xuqm.sdk.push.vendor package com.xuqm.sdk.push.vendor
import android.content.Context import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log import android.util.Log
import com.xuqm.sdk.push.model.PushVendor import com.xuqm.sdk.push.model.PushVendor
import com.xuqm.sdk.push.model.PushVendorConfig import com.xuqm.sdk.push.model.PushVendorConfig
/**
* 小米推送集成框架
*
* MiPush AAR 需要随 sdk-push 发布注册参数优先来自租户 PUSH 配置
* 旧版本 manifest meta-data 仅作为兼容兜底
*/
class XiaomiPushService : PushVendorInterface { class XiaomiPushService : PushVendorInterface {
override val vendor: PushVendor = PushVendor.XIAOMI override val vendor: PushVendor = PushVendor.XIAOMI
@ -27,12 +19,11 @@ class XiaomiPushService : PushVendorInterface {
override fun register(context: Context, config: PushVendorConfig) { override fun register(context: Context, config: PushVendorConfig) {
runCatching { runCatching {
val miPushClass = Class.forName("com.xiaomi.mipush.sdk.MiPushClient") val miPushClass = Class.forName("com.xiaomi.mipush.sdk.MiPushClient")
val meta = context.packageManager.getApplicationInfo( val appId = config.xiaomiAppId
context.packageName, PackageManager.GET_META_DATA val appKey = config.xiaomiAppKey
).metaData ?: Bundle.EMPTY check(appId.isNotBlank() && appKey.isNotBlank()) {
val appId = config.xiaomiAppId.ifBlank { meta.getString("XUQM_XIAOMI_APP_ID", "") } "Xiaomi push credentials not configured on control plane (xiaomi.appId / xiaomi.appKey required)"
val appKey = config.xiaomiAppKey.ifBlank { meta.getString("XUQM_XIAOMI_APP_KEY", "") } }
if (appId.isNotBlank() && appKey.isNotBlank()) {
miPushClass.getMethod( miPushClass.getMethod(
"registerPush", "registerPush",
Context::class.java, Context::class.java,
@ -40,11 +31,8 @@ class XiaomiPushService : PushVendorInterface {
String::class.java, String::class.java,
).invoke(null, context, appId, appKey) ).invoke(null, context, appId, appKey)
Log.i(TAG, "Xiaomi push registration requested") Log.i(TAG, "Xiaomi push registration requested")
} else {
Log.w(TAG, "Xiaomi appId/appKey not configured, skipping registration")
}
}.onFailure { error -> }.onFailure { error ->
Log.w(TAG, "Xiaomi push registration failed: ${error.message}") Log.e(TAG, "Xiaomi push registration failed: ${error.message}")
} }
} }

查看文件

@ -1,44 +1,31 @@
package com.xuqm.sdk.push.vivo package com.xuqm.sdk.push.vivo
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent
import android.util.Log import android.util.Log
import com.vivo.push.model.UnvarnishedMessage
import com.vivo.push.model.UPSNotificationMessage
import com.vivo.push.sdk.OpenClientPushMessageReceiver
import com.xuqm.sdk.push.PushSDK import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor import com.xuqm.sdk.push.model.PushVendor
class XuqmVivoPushReceiver : BroadcastReceiver() { class XuqmVivoPushReceiver : OpenClientPushMessageReceiver() {
override fun onReceive(context: Context, intent: Intent?) { override fun onReceiveRegId(context: Context, regId: String) {
if (intent == null) return if (regId.isBlank()) return
val appContext = context.applicationContext Log.i(TAG, "Vivo push registered: regId=$regId")
val regId = extractRegId(intent) ?: currentRegId(appContext)
if (regId.isNullOrBlank()) return
runCatching { runCatching {
PushSDK.updateNativePushToken(appContext, PushVendor.VIVO, regId) PushSDK.updateNativePushToken(context.applicationContext, PushVendor.VIVO, regId)
}.onFailure { error -> }.onFailure { error ->
Log.w(TAG, "Unable to persist vivo push token: ${error.message}") Log.e(TAG, "Unable to persist vivo push token: ${error.message}")
} }
} }
private fun extractRegId(intent: Intent): String? { override fun onTransmissionMessage(context: Context, message: UnvarnishedMessage) {
val extras = intent.extras ?: return null Log.d(TAG, "Vivo pass-through message received: ${message.content}")
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? { override fun onNotificationMessageClicked(context: Context, message: UPSNotificationMessage) {
return runCatching { Log.d(TAG, "Vivo notification clicked: ${message.title}")
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 { companion object {