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" />
<receiver
android:name="com.xuqm.sdk.push.vivo.XuqmVivoPushReceiver"
android:exported="false">
android:exported="true">
<intent-filter>
<action android:name="com.vivo.pushclient.action.RECEIVE" />
</intent-filter>

查看文件

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

查看文件

@ -62,7 +62,7 @@ class VivoPushService : PushVendorInterface {
}
Log.i(TAG, "Vivo push registration requested")
}.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
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import com.xuqm.sdk.push.model.PushVendor
import com.xuqm.sdk.push.model.PushVendorConfig
/**
* 小米推送集成框架
*
* MiPush AAR 需要随 sdk-push 发布注册参数优先来自租户 PUSH 配置
* 旧版本 manifest meta-data 仅作为兼容兜底
*/
class XiaomiPushService : PushVendorInterface {
override val vendor: PushVendor = PushVendor.XIAOMI
@ -27,24 +19,20 @@ class XiaomiPushService : PushVendorInterface {
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 = 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",
Context::class.java,
String::class.java,
String::class.java,
).invoke(null, context, appId, appKey)
Log.i(TAG, "Xiaomi push registration requested")
} else {
Log.w(TAG, "Xiaomi appId/appKey not configured, skipping registration")
val appId = config.xiaomiAppId
val appKey = config.xiaomiAppKey
check(appId.isNotBlank() && appKey.isNotBlank()) {
"Xiaomi push credentials not configured on control plane (xiaomi.appId / xiaomi.appKey required)"
}
miPushClass.getMethod(
"registerPush",
Context::class.java,
String::class.java,
String::class.java,
).invoke(null, context, appId, appKey)
Log.i(TAG, "Xiaomi push registration requested")
}.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
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
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.model.PushVendor
class XuqmVivoPushReceiver : BroadcastReceiver() {
class XuqmVivoPushReceiver : OpenClientPushMessageReceiver() {
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
override fun onReceiveRegId(context: Context, regId: String) {
if (regId.isBlank()) return
Log.i(TAG, "Vivo push registered: regId=$regId")
runCatching {
PushSDK.updateNativePushToken(appContext, PushVendor.VIVO, regId)
PushSDK.updateNativePushToken(context.applicationContext, PushVendor.VIVO, regId)
}.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? {
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
override fun onTransmissionMessage(context: Context, message: UnvarnishedMessage) {
Log.d(TAG, "Vivo pass-through message received: ${message.content}")
}
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()
override fun onNotificationMessageClicked(context: Context, message: UPSNotificationMessage) {
Log.d(TAG, "Vivo notification clicked: ${message.title}")
}
companion object {