使用ZXing库实现Android后台无UI持续扫码的问题求助
问题描述
我想开发一款能在后台持续扫描二维码的应用,类似超市的扫码设备。之前用ZXing库实现了持续扫码,但必须显示相机预览才能正常工作,而我不需要这个预览界面。我是Android开发新手,想问问这是否可行?我已经创建了Service尝试后台扫码,但没法正常工作,Scanned barcode:日志始终没输出,代码如下:
package com.cloudchef.recipecreation.service; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; import androidx.annotation.Nullable; import com.google.zxing.BarcodeFormat; import com.journeyapps.barcodescanner.BarcodeCallback; import com.journeyapps.barcodescanner.BarcodeResult; import com.journeyapps.barcodescanner.BarcodeView; import com.journeyapps.barcodescanner.DefaultDecoderFactory; import com.journeyapps.barcodescanner.camera.CameraSettings; import java.util.Collections; public class BarcodeScannerService extends Service { public static final String ACTION_SCAN = "com.cloudchef.recipecreation.service.ACTION_SCAN"; public static final String EXTRA_BARCODE = "com.cloudchef.recipecreation.service.EXTRA_BARCODE"; private BarcodeView barcodeView; private String lastText; // private BeepManager beepManager; private final BarcodeCallback callback = new BarcodeCallback() { @Override public void barcodeResult(BarcodeResult result) { Log.d("BarcodeScanner", "Scanned barcode: " + result.getText()); if (result.getText() == null || result.getText().equals(lastText)) { return; } lastText = result.getText(); // beepManager.playBeepSoundAndVibrate(); Intent intent = new Intent(ACTION_SCAN); intent.putExtra(EXTRA_BARCODE, lastText); sendBroadcast(intent); } }; @Override public void onCreate() { super.onCreate(); barcodeView = new BarcodeView(this); barcodeView.setDecoderFactory(new DefaultDecoderFactory(Collections.singletonList(BarcodeFormat.EAN_8))); CameraSettings cameraSettings = new CameraSettings(); cameraSettings.setRequestedCameraId(1); barcodeView.setCameraSettings(cameraSettings); barcodeView.decodeContinuous(callback); barcodeView.resume(); Log.d("Parth test", "Barcode scanner service started"); // beepManager = new BeepManager(this); // beepManager.setVibrateEnabled(true); // beepManager.setBeepEnabled(true); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } @Override public void onDestroy() { super.onDestroy(); barcodeView.stopDecoding(); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
解决方案
首先明确:后台无预览扫码在Android上是可行的,但你的Service实现存在几个关键问题,具体分析和修复方案如下:
1. BarcodeView必须绑定到视图层级才能工作
JourneyApps的BarcodeView本质是自定义View,依赖Android视图渲染机制,必须添加到Activity或Window的视图层级中才能启动相机并解码。在Service里直接创建BarcodeView但不绑定任何视图,相机根本不会启动,自然不会触发扫码回调。
2. 后台相机访问的系统限制
从Android 9(API 28)开始,系统禁止后台应用访问相机;Android 12(API 31)进一步强化了该限制,只有前台应用(有可见Activity或前台Service)才能访问相机。因此你需要:
- 将Service设置为前台Service,必须显示持续通知(前台Service的强制要求)
- 确保已申请相机权限,并且在运行时获取了用户授权
3. 修复后的实现思路
步骤1:申请权限
在AndroidManifest.xml中添加以下权限:
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-feature android:name="android.hardware.camera" />
步骤2:修改Service为前台Service,添加隐藏视图承载BarcodeView
更新Service的onCreate方法,创建隐藏Window来承载BarcodeView:
import android.app.Notification; import android.app.NotificationCompat; import android.view.Gravity; import android.view.WindowManager; import android.graphics.PixelFormat; import android.os.Build; @Override public void onCreate() { super.onCreate(); // 启动前台Service,显示通知 Notification notification = new NotificationCompat.Builder(this, "SCAN_CHANNEL") .setContentTitle("扫码服务运行中") .setContentText("后台正在扫描二维码") .setSmallIcon(R.drawable.ic_scan) // 替换为你的应用图标 .build(); startForeground(1, notification); // 创建隐藏Window,用于承载BarcodeView WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, PixelFormat.TRANSLUCENT ); params.gravity = Gravity.TOP | Gravity.LEFT; params.x = 0; params.y = 0; params.width = 1; // 设置极小尺寸,完全隐藏 params.height = 1; barcodeView = new BarcodeView(this); barcodeView.setDecoderFactory(new DefaultDecoderFactory(Collections.singletonList(BarcodeFormat.EAN_8))); CameraSettings cameraSettings = new CameraSettings(); cameraSettings.setRequestedCameraId(1); barcodeView.setCameraSettings(cameraSettings); barcodeView.decodeContinuous(callback); // 将BarcodeView添加到隐藏Window wm.addView(barcodeView, params); barcodeView.resume(); Log.d("Parth test", "Barcode scanner service started"); }
步骤3:清理资源
在onDestroy方法中移除视图并停止相机:
@Override public void onDestroy() { super.onDestroy(); WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); if (barcodeView != null) { barcodeView.stopDecoding(); wm.removeView(barcodeView); } stopForeground(true); // 停止前台服务并移除通知 }
4. 额外注意事项
- 相机ID问题:
setRequestedCameraId(1)指定的是后置相机?部分设备相机ID可能不同,建议自动检测可用相机或让用户选择 - 重复扫码过滤:你当前的
lastText逻辑可以保留,但要注意处理空值 - 电量消耗:后台持续扫码会显著增加电量消耗,建议添加开关让用户控制启停
内容的提问来源于stack exchange,提问作者Parth Shah
相关产品推荐
相关产品推荐

