guoyu/_已清理文件备份_周六 22512/md/语音测评闪退问题排查.md
2025-12-06 20:11:36 +08:00

9.5 KiB
Raw Blame History

语音测评闪退问题排查与解决方案

🔍 问题描述

App运行语音测评时经常闪退尤其是在导入数据时更容易发生。


📊 问题分析

可能原因1内存不足

  • 语音模型文件较大

    • vosk-model-small-cn-0.22.zip 约50-100MB
    • 首次解压需要大量内存
    • 解压时间约30秒
  • 导入数据同时运行

    • 如果正在导入大量学员数据
    • 同时运行语音识别
    • 内存可能不足导致崩溃

可能原因2页面切换时回调未停止

  • 回调函数继续执行

    • 页面卸载后,语音识别回调可能继续执行
    • 访问已销毁的组件实例导致崩溃
  • 状态检查不严格

    • 只检查 isRecording 状态
    • 没有检查页面是否已卸载

可能原因3数据导入错误影响系统

  • 班级不存在

    • 导入的学员数据包含不存在的班级
    • 后端返回错误,前端处理不当
  • 数据格式错误

    • Excel格式不符合要求
    • 必填字段为空

可能原因4模型初始化失败

  • 模型文件损坏或缺失
  • 路径解析错误
  • 权限问题

解决方案

方案1优化页面生命周期已修复

添加页面卸载标记

data() {
    return {
        // ... 其他数据
        pageUnloaded: false // 页面卸载标记
    }
}

在onUnload中设置标记

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生命周期

onHide() {
    console.log('[Speech] 页面隐藏,暂停识别')
    // 页面切换到后台时停止识别
    if (this.isRecording) {
        this.handleStop()
    }
}

在回调中检查页面状态

startSpeechVoice((res) => {
    // 检查页面是否已卸载
    if (self.pageUnloaded) {
        console.log('[Speech] 页面已卸载,忽略回调')
        return
    }
    
    // 检查是否仍在录音状态
    if (!self.isRecording) {
        console.log('[Speech] 已停止录音,忽略回调')
        return
    }
    
    // ... 处理识别结果
})

方案2修复数据导入已修复

班级验证

  • 在导入前验证班级是否存在
  • 班级不存在时,记录失败原因
  • 不中断整个导入流程

错误处理

  • 导入失败的记录单独显示
  • 提供详细的失败原因
  • 失败详情支持滚动查看

生成测试数据

  • 使用 generate_test_data.py 生成符合要求的测试数据
  • 所有字段自动标准化为系统选项
  • 班级自动映射到系统实际班级

方案3优化语音模型加载

延迟加载

onLoad(options) {
    // 先加载内容列表
    if (options.contentId) {
        this.loadContentById(options.contentId)
    } else {
        this.loadContentList()
    }
    
    // 延迟1秒后再初始化模型
    setTimeout(() => {
        // #ifdef APP-PLUS
        this.initSpeechModel()
        // #endif
    }, 1000)
}

检查内存

initSpeechModel() {
    // 检查可用内存如果API支持
    // #ifdef APP-PLUS
    try {
        const memInfo = plus.device.getInfo()
        console.log('[Speech] 设备内存信息:', memInfo)
    } catch(e) {
        console.log('[Speech] 无法获取内存信息')
    }
    // #endif
    
    // ... 继续初始化
}

方案4添加错误捕获

全局错误处理

// App.vue 或 main.js
uni.onError((error) => {
    console.error('[Global] 全局错误:', error)
    // 可以记录到后端日志
    // 显示友好的错误提示
    uni.showToast({
        title: '应用出错,请重启',
        icon: 'none',
        duration: 3000
    })
})

Try-Catch包裹关键代码

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. 查看控制台日志

# HBuilderX中查看真机运行日志
# 搜索关键字:[Speech]、Error、crash

2. 查看后端日志

# 查看后端日志,是否有错误
tail -f logs/app.log | grep ERROR

3. 使用测试数据

# 生成测试数据
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 - 测试数据生成脚本

🎯 总结

核心问题

  • 页面卸载后回调继续执行
  • 数据导入和语音识别同时进行
  • 内存不足

解决方法

  • 严格的生命周期管理
  • 状态检查
  • 分批操作
  • 错误处理

使用建议

  • 避免同时操作
  • 定期清理缓存
  • 使用测试数据验证

现在请测试修复后的版本,观察是否还会闪退! 🚀