refactor(bugcollect): 崩溃收集与上传分离,sdk-core 保持独立
设计问题:
- 之前用反射在 XuqmInitializerProvider 里调用 BugCollect,sdk-core 隐式依赖 sdk-bugcollect
- startCrashCapture 同时负责注册拦截器和触发上传,两者生命周期不同
新架构:
- sdk-core 完全干净,XuqmInitializerProvider 还原为只调用 autoInitialize
- sdk-bugcollect 自注册 BugCollectInitProvider(initOrder=89)
- Phase 1(同步):core init 完成后立即注册 UncaughtExceptionHandler,用
platformUrl 派生兜底 URL,落盘不依赖远端配置
- Phase 2(异步):awaitInitialization() 后用最新 bugCollectApiUrl 上传积压崩溃
- BugCollect.uploadPendingCrashes() 公开,供 Phase 2 调用
- startCrashCapture() 保持幂等,app 侧可选择性调用,不影响自动流程
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
父节点
cc1557daf0
当前提交
54b3163f53
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application>
|
||||||
|
<!--
|
||||||
|
initOrder=89 < sdk-core XuqmInitializerProvider(90),确保 core 先完成本地初始化,
|
||||||
|
BugCollect 能读到 XuqmSDK.appKey / platformUrl 后立即注册 UncaughtExceptionHandler。
|
||||||
|
-->
|
||||||
|
<provider
|
||||||
|
android:name="com.xuqm.sdk.bugcollect.internal.BugCollectInitProvider"
|
||||||
|
android:authorities="${applicationId}.xuqm-bugcollect-init"
|
||||||
|
android:exported="false"
|
||||||
|
android:initOrder="89" />
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
@ -129,11 +129,14 @@ object BugCollect {
|
|||||||
event("__log_info", tags + ("message" to message))
|
event("__log_info", tags + ("message" to message))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册全局崩溃拦截器(幂等)。
|
||||||
|
* 通常由 BugCollectInitProvider 在 app 启动时自动调用,无需 app 代码主动触发。
|
||||||
|
* 如需在特定时机手动注册,调用此方法也是安全的。
|
||||||
|
*/
|
||||||
fun startCrashCapture() {
|
fun startCrashCapture() {
|
||||||
if (crashCaptureStarted) return
|
if (crashCaptureStarted) return
|
||||||
if (!XuqmSDK.isInitialized()) return
|
if (!XuqmSDK.isInitialized()) return
|
||||||
// bugCollectApiUrl 由远端配置下发,启动时可能尚未到达。
|
|
||||||
// 用 platformUrl 推导兜底地址:崩溃文件里会保存此 URL,下次启动时用它上报。
|
|
||||||
val logApiUrl = XuqmSDK.bugCollectApiUrl
|
val logApiUrl = XuqmSDK.bugCollectApiUrl
|
||||||
?: (XuqmSDK.platformUrl.trimEnd('/') + "/api/bugcollect/")
|
?: (XuqmSDK.platformUrl.trimEnd('/') + "/api/bugcollect/")
|
||||||
crashCaptureStarted = true
|
crashCaptureStarted = true
|
||||||
@ -146,6 +149,11 @@ object BugCollect {
|
|||||||
queue().uploadPendingCrashes()
|
queue().uploadPendingCrashes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 上传上次崩溃落盘的文件(远端配置就绪后由 BugCollectInitProvider 自动调用)。 */
|
||||||
|
fun uploadPendingCrashes() {
|
||||||
|
if (isReady()) queue().uploadPendingCrashes()
|
||||||
|
}
|
||||||
|
|
||||||
fun defineFunnel(id: String, steps: List<String>) {
|
fun defineFunnel(id: String, steps: List<String>) {
|
||||||
FunnelTracker.define(id, steps)
|
FunnelTracker.define(id, steps)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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<out String>?, s: String?, sa: Array<out String>?, 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<out String>?): Int = 0
|
||||||
|
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int = 0
|
||||||
|
}
|
||||||
@ -19,26 +19,12 @@ class XuqmInitializerProvider : ContentProvider() {
|
|||||||
if (!XuqmSDK.isInitialized()) {
|
if (!XuqmSDK.isInitialized()) {
|
||||||
XuqmSDK.autoInitialize(ctx)
|
XuqmSDK.autoInitialize(ctx)
|
||||||
}
|
}
|
||||||
// 在 SDK 初始化后立即启动崩溃捕获,确保任何后续崩溃都能被落盘。
|
|
||||||
// startCrashCapture 内部使用 platformUrl 兜底,无需等远端配置到达。
|
|
||||||
startCrashCaptureIfAvailable()
|
|
||||||
}.onFailure { e ->
|
}.onFailure { e ->
|
||||||
Log.w("XuqmSDK", "Auto-initialization skipped: ${e.message}")
|
Log.w("XuqmSDK", "Auto-initialization skipped: ${e.message}")
|
||||||
}
|
}
|
||||||
return true
|
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<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? = null
|
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? = null
|
||||||
override fun getType(uri: Uri): String? = null
|
override fun getType(uri: Uri): String? = null
|
||||||
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
|
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户