Android开发如何正确获取ML Kit异步OCR处理的返回结果
问题根因
你的判断完全正确,问题出在ML Kit的OCR接口是异步执行的:recognizer.process(image)调用后会立刻返回一个Task对象,不会阻塞当前线程等待识别完成,你写的addOnSuccessListener里的逻辑会在OCR处理结束后才在后台线程触发。但当前代码在调用process()后没有等待回调执行,直接把还没更新的battleRecord(isWin还是初始false)返回给了上层,等回调里把isWin改成true存到数据库的时候,上层早就拿到错误的返回值走完逻辑了。
标准实现方案
Android官方推荐用协程处理这类异步串行任务,不需要写嵌套回调,代码逻辑和同步代码一样直观,改造步骤如下:
- 先在模块级
build.gradle的dependencies里引入必要依赖:
// 协程核心依赖 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" // 支持将Google服务的Task(ML Kit返回的异步任务对象)转为协程挂起函数 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3" // 提供生命周期感知的协程作用域,避免Activity销毁后协程泄漏 implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
- 改造
BattleLiveRecorderClass,把异步回调逻辑转为挂起函数,等待每一步OCR完成再往下执行:
import kotlinx.coroutines.tasks.await class BattleLiveRecorderClass(private val applicationContext: Context) { private val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS) private suspend fun insertPokemonFromImage(image: InputImage, bRecord: BattleRecord): BattleRecord { return try { // 等待OCR识别完成,*不会阻塞主线程*,识别完成后自动恢复后续逻辑 val extractedText = recognizer.process(image).await() // 原有OCR结果解析逻辑保持不变 bRecord.isWin = true saveToDb(bRecord) Log.d("BattleLiveRecorderClass::insertPokemonFromImage", "isWin: ${bRecord.isWin}") bRecord } catch (e: Exception) { Log.w("BattleLiveRecorderClass::insertPokemonFromImage", "OCR识别失败: $e") bRecord } } suspend fun record(battle: List<String>): Pair<Int?, Boolean> { Log.i("BattleLiveRecorderClass::record", "开始录制识别") var battleRecord = addBattleRecord(null) for(screenshot in battle){ val inputStream: InputStream = applicationContext.assets.open(screenshot) val image: InputImage = inputStreamToImage(inputStream) // 等当前图片OCR处理完再处理下一张 battleRecord = insertPokemonFromImage(image, battleRecord) } Log.d("BattleLiveRecorderClass::record", "最终识别结果: $battleRecord") return Pair(battleRecord.bid, battleRecord.isWin) } // 其余原有方法(addBattleRecord、saveToDb、inputStreamToImage等)保持不变 }
- 改造上层调用代码,在生命周期安全的协程作用域里调用挂起函数,拿最终结果:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_battle_screen_ocractivity) val recorder = BattleLiveRecorderClass(applicationContext) // lifecycleScope会在Activity销毁时自动取消协程,不会造成内存泄漏 lifecycleScope.launch { for(battle in listOf(battleLoss, battleWin)) { val result = recorder.record(battle) Log.i("BattleScreenOCRActivity::onCreate", "对局${result.first} 获胜状态: ${result.second}") // 此处拿到的result是所有OCR流程跑完的正确值,可以直接更新UI } } }
备选方案:如果你的项目暂时没接入协程,也可以给
record方法增加一个回调参数,在所有图片OCR流程全部走完后,在最后一个OCR回调里触发回调把结果传给上层,但多层回调会让代码可读性大幅下降,非必要不选这种实现。
内容的提问来源于stack exchange,提问作者Diego Lima
相关产品推荐
相关产品推荐

