三个真机实测暴露的问题:
1. 闪退 (给权限后 3-4 秒必崩, 且注入日志抓不到堆栈)
CwWaterfallState 用 mutableIntStateOf 记录重绘版本号, 却从音频采集线程
写入。Compose 快照状态只能在合成线程修改, 从后台线程写会在运行时崩溃 ——
崩在 Compose 内部, 所以业务类的日志注入抓不到。
改用 MutableStateFlow (本身线程安全), Canvas 侧 collectAsState 读取。
2. 同一状态的跨线程数据竞争
pushSamples 在采集线程写 rows/pending, snapshot() 在绘制线程读, 而
ArrayDeque 非线程安全 —— 并发 removeFirst()/toList() 会抛
ConcurrentModificationException 或 IndexOutOfBoundsException。
两侧统一加锁; FFT 计算放在锁外, 只有队列追加持锁。
3. APK 从 8.4MB 暴涨到 135MB
onnxruntime-android 的 AAR 自带 4 个架构原生库: arm64 28M + armv7 20M +
x86 33M + x86_64 34M = 115MB。上次提交移除 abiFilters 时把 x86 系列也
打包了进去 (仅模拟器需要)。
重新加上 abiFilters, 保留 arm64-v8a + armeabi-v7a 两个真机 ABI,
预计降至约 43MB。注意这与上次"恢复 64 位"不冲突: 那次删的是只留
armeabi-v7a 的限制, 这次是排除 x86 系列。
另外两处加固:
- CwDeepDecoder 的模型加载从 init{} 移入惰性 ensureLoaded(): 加载会触发
ONNX Runtime 原生库装载, 失败时抛 UnsatisfiedLinkError; 在构造函数中抛出
会连带崩掉创建它的 composable, try-catch 也救不回来。移到首次使用时执行,
失败经 errorMessage 上报给 UI。
- ONNX 会话限制 intraOp 线程数为 (核数-1) 且上限 4, 给音频采集和 UI 留出
余量, 默认行为会铺满所有核心。
验证:
./gradlew :feature:cw:compileDebugKotlin :core:data:compileDebugKotlin => 通过
./gradlew :core:domain:test => 79 个测试全绿
48 lines
1.8 KiB
Kotlin
48 lines
1.8 KiB
Kotlin
import java.util.Properties
|
|
|
|
plugins {
|
|
alias(libs.plugins.convention.applicationPlugin)
|
|
}
|
|
|
|
// Load signing config from keystore.properties (gitignored, never commit credentials)
|
|
val keystoreProperties = Properties().apply {
|
|
val propsFile = rootProject.file("keystore.properties")
|
|
if (propsFile.exists()) propsFile.inputStream().use { load(it) }
|
|
}
|
|
|
|
android {
|
|
defaultConfig {
|
|
// ONNX Runtime 的 AAR 自带 4 个架构共 115MB 原生库(arm64 28M / armv7 20M /
|
|
// x86 33M / x86_64 34M)。x86 系列只有模拟器用得到, 全打包会让 APK 从 8MB
|
|
// 涨到 135MB。仅保留真机需要的两个 ABI。
|
|
ndk {
|
|
abiFilters += listOf("arm64-v8a", "armeabi-v7a")
|
|
}
|
|
}
|
|
androidResources {
|
|
// 显式保留全部语言(防 shrinkResources 丢弃 in/id 印尼语配置); AGP 9 用 localeFilters
|
|
localeFilters += listOf(
|
|
"en", "zh", "tr", "in", "id", "es", "ru", "si", "uk"
|
|
)
|
|
// DeepCW 模型必须以未压缩形式打包: ONNX Runtime 通过 mmap 直接读取
|
|
// assets, 压缩后无法映射会导致 createSession 失败。noCompress 只在
|
|
// 打包 APK 的 app 模块生效, 在 feature 库模块声明无效。
|
|
noCompress += "onnx"
|
|
}
|
|
signingConfigs {
|
|
if (keystoreProperties["storeFile"] != null) {
|
|
create("release") {
|
|
storeFile = rootProject.file(keystoreProperties["storeFile"] as String)
|
|
storePassword = keystoreProperties["storePassword"] as String
|
|
keyAlias = keystoreProperties["keyAlias"] as String
|
|
keyPassword = keystoreProperties["keyPassword"] as String
|
|
}
|
|
}
|
|
}
|
|
buildTypes {
|
|
release {
|
|
signingConfig = signingConfigs.findByName("release")
|
|
}
|
|
}
|
|
}
|