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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 20:25:02