feat(push): 添加推送SDK和消息服务实现
- 实现了 Android 推送 SDK,支持华为、小米、Oppo、Vivo、荣耀、FCM 等厂商推送 - 添加了推送配置管理和设备注册功能 - 实现了推送令牌管理和用户绑定功能 - 添加了消息发送、撤回、编辑等核心消息服务功能 - 实现了单聊和群聊消息历史记录管理 - 添加了消息读取回执和群组消息状态同步 - 实现了消息过滤、黑名单和权限控制 - 添加了离线消息推送和消息预览功能 - 实现了消息 Webhook 回调机制
这个提交包含在:
父节点
9114672518
当前提交
84221ff6b2
@ -0,0 +1,80 @@
|
||||
package com.xuqm.sdk.push
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.google.gson.JsonObject
|
||||
|
||||
internal object PushNotificationChannelManager {
|
||||
|
||||
private const val TAG = "XuqmPushChannel"
|
||||
private const val PREFS_NAME = "xuqm_push_channels"
|
||||
private const val KEY_PREFIX_ROUTE = "route_"
|
||||
|
||||
fun apply(context: Context, pushConfig: JsonObject?) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || pushConfig == null) return
|
||||
val channels = pushConfig.getAsJsonArray("channels") ?: return
|
||||
val channelByKey = mutableMapOf<String, String>()
|
||||
val manager = context.getSystemService(NotificationManager::class.java)
|
||||
|
||||
channels.mapNotNull { it.takeIf { item -> item.isJsonObject }?.asJsonObject }
|
||||
.forEach { channel ->
|
||||
val key = channel.text("key").ifBlank { return@forEach }
|
||||
val baseChannelId = channel.text("channelId").ifBlank { key }
|
||||
val version = channel.int("version", 1).coerceAtLeast(1)
|
||||
val effectiveChannelId = "${baseChannelId}_v$version"
|
||||
val notificationChannel = NotificationChannel(
|
||||
effectiveChannelId,
|
||||
channel.text("name").ifBlank { key },
|
||||
channel.importance(),
|
||||
).apply {
|
||||
description = channel.text("description")
|
||||
enableVibration(channel.bool("vibration", true))
|
||||
setShowBadge(channel.bool("badge", true))
|
||||
if (!channel.bool("sound", true)) {
|
||||
setSound(null, null)
|
||||
}
|
||||
}
|
||||
manager.createNotificationChannel(notificationChannel)
|
||||
channelByKey[key] = effectiveChannelId
|
||||
Log.d(TAG, "Notification channel ready key=$key id=$effectiveChannelId")
|
||||
}
|
||||
|
||||
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
prefs.edit().apply {
|
||||
channelByKey.forEach { (key, channelId) -> putString("channel_$key", channelId) }
|
||||
val routing = pushConfig.getAsJsonObject("routing")
|
||||
routing?.entrySet()?.forEach { (type, routeElement) ->
|
||||
val route = routeElement.takeIf { it.isJsonObject }?.asJsonObject ?: return@forEach
|
||||
val channelKey = route.text("channel")
|
||||
val channelId = channelByKey[channelKey] ?: return@forEach
|
||||
putString(KEY_PREFIX_ROUTE + type, channelId)
|
||||
}
|
||||
}.apply()
|
||||
}
|
||||
|
||||
fun channelIdFor(context: Context, routeType: String): String? =
|
||||
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
.getString(KEY_PREFIX_ROUTE + routeType, null)
|
||||
|
||||
private fun JsonObject.text(key: String): String =
|
||||
runCatching { get(key)?.takeIf { !it.isJsonNull }?.asString?.trim().orEmpty() }.getOrDefault("")
|
||||
|
||||
private fun JsonObject.bool(key: String, fallback: Boolean): Boolean =
|
||||
runCatching { get(key)?.takeIf { !it.isJsonNull }?.asBoolean ?: fallback }.getOrDefault(fallback)
|
||||
|
||||
private fun JsonObject.int(key: String, fallback: Int): Int =
|
||||
runCatching { get(key)?.takeIf { !it.isJsonNull }?.asInt ?: fallback }.getOrDefault(fallback)
|
||||
|
||||
private fun JsonObject.importance(): Int {
|
||||
return when (text("importance").uppercase()) {
|
||||
"MIN" -> NotificationManager.IMPORTANCE_MIN
|
||||
"LOW" -> NotificationManager.IMPORTANCE_LOW
|
||||
"HIGH" -> NotificationManager.IMPORTANCE_HIGH
|
||||
"MAX" -> NotificationManager.IMPORTANCE_MAX
|
||||
else -> NotificationManager.IMPORTANCE_DEFAULT
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -37,6 +37,8 @@ object PushSDK {
|
||||
private val lastRegisteredDeviceKey = AtomicReference<String?>(null)
|
||||
@Volatile
|
||||
private var cachedVendorConfig: PushVendorConfig? = null
|
||||
@Volatile
|
||||
private var cachedConfigAt: Long = 0L
|
||||
|
||||
fun currentRegistration(context: Context): PushRegistrationSnapshot? {
|
||||
XuqmSDK.requireInit()
|
||||
@ -55,6 +57,9 @@ object PushSDK {
|
||||
return registration
|
||||
}
|
||||
|
||||
fun notificationChannelIdFor(context: Context, routeType: String): String? =
|
||||
PushNotificationChannelManager.channelIdFor(context.applicationContext, routeType)
|
||||
|
||||
fun updateNativePushToken(
|
||||
context: Context,
|
||||
vendor: PushVendor,
|
||||
@ -221,6 +226,7 @@ object PushSDK {
|
||||
Log.i("XuqmPushSDK", "Detected push vendor: ${detectedVendor.name}")
|
||||
scope.launch {
|
||||
val config = loadVendorConfig()
|
||||
PushNotificationChannelManager.apply(context.applicationContext, cachedPushConfig)
|
||||
vendorServices.forEach { service ->
|
||||
if (service.vendor == detectedVendor && service.isAvailable(context)) {
|
||||
Log.i("XuqmPushSDK", "Initializing push vendor: ${service.vendor.name}")
|
||||
@ -302,10 +308,16 @@ object PushSDK {
|
||||
}
|
||||
}.getOrNull()
|
||||
|
||||
@Volatile
|
||||
private var cachedPushConfig: com.google.gson.JsonObject? = null
|
||||
|
||||
private suspend fun loadVendorConfig(): PushVendorConfig {
|
||||
cachedVendorConfig?.let { return it }
|
||||
val now = System.currentTimeMillis()
|
||||
cachedVendorConfig?.takeIf { now - cachedConfigAt < CONFIG_CACHE_TTL_MS }?.let { return it }
|
||||
val loaded = runCatching {
|
||||
val pushConfig = configApi.sdkConfig(XuqmSDK.appKey).data?.pushConfig
|
||||
cachedPushConfig = pushConfig
|
||||
PushNotificationChannelManager.apply(XuqmSDK.appContext, pushConfig)
|
||||
PushVendorConfig(
|
||||
huaweiAppId = pushConfig?.getAsJsonObject("huawei")?.get("appId")?.asString.orEmpty(),
|
||||
xiaomiAppId = pushConfig?.getAsJsonObject("xiaomi")?.get("appId")?.asString.orEmpty(),
|
||||
@ -321,6 +333,9 @@ object PushSDK {
|
||||
PushVendorConfig()
|
||||
}
|
||||
cachedVendorConfig = loaded
|
||||
cachedConfigAt = now
|
||||
return loaded
|
||||
}
|
||||
|
||||
private const val CONFIG_CACHE_TTL_MS = 5 * 60 * 1000L
|
||||
}
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户