diff --git a/sdk-bugcollect/src/main/AndroidManifest.xml b/sdk-bugcollect/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ff7a1f0 --- /dev/null +++ b/sdk-bugcollect/src/main/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + 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 70cab38..1ee6442 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 @@ -129,11 +129,14 @@ object BugCollect { event("__log_info", tags + ("message" to message)) } + /** + * 注册全局崩溃拦截器(幂等)。 + * 通常由 BugCollectInitProvider 在 app 启动时自动调用,无需 app 代码主动触发。 + * 如需在特定时机手动注册,调用此方法也是安全的。 + */ fun startCrashCapture() { if (crashCaptureStarted) return if (!XuqmSDK.isInitialized()) return - // bugCollectApiUrl 由远端配置下发,启动时可能尚未到达。 - // 用 platformUrl 推导兜底地址:崩溃文件里会保存此 URL,下次启动时用它上报。 val logApiUrl = XuqmSDK.bugCollectApiUrl ?: (XuqmSDK.platformUrl.trimEnd('/') + "/api/bugcollect/") crashCaptureStarted = true @@ -146,6 +149,11 @@ object BugCollect { queue().uploadPendingCrashes() } + /** 上传上次崩溃落盘的文件(远端配置就绪后由 BugCollectInitProvider 自动调用)。 */ + fun uploadPendingCrashes() { + if (isReady()) queue().uploadPendingCrashes() + } + fun defineFunnel(id: String, steps: List) { FunnelTracker.define(id, steps) } diff --git a/sdk-bugcollect/src/main/java/com/xuqm/sdk/bugcollect/internal/BugCollectInitProvider.kt b/sdk-bugcollect/src/main/java/com/xuqm/sdk/bugcollect/internal/BugCollectInitProvider.kt new file mode 100644 index 0000000..d139c78 --- /dev/null +++ b/sdk-bugcollect/src/main/java/com/xuqm/sdk/bugcollect/internal/BugCollectInitProvider.kt @@ -0,0 +1,75 @@ +package com.xuqm.sdk.bugcollect.internal + +import android.content.ContentProvider +import android.content.ContentValues +import android.content.Context +import android.database.Cursor +import android.net.Uri +import android.util.Log +import com.xuqm.sdk.XuqmSDK +import com.xuqm.sdk.bugcollect.BugCollect +import com.xuqm.sdk.bugcollect.CrashCapture +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch + +/** + * 崩溃收集的早期初始化入口。 + * + * initOrder=89,在 sdk-core XuqmInitializerProvider(90) 之后运行,此时: + * - XuqmSDK.appKey / platformUrl 已由本地 config 文件同步设置 + * - 远端平台配置尚未到达(bugCollectApiUrl 可能还是 null) + * + * 职责: + * 1. 立即注册 UncaughtExceptionHandler,把崩溃落盘(只需 appKey + 派生 URL) + * 2. 后台等待 XuqmSDK.awaitInitialization() 完成后,上传积压的崩溃文件 + * + * sdk-core 不依赖本类,XuqmSDK 可独立使用。 + */ +internal class BugCollectInitProvider : ContentProvider() { + + override fun onCreate(): Boolean { + val ctx = context?.applicationContext ?: return true + + if (!XuqmSDK.isInitialized()) { + // core 未初始化(config 文件缺失等),跳过 + Log.w("BugCollect", "BugCollectInitProvider: XuqmSDK not initialized, skipping crash capture setup") + return true + } + + // ── Phase 1: 立即注册崩溃拦截,落盘不需要远端配置 ────────────────────── + val logApiUrl = XuqmSDK.bugCollectApiUrl + ?: (XuqmSDK.platformUrl.trimEnd('/') + "/api/bugcollect/") + + CrashCapture.start( + appContext = ctx, + logApiUrl = logApiUrl, + appKey = XuqmSDK.appKey, + getUserId = { XuqmSDK.getUserId() }, + ) + + // ── Phase 2: 等远端配置就绪后上传积压的崩溃文件 ───────────────────────── + uploadAfterInit(ctx) + + return true + } + + private fun uploadAfterInit(ctx: Context) { + CoroutineScope(SupervisorJob() + Dispatchers.IO).launch { + runCatching { + XuqmSDK.awaitInitialization() + // 远端配置到达后 bugCollectApiUrl 已更新,用最新 URL 上传 + BugCollect.uploadPendingCrashes() + }.onFailure { e -> + Log.w("BugCollect", "Failed to upload pending crashes after init: ${e.message}") + } + } + } + + override fun query(uri: Uri, p: Array?, s: String?, sa: Array?, so: String?): Cursor? = null + override fun getType(uri: Uri): String? = null + override fun insert(uri: Uri, values: ContentValues?): Uri? = null + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = 0 + override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array?): Int = 0 +} 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 f17b8c9..819af2d 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,26 +19,12 @@ 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