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

Android条码扫描器内存泄漏崩溃,BarcodeDetector.release调用异常排查

二维码扫描Activity问题求助

问题概述

目前存在两个关于二维码扫描Activity的问题需排查:

  1. BarcodeDetector的release()方法仅在少数设备上被调用,不清楚该方法的理想调用时机及背后原因;
  2. 扫描二维码后程序会在release()方法处崩溃,且该问题仅出现在华为Mate 20设备上,无法获取该设备日志。

相关代码

public class ScannedBarcodeActivity extends AppCompatActivity {


    SurfaceView surfaceView;
    TextView txtBarcodeValue;
    private BarcodeDetector barcodeDetector;
    private CameraSource cameraSource;
    private static final int REQUEST_CAMERA_PERMISSION = 201;
     String intentData = "";
    boolean isEmail = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scanned_barcode);
        initViews();
    }

    private void initViews() {
        txtBarcodeValue = findViewById(R.id.txtBarcodeValue);
        surfaceView = findViewById(R.id.surfaceView);

    }

    private void initialiseDetectorsAndSources() {

        Toast.makeText(getApplicationContext(), "Barcode scanner started", Toast.LENGTH_SHORT).show();
        barcodeDetector = new BarcodeDetector.Builder(this)
                .setBarcodeFormats(Barcode.ALL_FORMATS)
                .build();

        cameraSource = new CameraSource.Builder(this, barcodeDetector)
                .setRequestedPreviewSize(1920, 1080)
                .setAutoFocusEnabled(true) //you should add this feature
                .build();

        surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                try {
                    if (ActivityCompat.checkSelfPermission(ScannedBarcodeActivity.this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
                        cameraSource.start(surfaceView.getHolder());
                    } else {
                        ActivityCompat.requestPermissions(ScannedBarcodeActivity.this, new
                                String[]{android.Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                cameraSource.stop();
            }
        });


        barcodeDetector.setProcessor(new Detector.Processor<Barcode>() {
            @Override
            public void release() {
                Toast.makeText(getApplicationContext(), "To prevent memory leaks barcode scanner has been stopped", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void receiveDetections(Detector.Detections<Barcode> detections) {
                final SparseArray<Barcode> barcodes = detections.getDetectedItems();
                if (barcodes.size() != 0) {
                    txtBarcodeValue.post(new Runnable() {
                        @Override
                        public void run() {

                            intentData = barcodes.valueAt(0).displayValue;
                           Intent pushBack = new Intent(ScannedBarcodeActivity.this,TypeALabelActivity.class);

                           String type = "";
                            JSONObject obj = null;
                            try {
                                obj = new JSONObject(intentData);
                                type = obj.getString("LType");
                            } catch (JSONException e) {
                                e.printStackTrace();
                            }


                           if(type.equals("B")){
                               pushBack = new Intent(ScannedBarcodeActivity.this,TypeBLabelActivity.class);
                           }else if(type.equals("C")){
                               pushBack = new Intent(ScannedBarcodeActivity.this,TypeCLabelActivity.class);
                           }else if(type.equals("D")){
                               pushBack = new Intent(ScannedBarcodeActivity.this,TypeDLabelActivity.class);
                           }else if(type.equals("E")){
                               pushBack = new Intent(ScannedBarcodeActivity.this,TypeELabelActivity.class);
                           }
                            System.out.println("intentData" + intentData);
                           pushBack.putExtra("QR",intentData);
                           startActivity(pushBack);
                           finish();

//                            if (barcodes.valueAt(0).email != null) {
//                                txtBarcodeValue.removeCallbacks(null);
//                                intentData = barcodes.valueAt(0).email.address;
//                                txtBarcodeValue.setText(intentData);
//                                isEmail = true;
//                             } else {
//                                isEmail = false;
//
//                            }
                        }
                    });
                }
            }
        });
    }


    @Override
    protected void onPause() {
        super.onPause();
        cameraSource.release();
    }

    @Override
    protected void onResume() {
        super.onResume();
        initialiseDetectorsAndSources();
    }
}

崩溃细节

崩溃触发在扫描二维码后的release()方法中:

@Override
public void release() {
    Toast.makeText(getApplicationContext(), "To prevent memory leaks barcode scanner has been stopped", Toast.LENGTH_SHORT).show();
}

排查建议

针对问题1:BarcodeDetector.release()调用时机疑问

  • BarcodeDetector的release()是框架在检测器不再被使用时自动触发的方法,通常在关联的CameraSource调用release()后执行——因为CameraSource持有检测器的引用,当相机资源释放时,会通知检测器回收自身资源。
  • 当前代码在onPause()调用cameraSource.release(),但每次onResume()都会重新初始化检测器和相机源,导致旧实例可能未被正确回收,部分设备的框架会延迟或不触发release()。
  • 优化方案:初始化新实例前,先检查并释放旧的barcodeDetector和cameraSource,避免重复创建引发资源泄漏;同时确保相机源的生命周期与Activity绑定,减少不必要的资源重建。

针对问题2:华为Mate 20上的崩溃排查

  • 上下文失效问题:release()执行时,Activity可能已经通过finish()进入销毁流程,此时用getApplicationContext()显示Toast可能因上下文不稳定崩溃。建议改用Activity弱引用,并判断Activity状态:
    private WeakReference<ScannedBarcodeActivity> activityRef;
    
    // 在initialiseDetectorsAndSources中初始化
    activityRef = new WeakReference<>(this);
    
    @Override
    public void release() {
        ScannedBarcodeActivity activity = activityRef.get();
        if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
            Toast.makeText(activity, "扫码器已停止以避免内存泄漏", Toast.LENGTH_SHORT).show();
        }
    }
    
  • 线程合规问题:release()可能在非UI线程调用,而Toast必须在UI线程执行,华为EMUI系统对线程检查更严格,需强制切换到UI线程:
    @Override
    public void release() {
        ScannedBarcodeActivity activity = activityRef.get();
        if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
            activity.runOnUiThread(() -> {
                Toast.makeText(activity, "扫码器已停止以避免内存泄漏", Toast.LENGTH_SHORT).show();
            });
        }
    }
    
  • 生命周期冲突问题:扫描到二维码后立即跳转页面并finish(),此时onPause()会触发cameraSource.release(),进而调用release(),但Activity已进入销毁流程。建议先停止扫描资源再跳转:
    @Override
    public void run() {
        // 先停止扫描资源
        cameraSource.stop();
        if (barcodeDetector != null) {
            barcodeDetector.release();
        }
    
        // 后续页面跳转逻辑
        intentData = barcodes.valueAt(0).displayValue;
        // ... 原有跳转代码
        startActivity(pushBack);
        finish();
    }
    
  • 系统特性适配:华为Mate 20的EMUI系统对相机资源回收有特殊逻辑,可尝试降低预览分辨率(当前1920x1080可能资源占用过高),或检查相机权限动态申请是否完全符合华为系统要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 09:55:59