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.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.xuqm.sdk.push.model.PushNotificationClickEvent
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
object PushSDK { object PushSDK {
@ -37,9 +38,27 @@ object PushSDK {
private val lastRegisteredDeviceKey = AtomicReference<String?>(null) private val lastRegisteredDeviceKey = AtomicReference<String?>(null)
@Volatile private var cachedVendorConfig: PushVendorConfig? = null @Volatile private var cachedVendorConfig: PushVendorConfig? = null
@Volatile private var cachedConfigAt: Long = 0L @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) ────────────────────────────────────────────── // ── 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? = fun notificationChannelIdFor(context: Context, routeType: String): String? =
PushNotificationChannelManager.channelIdFor(context.applicationContext, routeType) 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 ─────────────────────── // ── Internal hooks called by XuqmSDK via reflection ───────────────────────
fun onSdkLogin(session: XuqmLoginSession) { 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 package com.xuqm.sdk.push.vivo
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.UnvarnishedMessage
import com.vivo.push.model.UPSNotificationMessage import com.vivo.push.model.UPSNotificationMessage
import com.vivo.push.sdk.OpenClientPushMessageReceiver import com.vivo.push.sdk.OpenClientPushMessageReceiver
import com.xuqm.sdk.push.PushDeepLinkHandler
import com.xuqm.sdk.push.PushSDK import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushNotificationClickEvent
import com.xuqm.sdk.push.model.PushVendor import com.xuqm.sdk.push.model.PushVendor
class XuqmVivoPushReceiver : OpenClientPushMessageReceiver() { class XuqmVivoPushReceiver : OpenClientPushMessageReceiver() {
@ -27,12 +26,14 @@ class XuqmVivoPushReceiver : OpenClientPushMessageReceiver() {
} }
override fun onNotificationMessageClicked(context: Context, message: UPSNotificationMessage) { override fun onNotificationMessageClicked(context: Context, message: UPSNotificationMessage) {
val url = message.params?.get("url")?.takeIf { it.isNotBlank() } val event = PushNotificationClickEvent(
?: message.skipContent?.takeIf { it.isNotBlank() } vendor = PushVendor.VIVO,
Log.d(TAG, "Vivo notification clicked: ${message.title}, url=$url") title = message.title,
if (url != null) { content = message.content,
PushDeepLinkHandler.open(context, url) params = message.params?.toMap() ?: emptyMap(),
} skipContent = message.skipContent,
)
PushSDK.dispatchNotificationClick(context, event)
} }
companion object { companion object {