refactor(bugcollect): 注册与 URL 完全解耦
注册 UncaughtExceptionHandler 不需要 URL: - CrashCapture.start 移除 logApiUrl 参数,只需 appContext + appKey - LogStorage.saveCrash 移除 logApiUrl,崩溃文件不再存储 URL - LogQueue.uploadPendingCrashes 上传时直接取 XuqmSDK.bugCollectApiUrl, URL 为 null 则跳过(下次启动重试),不依赖崩溃文件里的历史 URL - BugCollect.startCrashCapture 移除 URL 构造逻辑 - BugCollectInitProvider Phase 1 纯粹只注册,彻底消除对 platformUrl 的依赖, 版本兼容问题从根本上消失 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
父节点
be8eeba9e1
当前提交
77fef3a38b
@ -137,12 +137,9 @@ object BugCollect {
|
||||
fun startCrashCapture() {
|
||||
if (crashCaptureStarted) return
|
||||
if (!XuqmSDK.isInitialized()) return
|
||||
val logApiUrl = XuqmSDK.bugCollectApiUrl
|
||||
?: (XuqmSDK.platformUrl.trimEnd('/') + "/api/bugcollect/")
|
||||
crashCaptureStarted = true
|
||||
CrashCapture.start(
|
||||
appContext = XuqmSDK.appContext,
|
||||
logApiUrl = logApiUrl,
|
||||
appKey = XuqmSDK.appKey,
|
||||
getUserId = { XuqmSDK.getUserId() },
|
||||
)
|
||||
|
||||
@ -4,11 +4,16 @@ import android.content.Context
|
||||
import com.xuqm.sdk.bugcollect.internal.LogStorage
|
||||
|
||||
internal object CrashCapture {
|
||||
fun start(appContext: Context, logApiUrl: String, appKey: String, getUserId: () -> String?) {
|
||||
|
||||
@Volatile private var started = false
|
||||
|
||||
/** 注册全局崩溃拦截器,只需 appContext + appKey,不依赖上传 URL。 */
|
||||
fun start(appContext: Context, appKey: String, getUserId: () -> String?) {
|
||||
if (started) return
|
||||
started = true
|
||||
val prev = Thread.getDefaultUncaughtExceptionHandler()
|
||||
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
|
||||
// 使用带 Context 的重载,依赖 context.filesDir 获取可靠路径
|
||||
LogStorage.saveCrash(appContext, throwable, logApiUrl, appKey, getUserId())
|
||||
LogStorage.saveCrash(appContext, throwable, appKey, getUserId())
|
||||
prev?.uncaughtException(thread, throwable)
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,7 +65,8 @@ internal class LogQueue(
|
||||
}
|
||||
|
||||
fun uploadPendingCrashes() {
|
||||
if (logApiUrl.isBlank()) return
|
||||
// 上传时才需要 URL,从远端配置取最新值
|
||||
val uploadUrl = com.xuqm.sdk.XuqmSDK.bugCollectApiUrl ?: return
|
||||
scope.launch {
|
||||
val crashDir = LogStorage.crashDir(appContext)
|
||||
val files = crashDir.listFiles() ?: return@launch
|
||||
@ -94,8 +95,6 @@ internal class LogQueue(
|
||||
userId = obj.optNullableString("userId"),
|
||||
user = IssueEvent.UserInfo(id = obj.optNullableString("userId")),
|
||||
)
|
||||
// 优先使用崩溃文件里保存的 URL(崩溃时可能远端配置未到达)
|
||||
val uploadUrl = obj.optString("logApiUrl").takeIf { it.isNotBlank() } ?: logApiUrl
|
||||
LogUploader.uploadIssue(uploadUrl, event)
|
||||
file.delete()
|
||||
}.onFailure { e ->
|
||||
|
||||
@ -17,15 +17,14 @@ import kotlinx.coroutines.launch
|
||||
/**
|
||||
* 崩溃收集的早期初始化入口。
|
||||
*
|
||||
* initOrder=89,在 sdk-core XuqmInitializerProvider(90) 之后运行,此时:
|
||||
* - XuqmSDK.appKey / platformUrl 已由本地 config 文件同步设置
|
||||
* - 远端平台配置尚未到达(bugCollectApiUrl 可能还是 null)
|
||||
* initOrder=89,在 sdk-core XuqmInitializerProvider(90) 之后运行,此时
|
||||
* XuqmSDK.appKey 已由本地 config 文件同步设置。
|
||||
*
|
||||
* 职责:
|
||||
* 1. 立即注册 UncaughtExceptionHandler,把崩溃落盘(只需 appKey + 派生 URL)
|
||||
* 2. 后台等待 XuqmSDK.awaitInitialization() 完成后,上传积压的崩溃文件
|
||||
*
|
||||
* sdk-core 不依赖本类,XuqmSDK 可独立使用。
|
||||
* 职责分离:
|
||||
* Phase 1(同步):只注册 UncaughtExceptionHandler,把崩溃落盘。
|
||||
* 不需要上传 URL,注册与 URL 完全解耦。
|
||||
* Phase 2(异步):等待 XuqmSDK.awaitInitialization() 完成,
|
||||
* 远端配置下发 bugCollectApiUrl 后再上传积压的崩溃文件。
|
||||
*/
|
||||
internal class BugCollectInitProvider : ContentProvider() {
|
||||
|
||||
@ -33,54 +32,28 @@ internal class BugCollectInitProvider : ContentProvider() {
|
||||
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
|
||||
?: deriveFallbackUrl()
|
||||
|
||||
if (logApiUrl.isBlank()) {
|
||||
Log.w("BugCollect", "Crash capture skipped: no URL available (update sdk-core to latest SNAPSHOT)")
|
||||
Log.w("BugCollect", "BugCollectInitProvider: XuqmSDK not initialized, skipping")
|
||||
return true
|
||||
}
|
||||
|
||||
// ── Phase 1:立即注册拦截器,只需 appKey,无需 URL ──────────────────────
|
||||
CrashCapture.start(
|
||||
appContext = ctx,
|
||||
logApiUrl = logApiUrl,
|
||||
appKey = XuqmSDK.appKey,
|
||||
getUserId = { XuqmSDK.getUserId() },
|
||||
)
|
||||
|
||||
// ── Phase 2: 等远端配置就绪后上传积压的崩溃文件 ─────────────────────────
|
||||
uploadAfterInit(ctx)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 XuqmSDK.platformUrl 派生兜底地址。
|
||||
* 用 try-catch 保护:旧版 sdk-core 没有此属性时返回空串,让调用方跳过。
|
||||
*/
|
||||
private fun deriveFallbackUrl(): String = runCatching {
|
||||
XuqmSDK.platformUrl.trimEnd('/') + "/api/bugcollect/"
|
||||
}.getOrElse { e ->
|
||||
Log.w("BugCollect", "Cannot derive bugcollect URL (sdk-core may be outdated): ${e.message}")
|
||||
""
|
||||
}
|
||||
|
||||
private fun uploadAfterInit(ctx: Context) {
|
||||
// ── Phase 2:远端配置就绪后上传 ─────────────────────────────────────────
|
||||
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}")
|
||||
Log.w("BugCollect", "Pending crash upload failed after init: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun query(uri: Uri, p: Array<out String>?, s: String?, sa: Array<out String>?, so: String?): Cursor? = null
|
||||
|
||||
@ -13,13 +13,11 @@ internal object LogStorage {
|
||||
|
||||
/**
|
||||
* 同步写崩溃文件(在 UncaughtExceptionHandler 中调用)。
|
||||
* Application Context 在 handler 回调时仍然有效,直接用 context.filesDir 获取可靠路径。
|
||||
* 文件在下次启动时由 LogQueue.uploadPendingCrashes 上报后删除。
|
||||
* 不存储上传 URL——URL 在下次启动上传时从 XuqmSDK.bugCollectApiUrl 取得。
|
||||
*/
|
||||
fun saveCrash(
|
||||
context: Context,
|
||||
throwable: Throwable,
|
||||
logApiUrl: String,
|
||||
appKey: String,
|
||||
userId: String?,
|
||||
) {
|
||||
@ -31,7 +29,6 @@ internal object LogStorage {
|
||||
put("message", throwable.message ?: throwable.javaClass.name)
|
||||
put("stack", throwable.stackTraceToString())
|
||||
put("exceptionType", throwable.javaClass.simpleName)
|
||||
put("logApiUrl", logApiUrl)
|
||||
put("appKey", appKey)
|
||||
put("userId", userId ?: JSONObject.NULL)
|
||||
put("timestamp", System.currentTimeMillis())
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户