You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 12:10:34