feat(push): 添加推送服务功能支持
- 实现了 ConversationViewModel 来管理对话列表的刷新和状态 - 集成了 FCM 推送服务支持并实现了自动令牌获取机制 - 构建了完整的 PushSDK 推送系统,支持华为、小米、OPPO、VIVO、荣耀等厂商推送 - 添加了推送配置管理和设备注册/注销功能 - 实现了跨平台推送令牌管理和服务绑定逻辑 - 扩展了服务器端功能服务管理器以支持推送服务激活请求流程
这个提交包含在:
父节点
e87b1b0af2
当前提交
9114672518
@ -10,6 +10,7 @@ import com.xuqm.sdk.im.model.ConversationData
|
||||
import com.xuqm.sdk.im.model.ImMessage
|
||||
import com.xuqm.sdk.im.model.ImGroup
|
||||
import com.xuqm.sdk.sample.di.AppDependencies
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
@ -21,6 +22,7 @@ class ConversationViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
private val cache = AppDependencies.localImCache
|
||||
private var refreshJob: Job? = null
|
||||
private val _conversations = MutableStateFlow<List<ConversationData>>(emptyList())
|
||||
val conversations: StateFlow<List<ConversationData>> = _conversations
|
||||
|
||||
@ -88,7 +90,11 @@ class ConversationViewModel : ViewModel() {
|
||||
|
||||
fun refresh() {
|
||||
Log.d(TAG, "refresh() called")
|
||||
viewModelScope.launch {
|
||||
if (refreshJob?.isActive == true) {
|
||||
Log.d(TAG, "refresh skipped: already running")
|
||||
return
|
||||
}
|
||||
refreshJob = viewModelScope.launch {
|
||||
_isRefreshing.value = true
|
||||
try {
|
||||
val conversations = runCatching { ImSDK.listConversations() }.getOrDefault(emptyList())
|
||||
|
||||
@ -29,8 +29,8 @@ android {
|
||||
dependencies {
|
||||
api(project(":sdk-core"))
|
||||
api(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))
|
||||
implementation(platform(libs.firebase.bom))
|
||||
implementation(libs.firebase.messaging)
|
||||
compileOnly(platform(libs.firebase.bom))
|
||||
compileOnly(libs.firebase.messaging)
|
||||
api("com.huawei.hms:push:6.12.0.300")
|
||||
api("com.hihonor.mcs:push:7.0.41.301")
|
||||
api("io.github.hebeiliang.mipush:Push:2.0.0")
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
@ -31,7 +33,12 @@
|
||||
<application>
|
||||
<meta-data
|
||||
android:name="push_kit_auto_init_enabled"
|
||||
android:value="true" />
|
||||
android:value="false" />
|
||||
|
||||
<provider
|
||||
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||
android:authorities="${applicationId}.firebaseinitprovider"
|
||||
tools:node="remove" />
|
||||
<meta-data
|
||||
android:name="com.vivo.push.app_id"
|
||||
android:value="${XUQM_VIVO_APP_ID}" />
|
||||
|
||||
@ -3,7 +3,6 @@ package com.xuqm.sdk.push
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import com.xuqm.sdk.XuqmLoginSession
|
||||
import com.xuqm.sdk.XuqmSDK
|
||||
import com.xuqm.sdk.core.ServiceEndpointRegistry
|
||||
@ -34,13 +33,26 @@ object PushSDK {
|
||||
private val configApi: PushConfigApi get() = ApiClient.create(PushConfigApi::class.java, ServiceEndpointRegistry.controlBaseUrl)
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
private val registeredUserId = AtomicReference<String?>(null)
|
||||
private val registeringDeviceKey = AtomicReference<String?>(null)
|
||||
private val lastRegisteredDeviceKey = AtomicReference<String?>(null)
|
||||
@Volatile
|
||||
private var cachedVendorConfig: PushVendorConfig? = null
|
||||
|
||||
fun currentRegistration(context: Context): PushRegistrationSnapshot? {
|
||||
XuqmSDK.requireInit()
|
||||
val deviceId = DeviceUtils.getDeviceId(context)
|
||||
return store(context).load(deviceId = deviceId, fallbackVendor = detectVendor())
|
||||
val detectedVendor = detectVendor()
|
||||
val registration = store(context).load(deviceId = deviceId, fallbackVendor = detectedVendor)
|
||||
?: return null
|
||||
if (registration.vendor != detectedVendor) {
|
||||
Log.i(
|
||||
"XuqmPushSDK",
|
||||
"Clearing cached ${registration.vendor.name} push token on ${detectedVendor.name} device",
|
||||
)
|
||||
store(context).clearToken()
|
||||
return null
|
||||
}
|
||||
return registration
|
||||
}
|
||||
|
||||
fun updateNativePushToken(
|
||||
@ -49,6 +61,14 @@ object PushSDK {
|
||||
pushToken: String,
|
||||
) {
|
||||
XuqmSDK.requireInit()
|
||||
val detectedVendor = detectVendor()
|
||||
if (vendor != detectedVendor) {
|
||||
Log.i(
|
||||
"XuqmPushSDK",
|
||||
"Ignoring ${vendor.name} push token on ${detectedVendor.name} device",
|
||||
)
|
||||
return
|
||||
}
|
||||
val normalizedToken = pushToken.trim()
|
||||
require(normalizedToken.isNotBlank()) { "pushToken must not be blank" }
|
||||
store(context).save(vendor, normalizedToken)
|
||||
@ -109,11 +129,24 @@ object PushSDK {
|
||||
val registration = currentRegistration(context)
|
||||
val vendor = registration?.vendor ?: detectVendor()
|
||||
val pushToken = registration?.pushToken?.takeIf { it.isNotBlank() }
|
||||
val deviceId = DeviceUtils.getDeviceId(context)
|
||||
if (pushToken.isNullOrBlank()) {
|
||||
Log.w("XuqmPushSDK", "Native push token not ready yet, waiting for onNewToken()")
|
||||
ensureNativePushToken(context)
|
||||
return
|
||||
}
|
||||
Log.e(">>>>>>>>>>>>>>>>", pushToken)
|
||||
val registrationKey = listOf(userId, vendor.name, pushToken, deviceId).joinToString("|")
|
||||
if (lastRegisteredDeviceKey.get() == registrationKey) {
|
||||
Log.d("XuqmPushSDK", "Skipping duplicate push device registration for userId=$userId vendor=${vendor.name}")
|
||||
return
|
||||
}
|
||||
if (!registeringDeviceKey.compareAndSet(null, registrationKey)) {
|
||||
if (registeringDeviceKey.get() == registrationKey) {
|
||||
Log.d("XuqmPushSDK", "Push device registration already in progress for userId=$userId vendor=${vendor.name}")
|
||||
return
|
||||
}
|
||||
}
|
||||
scope.launch {
|
||||
runCatching {
|
||||
api.registerDevice(
|
||||
@ -121,18 +154,21 @@ object PushSDK {
|
||||
userId = userId,
|
||||
vendor = vendor.name,
|
||||
token = pushToken,
|
||||
deviceId = DeviceUtils.getDeviceId(context),
|
||||
deviceId = deviceId,
|
||||
brand = Build.MANUFACTURER.orEmpty(),
|
||||
model = Build.MODEL.orEmpty(),
|
||||
osVersion = DeviceUtils.getOsVersion(),
|
||||
appVersion = appVersion(context),
|
||||
)
|
||||
registeredUserId.set(userId)
|
||||
lastRegisteredDeviceKey.set(registrationKey)
|
||||
store(context).updateLastUserId(userId)
|
||||
Log.i(
|
||||
"XuqmPushSDK",
|
||||
"Registered push device for userId=$userId vendor=${vendor.name}",
|
||||
)
|
||||
}.also {
|
||||
registeringDeviceKey.compareAndSet(registrationKey, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -206,6 +242,8 @@ object PushSDK {
|
||||
|
||||
fun onSdkLogout() {
|
||||
val userId = registeredUserId.getAndSet(null) ?: return
|
||||
lastRegisteredDeviceKey.set(null)
|
||||
registeringDeviceKey.set(null)
|
||||
unregisterDevice(userId)
|
||||
}
|
||||
|
||||
@ -230,24 +268,13 @@ object PushSDK {
|
||||
if (registration?.pushToken?.isNotBlank() == true) return
|
||||
|
||||
if (vendor == PushVendor.FCM) {
|
||||
runCatching { FirebaseMessaging.getInstance() }
|
||||
.onSuccess { messaging ->
|
||||
messaging.token
|
||||
.addOnSuccessListener { token ->
|
||||
if (token.isNotBlank()) {
|
||||
store(context).save(PushVendor.FCM, token)
|
||||
val sessionUserId = XuqmSDK.currentLoginSession?.userId
|
||||
if (sessionUserId != null) {
|
||||
registerDevice(context, sessionUserId)
|
||||
val service = vendorServices.firstOrNull { it.vendor == PushVendor.FCM }
|
||||
if (service != null && service.isAvailable(context)) {
|
||||
scope.launch {
|
||||
service.register(context, loadVendorConfig())
|
||||
}
|
||||
}
|
||||
}
|
||||
.addOnFailureListener { error ->
|
||||
Log.w("XuqmPushSDK", "Unable to fetch FCM token: ${error.message}")
|
||||
}
|
||||
}
|
||||
.onFailure { error ->
|
||||
Log.w("XuqmPushSDK", "Firebase Messaging not available: ${error.message}")
|
||||
} else {
|
||||
Log.w("XuqmPushSDK", "FCM service not available, skipping native push registration")
|
||||
}
|
||||
} else {
|
||||
val service = vendorServices.firstOrNull { it.vendor == vendor }
|
||||
@ -258,27 +285,8 @@ object PushSDK {
|
||||
} else {
|
||||
Log.w(
|
||||
"XuqmPushSDK",
|
||||
"Vendor ${vendor.name} service not available, falling back to FCM",
|
||||
"Vendor ${vendor.name} service not available, skipping native push registration",
|
||||
)
|
||||
runCatching { FirebaseMessaging.getInstance() }
|
||||
.onSuccess { messaging ->
|
||||
messaging.token
|
||||
.addOnSuccessListener { token ->
|
||||
if (token.isNotBlank()) {
|
||||
store(context).save(PushVendor.FCM, token)
|
||||
val sessionUserId = XuqmSDK.currentLoginSession?.userId
|
||||
if (sessionUserId != null) {
|
||||
registerDevice(context, sessionUserId)
|
||||
}
|
||||
}
|
||||
}
|
||||
.addOnFailureListener { error ->
|
||||
Log.w("XuqmPushSDK", "Unable to fetch FCM token: ${error.message}")
|
||||
}
|
||||
}
|
||||
.onFailure { error ->
|
||||
Log.w("XuqmPushSDK", "Firebase Messaging not available: ${error.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ class FcmPushService : PushVendorInterface {
|
||||
|
||||
override fun register(context: Context, config: PushVendorConfig) {
|
||||
runCatching {
|
||||
initializeFirebase(context)
|
||||
val fcmClass = Class.forName("com.google.firebase.messaging.FirebaseMessaging")
|
||||
val instance = fcmClass.getMethod("getInstance").invoke(null)
|
||||
fcmClass.getMethod("getToken")
|
||||
@ -68,4 +69,14 @@ class FcmPushService : PushVendorInterface {
|
||||
companion object {
|
||||
private const val TAG = "FcmPushService"
|
||||
}
|
||||
|
||||
private fun initializeFirebase(context: Context) {
|
||||
val firebaseAppClass = Class.forName("com.google.firebase.FirebaseApp")
|
||||
val apps = firebaseAppClass.getMethod("getApps", Context::class.java)
|
||||
.invoke(null, context) as? List<*>
|
||||
if (apps.isNullOrEmpty()) {
|
||||
firebaseAppClass.getMethod("initializeApp", Context::class.java)
|
||||
.invoke(null, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户