更新记录
1.0.0(2026-09-07) 下载此版本
Sensors Wave 原生崩溃采集插件
平台兼容性
uni-app(4.0)
| Vue2 | Vue2插件版本 | Vue3 | Vue3插件版本 | Chrome | Safari | app-vue | app-vue插件版本 | app-nvue | app-nvue插件版本 | Android | Android插件版本 | iOS | iOS插件版本 | 鸿蒙 | 鸿蒙插件版本 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| √ | 1.0.0 | √ | 1.0.0 | - | - | √ | 1.0.0 | √ | 1.0.0 | 11.0 | 1.0.0 | 14 | 1.0.0 | √ | 1.0.0 |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
uni-app x(4.0)
| Chrome | Safari | Android | Android插件版本 | iOS | iOS插件版本 | 鸿蒙 | 鸿蒙插件版本 | 微信小程序 |
|---|---|---|---|---|---|---|---|---|
| - | - | 11.0 | 1.0.0 | 14 | 1.0.0 | √ | 1.0.0 | - |
sensorswave-crash 原生崩溃采集插件(UTS)
UniApp 原生崩溃(fatal)采集插件,配合 @sensorswave/uniapp-sdk(enableCrashTrack: true)实现
「装一个 SDK 完成全部数据采集,包括原生崩溃」。
插件是纯崩溃捕获器:只负责崩溃瞬间在本地落盘规范化记录(sw_crash_records/<13位时间戳>_<id>.json),
无网络、无分析逻辑。下次启动时 JS SDK 经桥拉取(最旧优先 ≤10 条)、构造
$Exception($exception_level='fatal'、time 回填崩溃时刻)入队上报,
入队成功后 ack 删除。数据面统一在 JS,与埋点共用同一管道 / 合规门禁(opt-out 时不拉取不删除)。
形态:纯 UTS 源码插件(uni_modules),零预编译产物、零三方依赖。三端(Android / iOS / HarmonyOS)共用一份源码目录,UTS 在云打包 / 自定义基座时才编译为 Kotlin / Swift / ArkTS。
背景:DCloud 自 2024-08-01 起插件市场不再接收新的 App 原生语言插件,本插件由旧 原生语言方案(Android AAR + iOS framework,git 历史可查)整体迁移而来。
目录结构
uni_modules/sensorswave-crash/
├── README.md # 本文件(随插件目录分发,插件市场详情页展示)
├── package.json # uni_modules 清单(三端 App 矩阵全 "y",vue3 only)
└── utssdk/
├── interface.uts # 桥契约声明(4 个同步 JSON 方法)
├── crash-record.uts # 跨端共享纯逻辑(常量 + JSON 组装/转义/文件名/TTL)
├── app-android/index.uts # Thread.setDefaultUncaughtExceptionHandler 链式
├── app-ios/index.uts # NSSetUncaughtExceptionHandler(经混编层注册)
│ ├── SwCrashHybrid.swift # UTS 原生混编:C 函数指针注册 + 旧 handler 链式
│ ├── exception-frames.uts # callStackSymbols 文本帧解析(纯函数)
│ └── exception-image.uts # 主二进制 LC_UUID 读取(debug_id 富化)
└── app-harmony/index.uts # errorManager + hiAppEvent watcher 双通道
demo 侧 demo/uni_modules/sensorswave-crash/ 是本目录的拷贝(yarn sync:demo 维护)。
📌 HBuilderX 识别 UTS 插件要求插件位于打开的 uni-app 工程内(本仓库根目录无 manifest.json,不会被当成工程):本地开发 / 制作自定义基座 / 发布插件市场均在 demo 工程中操作,本目录是唯一的源码真源。
覆盖范围
| 平台 | 覆盖 | 不覆盖 | 捕获时机 | kind 值 |
|---|---|---|---|---|
| Android | ✅ Java/Kotlin 未捕获异常(UEH 链式包装,写完委托原 handler;结构化帧直取 stackTrace) |
❌ NDK/C++ 崩溃、SIGSEGV 等信号级(未来可经 config.json 引 xCrash 补齐,JS 契约不动) | HookProxy(Application.onCreate,HBuilderX 3.96+)+ startCapture(SDK init) | java |
| iOS | ✅ ObjC/Swift NSException(含 raise();结构化帧来自 callStackSymbols 解析 + debug_id 读盘 LC_UUID;handler 经原生混编 SwCrashHybrid.swift 注册——UTS 闭包桥接 C 函数指针为死桩,注册后落盘后链式调用前一 handler 不覆盖宿主) |
❌ SIGSEGV/SIGABRT 等 BSD 信号级(Swift fatalError/force-unwrap 属此类;信号级捕获无法纯 UTS 移植) |
HookProxy(applicationDidFinishLaunchingWithOptions,3.97+)+ startCapture(SDK init) | oc |
| HarmonyOS | ✅ ArkTS 未捕获异常(errorManager 通道,崩溃瞬间当场落盘,富堆栈)+ C++ 崩溃 / APP_FREEZE 卡死(hiAppEvent watcher 通道,系统 faultlog 下个启动回放,无堆栈属预期) | ❌ external_log 故障日志文件不消费不代删(系统 5MB 滚动上限自管) | 模块导入时(双通道同时注册) | arkts / cpp / freeze |
采集注册早于 enableCrashTrack 门控:是否回放由 JS 侧合规状态决定,opt-out 期间
记录保留在设备上,授权恢复后自动补报。
业务接入
- 插件市场导入(或拷贝)
sensorswave-crash到工程uni_modules/ - HBuilderX 制作自定义基座(UTS 插件在云打包/基座制作时才编译,标准基座不生效)。 本地调试同理:直接「运行到手机」跑的是标准基座,采集不生效——需 「运行 → 运行到手机或模拟器 → 制作自定义调试基座」后用该基座运行; 改插件源码后须重新制作基座(非热更新)
- SDK init 开启
enableCrashTrack: true(仅app平台生效;其余平台静默跳过)
接入方式(按平台):Android / HarmonyOS 下 SDK init 经
uni.requireUTSPlugin('@/uni_modules/sensorswave-crash') 自动解析插件并接线崩溃回放,
无需业务代码(自动通道可用性随 HBuilderX 版本浮动,未逐版本验证);iOS(vue 工程)
自动通道不工作——运行时按 @/uni_modules/<id> 路径解析不到本地 UTS 插件(实测报
is not found),必须走下方手动注入(根 README「原生崩溃采集」有完整说明)。
自动通道失败时(且未手动注入)init 打 warn 日志提示接入指引(采集能力静默降级,
不影响其他功能)。
手动注入(iOS 必做,各端推荐统一使用):在
#ifdef APP 下静态 import 模块后注入:
// #ifdef APP
import * as crashUts from '@/uni_modules/sensorswave-crash'
import { createUtsCrashProvider } from '@sensorswave/uniapp-sdk'
// ⚠️ pull/ack 两个导出须以对象字面量逐成员引用后传入:UTS 代理按「JS 是否引用」
// 生成并摇树(namespace 直传会被摇掉),漏引用 ackCrashRecords 会导致回放能拉到
// 记录但删除静默失效(重启重复上报)。注入时 SDK 会对缺失方法打 warn 兜底。
SensorsWave.registerCrashProvider(createUtsCrashProvider({
getPendingCrashRecords: crashUts.getPendingCrashRecords,
ackCrashRecords: crashUts.ackCrashRecords,
}))
// #endif
init 之前注入可完全接管(自动通道不再触发);init 之后注入会立即用注入的 provider 重放一次(已 ack 的记录拉取为空,不会产生重复事件)。
验证(demo 工程已接好)
- demo
pages/error-track底部「原生崩溃(fatal)」区:插件状态行 + 触发按钮 - 触发后约 0.5~1s 进程死亡 → 重新打开 → debug 日志出现
$Exception(level=fatal、time为崩溃时刻),batchSend:false下立即 POST/in/track - 再次重启无重复事件(ack 生效)
- 隐私验证:
optOutCapturing下崩溃重启不上报且设备记录保留,恢复授权后补报 - iOS 必须脱离 Xcode 从桌面启动(挂调试器时 NSException handler 不触发)
桥契约(三端一致,4 个同步方法)
| 方法 | 签名 | 说明 |
|---|---|---|
getPendingCrashRecords |
(): string |
同步拉取 payload(最旧优先 ≤10 条;拉取前做 TTL 清理,并逐条校验记录内容——非法/半成品记录直接删除,防毒丸阻塞回放通道) |
ackCrashRecords |
(ids: string[]) |
幂等删除已入队记录(harmony 端同时把记录时刻写入跨启动去重存储) |
startCapture |
(debug: boolean): string |
幂等注册 + TTL 清理;返回 {"started":true,"version":"...","kinds":[...]};debug=true 放开 testCrash |
testCrash |
(kind: string): void |
演示崩溃:java / oc / arkts;延迟 300~500ms 脱离 JS 调用栈执行(避免被 JS 桥/Vue errorHandler 吞成 error 级事件)。java 为后台线程真实抛出直达 UEH;oc / arkts 为模拟派发(直接调用注册的同一落盘链后终止进程——iOS 模拟器 Rosetta 下真实 raise 损坏、harmony 运行时会吞定时器回调异常,裸抛均不可达;oc-raise 为 iOS 真机真实派发通道) |
payload:成功 {"code":"ok","total":N,"records":[CrashRecord...]}(最旧优先 ≤10 条);
失败 {"code":"error","message":"..."}。CrashRecord 字段契约见
src/core/native-crash-types.ts(id/kind/exception_type/message/stack/timestamp/process_name/...)。
结构化 frames(仅非空才写):Android 直取 throwable.stackTrace(platform:"java",
module=全限定类名,canonical 序:外层在前、崩溃点在末,>30 帧保首 5 入口 + 末 20 崩溃侧);
iOS 由 callStackSymbols 文本解析(platform:"cocoa",instruction_addr PAC 已剥 16 位 hex,
未符号化帧带 image_addr,主二进制帧带 debug_id=LC_UUID 大写连字符 36 位——服务端 dSYM
符号化匹配原料)。JS 侧 sanitizeNativeCrashFrames 白名单清洗后优先使用;无 frames 的
记录回退 stack 文本解析(parseExceptionFrames)。
存储与可靠性
- 目录:Android
<filesDir>/sw_crash_records/;iOS<NSHomeDirectory>/Library/sw_crash_records/; harmony<filesDir>/sw_crash_records/ - 文件名
<13位零填充毫秒时间戳>_<id>.json(字典序=时间序;文件名解析 id 供 ack) - 写入原子性:三端均为「写临时文件 + rename」(iOS
atomically:true等价)——崩溃线程 写盘中途进程死亡不留半成品.json(.tmp残料不满足记录文件名规则,不会被误读) - 防毒丸:拉取时逐条校验内容(可解析为对象 + 非空
id),非法/半成品记录直接删除—— 半成品若原样嵌入 payload 会令 JS 侧整个 payload 解析失败,回放通道被永久阻塞且无法 ack - 边界:TTL 7 天(拉取与 startCapture 时清理)、容量上限 100 条(超出丢最旧)、 单次回放 ≤10 条(对齐 JS 队列预算)
- 崩溃路径只允许同步 API + 手写 JSON 转义(
crash-record.uts的escapeJson), 不依赖序列化库;任何异常吞掉,采集链自身绝不能成为新的异常源 - harmony 跨启动去重:
filesDir/sw_crash_dedup.dat(每行一个毫秒时间戳,容量 20、24h 过期), watcher 事件以 ±5s 时间窗比对「已 ack 键 + 现存待处理记录」,防 errorManager/watcher 双计、防系统重放重生记录
已知限制与编译验证点
- 采集范围降级(用户已确认):无 Android NDK / iOS BSD 信号级捕获——纯 UTS 无法移植 xCrash / PLCrashReporter 的信号机制;README/docs 已注明,未来可经 config.json 引 xCrash 补齐
- iOS 挂调试器(Xcode attach)时崩溃不捕获——E2E 从桌面启动
- harmony:JS 启动前的崩溃不在 errorManager 通道覆盖(watcher 通道由系统 faultlog 兜底); watcher 送达晚于 SDK init pull 时,记录顺延一个启动周期回放(不丢失)
- UTS 平台类型边界标注了【需编译验证】(各实现文件头):Java 数组迭代(Kotlin
.size)、 iOS String 文件读写(Swift 标签参数)、FileManager 返回值桥接等。编译不过均为单点替换, 不影响契约与 JS 侧 - 旧版 HBuilderX(Android <3.96 / iOS <3.97)HookProxy 不生效时,靠 startCapture (SDK init 调用)兜底安装,覆盖窗口收窄为「SDK init 后」;插件源码不依赖 模块导入即安装(顶层语句在 uts2swift 下非法,见实现文件头【跨端约束】)
- 插件版本:
crash-record.uts的PLUGIN_VERSION(与插件 package.json 保持一致); 记录 JSON 的library_version字段携带插件版本,便于服务端区分数据形态

收藏人数:
下载插件并导入HBuilderX
赞赏(0)
下载 2
赞赏 0
下载 12574213
赞赏 1949
赞赏
京公网安备:11010802035340号