fix(android): 修复进入推流页立即闪退(未捕获 Error)

用户反馈:扫描到客户端后点击「开始推流」立刻闪退。

根因:PoseDetector.initialize() 只 catch (Exception),而 MediaPipe 初始化
失败会抛 UnsatisfiedLinkError / NoClassDefFoundError / ExceptionInInitializerError
—— 这些都是 Error,不是 Exception,会直接冒泡成进程崩溃。

证据(对照实验):新增 Robolectric 组合测试后
  改回 catch (Exception):5 项测试 2 项失败
    - java.lang.UnsatisfiedLinkError: no mediapipe_tasks_jni in java.library.path
    - java.lang.NoClassDefFoundError: Could not initialize class ...PoseLandmarker
  改为 catch (Throwable):5 项全部通过

修复与加固:
- PoseDetector / CameraController 全部改为捕获 Throwable,失败只提示不崩溃
- 移除 accompanist-permissions 0.34.0(2024-02 发布,会传递引入
  compose animation-core 1.6.0,与 Compose BOM 存在版本偏斜风险),
  改用平台 Activity Result API
- MediaPipe 回调在相机线程:所有 Compose 状态写入统一 post 回主线程
- WebSocket 连接、相机启动、单帧分析全部包上失败保护
- 新增 CrashLogger:未捕获异常落盘到 filesDir/crashes/,
  并在连接页直接展示堆栈(免去连 adb 抓 logcat)
- 新增 ScreenCompositionTest(Robolectric):真实组合三个页面,
  覆盖「进入页面即崩溃」这一类缺陷,共 30 项单元测试
This commit is contained in:
2026-09-14 22:43:03 +08:00
parent fd50a7fdf3
commit e9ea1b5811
8 changed files with 387 additions and 69 deletions
+21 -3
View File
@@ -122,7 +122,11 @@ android {
}
testOptions {
unitTests.isReturnDefaultValues = true
unitTests {
isReturnDefaultValues = true
// Robolectric 需要真实资源与 Manifest 才能组合 Compose 界面
isIncludeAndroidResources = true
}
}
lint {
@@ -161,13 +165,27 @@ dependencies {
// ---- WebSocket 推流(T-AND-005)----
implementation("com.squareup.okhttp3:okhttp:4.12.0")
// ---- 权限 ----
implementation("com.google.accompanist:accompanist-permissions:0.34.0")
// 权限:使用平台 Activity Result API(androidx.activity)。
// 原先用 accompanist-permissions 0.34.0,它发布于 2024-02,会传递引入
// compose animation-core 1.6.0,与 Compose BOM 存在版本偏斜风险;
// 该库已进入维护状态,官方也建议改用 Activity Result API。
// ---- 单元测试(T-AND-004 等纯逻辑,跑在 JVM 上,无需设备)----
testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlin:kotlin-test:2.0.21")
// Robolectric:在 JVM 上跑 Android 框架 + Compose,用于验证
// 「进入运行页即崩溃」这类只在真机/运行时才暴露的缺陷
testImplementation("org.robolectric:robolectric:4.13")
testImplementation("androidx.test:core-ktx:1.6.1")
testImplementation("androidx.test.ext:junit:1.2.1")
testImplementation("androidx.compose.ui:ui-test-junit4")
// 必须用 debugImplementation:它往 debug 变体的合并 Manifest 里注册
// androidx.activity.ComponentActivity,Compose 测试规则要靠它启动宿主 Activity。
// 放 testImplementation 不会参与 Manifest 合并,运行时报
// "Unable to resolve activity for Intent ... ComponentActivity"。
debugImplementation("androidx.compose.ui:ui-test-manifest")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
androidTestImplementation("androidx.compose.ui:ui-test-junit4")