# 语音测评闪退问题排查与解决方案 ## 🔍 **问题描述** App运行语音测评时经常闪退,尤其是在导入数据时更容易发生。 --- ## 📊 **问题分析** ### **可能原因1:内存不足** - **语音模型文件较大** - vosk-model-small-cn-0.22.zip 约50-100MB - 首次解压需要大量内存 - 解压时间约30秒 - **导入数据同时运行** - 如果正在导入大量学员数据 - 同时运行语音识别 - 内存可能不足导致崩溃 ### **可能原因2:页面切换时回调未停止** - **回调函数继续执行** - 页面卸载后,语音识别回调可能继续执行 - 访问已销毁的组件实例导致崩溃 - **状态检查不严格** - 只检查 `isRecording` 状态 - 没有检查页面是否已卸载 ### **可能原因3:数据导入错误影响系统** - **班级不存在** - 导入的学员数据包含不存在的班级 - 后端返回错误,前端处理不当 - **数据格式错误** - Excel格式不符合要求 - 必填字段为空 ### **可能原因4:模型初始化失败** - **模型文件损坏或缺失** - **路径解析错误** - **权限问题** --- ## ✅ **解决方案** ### **方案1:优化页面生命周期(已修复)** #### **添加页面卸载标记** ```javascript data() { return { // ... 其他数据 pageUnloaded: false // 页面卸载标记 } } ``` #### **在onUnload中设置标记** ```javascript onUnload() { console.log('[Speech] 页面卸载,清理资源') // 标记页面已卸载,防止回调继续执行 this.pageUnloaded = true // 停止语音识别 if (this.isRecording) { this.isRecording = false try { if (typeof stopSpeechVoice === 'function') { stopSpeechVoice() } } catch(e) { console.error('[Speech] 停止识别时出错:', e) } } // 清理定时器 this.stopAutoScroll() if (this.scrollTimer) { clearInterval(this.scrollTimer) this.scrollTimer = null } } ``` #### **添加onHide生命周期** ```javascript onHide() { console.log('[Speech] 页面隐藏,暂停识别') // 页面切换到后台时停止识别 if (this.isRecording) { this.handleStop() } } ``` #### **在回调中检查页面状态** ```javascript startSpeechVoice((res) => { // 检查页面是否已卸载 if (self.pageUnloaded) { console.log('[Speech] 页面已卸载,忽略回调') return } // 检查是否仍在录音状态 if (!self.isRecording) { console.log('[Speech] 已停止录音,忽略回调') return } // ... 处理识别结果 }) ``` --- ### **方案2:修复数据导入(已修复)** #### **班级验证** - ✅ 在导入前验证班级是否存在 - ✅ 班级不存在时,记录失败原因 - ✅ 不中断整个导入流程 #### **错误处理** - ✅ 导入失败的记录单独显示 - ✅ 提供详细的失败原因 - ✅ 失败详情支持滚动查看 #### **生成测试数据** - ✅ 使用 `generate_test_data.py` 生成符合要求的测试数据 - ✅ 所有字段自动标准化为系统选项 - ✅ 班级自动映射到系统实际班级 --- ### **方案3:优化语音模型加载** #### **延迟加载** ```javascript onLoad(options) { // 先加载内容列表 if (options.contentId) { this.loadContentById(options.contentId) } else { this.loadContentList() } // 延迟1秒后再初始化模型 setTimeout(() => { // #ifdef APP-PLUS this.initSpeechModel() // #endif }, 1000) } ``` #### **检查内存** ```javascript initSpeechModel() { // 检查可用内存(如果API支持) // #ifdef APP-PLUS try { const memInfo = plus.device.getInfo() console.log('[Speech] 设备内存信息:', memInfo) } catch(e) { console.log('[Speech] 无法获取内存信息') } // #endif // ... 继续初始化 } ``` --- ### **方案4:添加错误捕获** #### **全局错误处理** ```javascript // App.vue 或 main.js uni.onError((error) => { console.error('[Global] 全局错误:', error) // 可以记录到后端日志 // 显示友好的错误提示 uni.showToast({ title: '应用出错,请重启', icon: 'none', duration: 3000 }) }) ``` #### **Try-Catch包裹关键代码** ```javascript async evaluateScore() { if (!this.recognizedText || !this.selectedContent) { uni.showToast({ title: '请先完成语音识别', icon: 'none' }) return } this.isEvaluating = true try { const result = await evaluateSpeechRecognition( this.selectedContent.content, this.recognizedText ) if (result.code === 200 && result.data) { this.scoreResult = result.data await this.saveScoreToBackend(result.data) uni.showToast({ title: '评分完成', icon: 'success', duration: 2000 }) } else { throw new Error(result.msg || '评分失败') } } catch (error) { console.error('评分失败', error) uni.showToast({ title: error.message || '评分失败,请重试', icon: 'none', duration: 3000 }) } finally { this.isEvaluating = false } } ``` --- ## 🧪 **测试步骤** ### **1. 测试页面切换** 1. 打开语音测评页面 2. 开始语音识别 3. 立即返回首页 4. 观察是否崩溃 **预期结果:** ✅ 不崩溃,识别自动停止 ### **2. 测试数据导入** 1. 准备100条测试数据 2. 在语音测评页面 3. 同时执行数据导入 4. 观察是否崩溃 **预期结果:** ✅ 不崩溃,两个操作互不影响 ### **3. 测试模型加载** 1. 清除模型缓存 2. 重新打开语音测评 3. 等待模型加载 4. 观察是否崩溃 **预期结果:** ✅ 不崩溃,正常加载 ### **4. 测试长时间识别** 1. 开始语音识别 2. 持续朗读3分钟 3. 观察是否崩溃 **预期结果:** ✅ 不崩溃,正常识别 --- ## 📋 **使用建议** ### **避免同时操作** - ❌ 不要在导入数据时使用语音测评 - ❌ 不要在语音识别时导入大量数据 - ✅ 先完成一个操作,再进行另一个 ### **分批导入数据** - ✅ 每次导入不超过50条 - ✅ 等待上一批导入完成再导入下一批 - ✅ 避免一次性导入过多数据 ### **定期清理缓存** - ✅ 在设置中清理应用缓存 - ✅ 清理语音模型缓存后重新加载 - ✅ 卸载重装应用(如果频繁崩溃) ### **检查设备** - ✅ 确保设备有足够的可用内存(至少500MB) - ✅ 关闭其他占用内存的应用 - ✅ 重启设备 --- ## 🔧 **调试方法** ### **1. 查看控制台日志** ```bash # HBuilderX中查看真机运行日志 # 搜索关键字:[Speech]、Error、crash ``` ### **2. 查看后端日志** ```bash # 查看后端日志,是否有错误 tail -f logs/app.log | grep ERROR ``` ### **3. 使用测试数据** ```bash # 生成测试数据 python generate_test_data.py # 导入测试数据,观察是否正常 ``` ### **4. 逐步排查** 1. **不使用语音测评** - 只导入数据 - 如果正常:语音测评有问题 - 如果崩溃:数据导入有问题 2. **不导入数据** - 只使用语音测评 - 如果正常:数据导入影响了语音测评 - 如果崩溃:语音测评本身有问题 3. **清除数据** - 卸载重装应用 - 如果正常:缓存数据有问题 - 如果崩溃:代码或资源文件有问题 --- ## 💡 **优化建议** ### **1. 按需加载模型** - 不在onLoad中自动初始化 - 用户点击"开始说话"时才加载 - 显示"模型加载中"提示 ### **2. 使用更小的模型** - 当前使用 vosk-model-small-cn-0.22 - 可以使用更小的模型(如果存在) - 或者使用在线API(需要网络) ### **3. 优化数据导入** - 使用分批导入(每批20-30条) - 显示导入进度 - 支持后台导入 ### **4. 添加异常恢复** - 崩溃后自动保存状态 - 重新打开时恢复上次状态 - 提供"恢复上次会话"功能 --- ## ✅ **验收标准** - [ ] 页面快速切换不崩溃 - [ ] 导入数据时不崩溃 - [ ] 长时间语音识别不崩溃 - [ ] 模型加载失败有友好提示 - [ ] 数据导入失败有详细错误信息 - [ ] 控制台无严重错误日志 - [ ] 内存占用在合理范围内 --- ## 📝 **修改文件清单** ### **已修改** 1. ✅ `fronted_uniapp/pages/speech/speech.vue` - 添加 `pageUnloaded` 标记 - 添加 `onHide` 生命周期 - 回调中检查页面状态 2. ✅ `Study-Vue-redis/ry-study-ui/src/views/study/classUser/index.vue` - 导入失败对话框添加滚动条 3. ✅ `Study-Vue-redis/ry-study-admin/.../StudyClassUserController.java` - 移除导入前的班级验证 4. ✅ `Study-Vue-redis/ry-study-system/.../StudyClassUserServiceImpl.java` - 添加逐条班级验证 - 记录失败原因到错误信息 ### **新增文件** 1. ✅ `generate_test_data.py` - 测试数据生成脚本 --- ## 🎯 **总结** ### **核心问题** - 页面卸载后回调继续执行 - 数据导入和语音识别同时进行 - 内存不足 ### **解决方法** - 严格的生命周期管理 - 状态检查 - 分批操作 - 错误处理 ### **使用建议** - 避免同时操作 - 定期清理缓存 - 使用测试数据验证 **现在请测试修复后的版本,观察是否还会闪退!** 🚀