From cc1557daf0d87efd84cec9533927924517486226 Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Thu, 18 Jun 2026 10:05:43 +0800 Subject: [PATCH] =?UTF-8?q?fix(bugcollect):=20=E4=BF=AE=E5=A4=8D=20fatal?= =?UTF-8?q?=20crash=20=E4=B8=8D=E4=B8=8A=E6=8A=A5=E2=80=94=E2=80=94startCr?= =?UTF-8?q?ashCapture=20=E4=BB=8E=E6=9C=AA=E8=A2=AB=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根本原因链: 1. XuqmInitializerProvider 只调用 XuqmSDK.autoInitialize,从不调用 startCrashCapture 2. 即使调用,startCrashCapture 检查 bugCollectApiUrl == null 即提前返回; 而 bugCollectApiUrl 由远端配置下发,崩溃发生时往往尚未到达 3. UncaughtExceptionHandler 从未注册,崩溃文件没有写入,下次启动无数据可上报 修复: - XuqmSDK 新增 platformUrl 属性(同步可用,初始化即有值) - BugCollect.startCrashCapture 不再依赖 bugCollectApiUrl, 用 platformUrl + "/api/bugcollect/" 作为兜底 URL 写入崩溃文件 - LogQueue.uploadPendingCrashes 优先使用崩溃文件内保存的 logApiUrl - XuqmInitializerProvider 通过反射调用 BugCollect.startCrashCapture, 在 SDK 初始化后立即注册 UncaughtExceptionHandler Co-Authored-By: Claude Sonnet 4.6 --- .../java/com/xuqm/sdk/bugcollect/BugCollect.kt | 8 ++++++-- .../main/java/com/xuqm/sdk/bugcollect/LogQueue.kt | 4 +++- sdk-core/src/main/java/com/xuqm/sdk/XuqmSDK.kt | 3 +++ .../xuqm/sdk/internal/XuqmInitializerProvider.kt | 14 ++++++++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/sdk-bugcollect/src/main/java/com/xuqm/sdk/bugcollect/BugCollect.kt b/sdk-bugcollect/src/main/java/com/xuqm/sdk/bugcollect/BugCollect.kt index 45ee491..70cab38 100644 --- a/sdk-bugcollect/src/main/java/com/xuqm/sdk/bugcollect/BugCollect.kt +++ b/sdk-bugcollect/src/main/java/com/xuqm/sdk/bugcollect/BugCollect.kt @@ -131,11 +131,15 @@ object BugCollect { fun startCrashCapture() { if (crashCaptureStarted) return - val bugCollectApiUrl = XuqmSDK.bugCollectApiUrl ?: return + if (!XuqmSDK.isInitialized()) return + // bugCollectApiUrl 由远端配置下发,启动时可能尚未到达。 + // 用 platformUrl 推导兜底地址:崩溃文件里会保存此 URL,下次启动时用它上报。 + val logApiUrl = XuqmSDK.bugCollectApiUrl + ?: (XuqmSDK.platformUrl.trimEnd('/') + "/api/bugcollect/") crashCaptureStarted = true CrashCapture.start( appContext = XuqmSDK.appContext, - logApiUrl = bugCollectApiUrl, + logApiUrl = logApiUrl, appKey = XuqmSDK.appKey, getUserId = { XuqmSDK.getUserId() }, ) diff --git a/sdk-bugcollect/src/main/java/com/xuqm/sdk/bugcollect/LogQueue.kt b/sdk-bugcollect/src/main/java/com/xuqm/sdk/bugcollect/LogQueue.kt index a13d386..2ab2a39 100644 --- a/sdk-bugcollect/src/main/java/com/xuqm/sdk/bugcollect/LogQueue.kt +++ b/sdk-bugcollect/src/main/java/com/xuqm/sdk/bugcollect/LogQueue.kt @@ -94,7 +94,9 @@ internal class LogQueue( userId = obj.optNullableString("userId"), user = IssueEvent.UserInfo(id = obj.optNullableString("userId")), ) - LogUploader.uploadIssue(logApiUrl, event) + // 优先使用崩溃文件里保存的 URL(崩溃时可能远端配置未到达) + val uploadUrl = obj.optString("logApiUrl").takeIf { it.isNotBlank() } ?: logApiUrl + LogUploader.uploadIssue(uploadUrl, event) file.delete() }.onFailure { e -> Log.e(TAG, "Failed to upload crash: ${file.name}", e) diff --git a/sdk-core/src/main/java/com/xuqm/sdk/XuqmSDK.kt b/sdk-core/src/main/java/com/xuqm/sdk/XuqmSDK.kt index 5f62659..996661b 100644 --- a/sdk-core/src/main/java/com/xuqm/sdk/XuqmSDK.kt +++ b/sdk-core/src/main/java/com/xuqm/sdk/XuqmSDK.kt @@ -40,6 +40,9 @@ object XuqmSDK { private var initialized = false @Volatile private var initializedAppKey: String? = null @Volatile private var resolvedPlatformUrl: String = DEFAULT_PLATFORM_URL + + /** 初始化时使用的平台地址(同步可用,远端配置到达前即有值)。 */ + val platformUrl: String get() = resolvedPlatformUrl @Volatile private var loginSession: XuqmLoginSession? = null @Volatile private var userInfoValue: XuqmUserInfo? = null @Volatile var platformConfig: SdkPlatformConfig? = null diff --git a/sdk-core/src/main/java/com/xuqm/sdk/internal/XuqmInitializerProvider.kt b/sdk-core/src/main/java/com/xuqm/sdk/internal/XuqmInitializerProvider.kt index 819af2d..f17b8c9 100644 --- a/sdk-core/src/main/java/com/xuqm/sdk/internal/XuqmInitializerProvider.kt +++ b/sdk-core/src/main/java/com/xuqm/sdk/internal/XuqmInitializerProvider.kt @@ -19,12 +19,26 @@ class XuqmInitializerProvider : ContentProvider() { if (!XuqmSDK.isInitialized()) { XuqmSDK.autoInitialize(ctx) } + // 在 SDK 初始化后立即启动崩溃捕获,确保任何后续崩溃都能被落盘。 + // startCrashCapture 内部使用 platformUrl 兜底,无需等远端配置到达。 + startCrashCaptureIfAvailable() }.onFailure { e -> Log.w("XuqmSDK", "Auto-initialization skipped: ${e.message}") } return true } + private fun startCrashCaptureIfAvailable() { + runCatching { + val bugCollect = Class.forName("com.xuqm.sdk.bugcollect.BugCollect") + val companion = bugCollect.getField("INSTANCE").get(null) + val method = bugCollect.getMethod("startCrashCapture") + method.invoke(companion) + }.onFailure { + // sdk-bugcollect 未依赖时跳过,不影响其他功能 + } + } + override fun query(uri: Uri, projection: Array?, selection: String?, selectionArgs: Array?, sortOrder: String?): Cursor? = null override fun getType(uri: Uri): String? = null override fun insert(uri: Uri, values: ContentValues?): Uri? = null