Android条码扫描器内存泄漏崩溃,BarcodeDetector.release调用异常排查
二维码扫描Activity问题求助
问题概述
目前存在两个关于二维码扫描Activity的问题需排查:
BarcodeDetector的release()方法仅在少数设备上被调用,不清楚该方法的理想调用时机及背后原因;- 扫描二维码后程序会在
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
相关产品推荐
相关产品推荐

