feat(push): 通知点击事件通过回调给 App,不在 SDK 内解析业务逻辑

- 新增 PushNotificationClickEvent(vendor/title/content/params/skipContent)
- PushSDK.setNotificationClickListener 供 App 注册通知点击回调
- PushSDK.dispatchNotificationClick(internal):触发回调 + 启动宿主 Activity
  携带原始 extras(EXTRA_PUSH_PARAMS/EXTRA_PUSH_SKIP_CONTENT/EXTRA_PUSH_VENDOR),
  冷启动场景由 App 自行从 Intent extras 解析
- XuqmVivoPushReceiver 仅负责把 UPSNotificationMessage 转为 event 后分发,
  不做任何业务层解析(URL 跳转逻辑由 App 实现)
- 删除 PushDeepLinkHandler(职责越界)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-06-25 18:03:37 +08:00
父节点 f63ae07bcb
当前提交 fc43885f1e
共有 4 个文件被更改,包括 58 次插入28 次删除

查看文件

@ -1,20 +0,0 @@
package com.xuqm.sdk.push
import android.content.Context
import android.content.Intent
import android.util.Log
object PushDeepLinkHandler {
const val EXTRA_PUSH_URL = "xuqm_push_url"
private const val TAG = "PushDeepLinkHandler"
fun open(context: Context, url: String) {
val pm = context.packageManager
val intent = pm.getLaunchIntentForPackage(context.packageName) ?: return
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
intent.putExtra(EXTRA_PUSH_URL, url)
Log.d(TAG, "Opening app with push URL: $url")
context.startActivity(intent)
}
}

查看文件

@ -25,6 +25,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import com.xuqm.sdk.push.model.PushNotificationClickEvent
import java.util.concurrent.atomic.AtomicReference
object PushSDK {
@ -37,9 +38,27 @@ object PushSDK {
private val lastRegisteredDeviceKey = AtomicReference<String?>(null)
@Volatile private var cachedVendorConfig: PushVendorConfig? = null
@Volatile private var cachedConfigAt: Long = 0L
@Volatile private var notificationClickListener: ((PushNotificationClickEvent) -> Unit)? = null
/** 推送通知点击 Intent extras 的键:原始 params JSON */
const val EXTRA_PUSH_PARAMS = "xuqm_push_params"
/** 推送通知点击 Intent extras 的键skipContent */
const val EXTRA_PUSH_SKIP_CONTENT = "xuqm_push_skip_content"
/** 推送通知点击 Intent extras 的键:厂商名 */
const val EXTRA_PUSH_VENDOR = "xuqm_push_vendor"
// ── Public API (spec-aligned) ──────────────────────────────────────────────
/**
* 注册推送通知点击回调
* App 在运行时会通过此回调收到通知点击事件
* App 未运行冷启动通知数据通过 Intent extras 传递到启动 Activity
* Intent extras [EXTRA_PUSH_PARAMS][EXTRA_PUSH_SKIP_CONTENT][EXTRA_PUSH_VENDOR]
*/
fun setNotificationClickListener(listener: ((PushNotificationClickEvent) -> Unit)?) {
notificationClickListener = listener
}
/**
* 设置离线推送开关
* 关闭后用户不会收到离线推送但在线消息仍正常接收
@ -91,6 +110,27 @@ object PushSDK {
fun notificationChannelIdFor(context: Context, routeType: String): String? =
PushNotificationChannelManager.channelIdFor(context.applicationContext, routeType)
// ── Internal notification click dispatch ──────────────────────────────────
/**
* 由各厂商 Receiver 在通知被点击时调用
* 优先触发已注册的 [notificationClickListener]App 在前台/后台均有效
* 同时启动宿主 App携带原始 params extras 供冷启动场景下读取
*/
internal fun dispatchNotificationClick(context: Context, event: PushNotificationClickEvent) {
Log.d("XuqmPushSDK", "Notification clicked: vendor=${event.vendor}, params=${event.params}")
notificationClickListener?.invoke(event)
val pm = context.packageManager
val intent = pm.getLaunchIntentForPackage(context.packageName) ?: return
intent.flags = android.content.Intent.FLAG_ACTIVITY_NEW_TASK or android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP
intent.putExtra(EXTRA_PUSH_VENDOR, event.vendor.name)
intent.putExtra(EXTRA_PUSH_SKIP_CONTENT, event.skipContent)
if (event.params.isNotEmpty()) {
intent.putExtra(EXTRA_PUSH_PARAMS, HashMap(event.params))
}
context.startActivity(intent)
}
// ── Internal hooks called by XuqmSDK via reflection ───────────────────────
fun onSdkLogin(session: XuqmLoginSession) {

查看文件

@ -0,0 +1,9 @@
package com.xuqm.sdk.push.model
data class PushNotificationClickEvent(
val vendor: PushVendor,
val title: String?,
val content: String?,
val params: Map<String, String>,
val skipContent: String?,
)

查看文件

@ -1,13 +1,12 @@
package com.xuqm.sdk.push.vivo
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.PushDeepLinkHandler
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushNotificationClickEvent
import com.xuqm.sdk.push.model.PushVendor
class XuqmVivoPushReceiver : OpenClientPushMessageReceiver() {
@ -27,12 +26,14 @@ class XuqmVivoPushReceiver : OpenClientPushMessageReceiver() {
}
override fun onNotificationMessageClicked(context: Context, message: UPSNotificationMessage) {
val url = message.params?.get("url")?.takeIf { it.isNotBlank() }
?: message.skipContent?.takeIf { it.isNotBlank() }
Log.d(TAG, "Vivo notification clicked: ${message.title}, url=$url")
if (url != null) {
PushDeepLinkHandler.open(context, url)
}
val event = PushNotificationClickEvent(
vendor = PushVendor.VIVO,
title = message.title,
content = message.content,
params = message.params?.toMap() ?: emptyMap(),
skipContent = message.skipContent,
)
PushSDK.dispatchNotificationClick(context, event)
}
companion object {