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

ARCore实时帧捕获文本识别:是否需要使用信号量?

解决实时OCR识别的崩溃与线程同步问题

首先,你的APP崩溃的直接原因非常明确:你在子线程(executor.execute()执行的任务)里调用了captureImage(),而这个方法里弹出了Toast——Toast必须在UI线程(拥有Looper的线程)中创建和显示,子线程默认没有初始化Looper,所以抛出了Can't toast on a thread that has not called Looper.prepare()异常。

接下来,我们一步步解决问题,同时优化你的线程同步逻辑:

1. 修复Toast的UI线程执行问题

把captureImage()中的Toast显示代码移到UI线程执行。你可以用Activity的runOnUiThread()方法,或者复用你已经定义的handler:

// 原来在子线程中执行的错误写法
Toast.makeText(this, "捕获图像", Toast.LENGTH_SHORT).show();

// 修改为UI线程执行:
runOnUiThread(() -> {
    Toast.makeText(this, "捕获图像", Toast.LENGTH_SHORT).show();
});

如果captureImage()不在Activity类中,就传递Handler实例过来,用handler.post()包裹Toast代码即可。

2. 修复locked变量的线程安全问题

你的locked是普通布尔变量,在UI线程(onUpdateFrame回调)和子线程(OCR任务)中都被读写,这会导致竞态条件(比如多个线程同时修改或读取,导致逻辑混乱)。

推荐用AtomicBoolean代替普通boolean,它是线程安全的原子类,不需要额外的同步块就能保证读写操作的原子性:

// 替换原来的private boolean locked = false;
private AtomicBoolean locked = new AtomicBoolean(false);

然后修改onUpdateFrame中的判断逻辑,用原子操作确保同一时间只有一个OCR任务在执行:

public void onUpdateFrame(FrameTime frameTime) {
    Frame frame = arFragment.getArSceneView().getArFrame();
    if (frame == null) {
        return;
    }
    
    // 用compareAndSet原子性判断并设置锁,避免多线程竞态
    if(frame.getCamera().getTrackingState() == TrackingState.TRACKING && locked.compareAndSet(false, true)) {
        // 显示ProgressDialog要确保在UI线程(onUpdateFrame属于ARCore渲染线程,不是UI线程)
        handler.post(() -> {
            if (mProgressDialog == null) {
                mProgressDialog = ProgressDialog.show(this, "Processing", "OCR...", true);
            } else {
                mProgressDialog.show();
            }
        });
        
        executor.execute(() -> {
            Bitmap b = captureImage();
            final String text = getOCRResult(b);
            
            handler.post(() -> {
                if(text != null && !text.isEmpty()){
                    doSomething();
                    // 识别成功后保持锁定,停止后续帧捕获
                } else{
                    // 识别失败,解锁允许下一帧处理
                    locked.set(false);
                }
                // 别忘了隐藏进度弹窗
                if(mProgressDialog != null && mProgressDialog.isShowing()){
                    mProgressDialog.dismiss();
                }
            });
        });
    }
}

compareAndSet方法会原子性地检查当前值是否为false,如果是则设置为true并返回true,否则返回false,完美避免了多个线程同时进入任务提交逻辑的问题。

3. 关于是否需要信号量

其实你当前的场景完全不需要使用信号量。信号量主要用于控制多个线程对有限资源的访问并发数(比如限制最多3个线程同时处理任务),而你的需求是“同一时间只允许一个线程执行OCR识别”,用AtomicBoolean的原子锁已经完全能满足需求,比信号量更简单直接。

额外优化建议

  • 如果OCR识别耗时较长,建议给getOCRResult()设置超时机制,避免UI长时间无响应。
  • 注意在页面销毁时,要关闭executor、销毁ProgressDialog和handler,避免内存泄漏。

内容的提问来源于stack exchange,提问作者nCoder

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 17:07:47