diff --git a/sample-app/src/main/java/com/xuqm/sdk/sample/ui/conversation/ConversationViewModel.kt b/sample-app/src/main/java/com/xuqm/sdk/sample/ui/conversation/ConversationViewModel.kt index ec8d083..12ebdd0 100644 --- a/sample-app/src/main/java/com/xuqm/sdk/sample/ui/conversation/ConversationViewModel.kt +++ b/sample-app/src/main/java/com/xuqm/sdk/sample/ui/conversation/ConversationViewModel.kt @@ -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>(emptyList()) val conversations: StateFlow> = _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()) diff --git a/sdk-push/build.gradle.kts b/sdk-push/build.gradle.kts index 1fc5548..4590de1 100644 --- a/sdk-push/build.gradle.kts +++ b/sdk-push/build.gradle.kts @@ -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") diff --git a/sdk-push/src/main/AndroidManifest.xml b/sdk-push/src/main/AndroidManifest.xml index b17fd6f..860fa53 100644 --- a/sdk-push/src/main/AndroidManifest.xml +++ b/sdk-push/src/main/AndroidManifest.xml @@ -1,5 +1,7 @@ - + @@ -31,7 +33,12 @@ + android:value="false" /> + + diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/PushSDK.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/PushSDK.kt index 28800ab..a5c7cf2 100644 --- a/sdk-push/src/main/java/com/xuqm/sdk/push/PushSDK.kt +++ b/sdk-push/src/main/java/com/xuqm/sdk/push/PushSDK.kt @@ -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(null) + private val registeringDeviceKey = AtomicReference(null) + private val lastRegisteredDeviceKey = AtomicReference(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,25 +268,14 @@ 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) - } - } - } - .addOnFailureListener { error -> - Log.w("XuqmPushSDK", "Unable to fetch FCM token: ${error.message}") - } - } - .onFailure { error -> - Log.w("XuqmPushSDK", "Firebase Messaging not available: ${error.message}") + val service = vendorServices.firstOrNull { it.vendor == PushVendor.FCM } + if (service != null && service.isAvailable(context)) { + scope.launch { + service.register(context, loadVendorConfig()) } + } else { + Log.w("XuqmPushSDK", "FCM service not available, skipping native push registration") + } } else { val service = vendorServices.firstOrNull { it.vendor == vendor } if (service != null && service.isAvailable(context)) { @@ -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}") - } } } } diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/FcmPushService.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/FcmPushService.kt index c8444c6..5966b76 100644 --- a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/FcmPushService.kt +++ b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/FcmPushService.kt @@ -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) + } + } }