- 随着代码更新迭代,发展迅速的前提下,作为一个java后端很苦恼的就是接触一些自己不擅长的领域,,现如今有公司都封装了业务类,所谓万物皆可以调用,那么今天就来说一说如何编写摄像头识别二维码/条形码业务类的实例代码;(博主在Android也是新手,只能总结经验,希望可以帮助到大家)
首先,所谓的业务类就是自己先编写这块业务的主要代码,然后需要这块业务时直接调用即可,这样可以调高代码的复用率与减少工作量,那么接下来让我们先了解了解安卓实现摄像头识别二维码/条形码的几种实现方式
- 第一种(也是博主使用的一种):使用Google ML Kit(Google ML Kit是一种强大的机器学习库,支持二维码和条形码扫描。它简单易用,支持多种格式。)
- 第二种:使用ZXing库(ZXing是一个开源的条形码和二维码图像处理库,广泛用于Android应用中。)博主第一次实例尝试用到,但是发现不太好用,因为还要自己下载ZXing
- 第三种:使用Google Vision API(Google Vision API是另一种处理图像的强大工具,但它的使用相比ML Kit更复杂。)
- 第四种:使用OpenCV(OpenCV是一种计算机视觉库,可以处理图像和视频数据。)
- 第五种:使用Firebase ML Kit(Firebase ML Kit与Google ML Kit类似,但集成了Firebase的更多功能。)
总结
以上几种方式都可以用于在Android应用中实现摄像头识别二维码。选择哪种方式主要取决于具体需求、技术栈和开发者的熟悉程度。Google ML Kit和ZXing是最常用和最推荐的方法,因为它们易于集成且功能强大。
下面可以写业务类了,但是写之前先配置相关的配置文件
其中比较重要的一点是:
在AndroidManifest.xml中一定配置摄像机权限
<uses-permission android:name="android.permission.CAMERA" />
并在应用中声明相机使用权限
<application
...
<uses-feature android:name="android.hardware.camera.any" android:required="true" />
</application>
首先,在build.gradle
文件中添加相关的依赖项:
implementation 'androidx.camera:camera-core:1.1.0'
implementation 'androidx.camera:camera-camera2:1.1.0'
implementation 'androidx.camera:camera-lifecycle:1.1.0'
implementation 'androidx.camera:camera-view:1.0.0-alpha29'
implementation 'com.google.mlkit:barcode-scanning:17.0.2'
implementation 'com.google.mlkit:text-recognition-chinese:16.0.0'
内部业务和代码原理实现:
1. 摄像头预览和分析配置
- PreviewView:用于显示摄像头预览。
- ProcessCameraProvider:管理摄像头的生命周期。
- CameraSelector:选择前置或后置摄像头。
- ImageAnalysis:用于图像分析和处理。
public void startCamera() {
ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(context);
cameraProviderFuture.addListener(() -> {
try {
cameraProvider = cameraProviderFuture.get();
bindPreview();
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "摄像头初始化失败。", e);
Toast.makeText(context, "摄像头初始化失败。", Toast.LENGTH_SHORT).show();
}
}, ContextCompat.getMainExecutor(context));
}
private void bindPreview() {
Preview preview = new Preview.Builder().build();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
preview.setSurfaceProvider(previewView.getSurfaceProvider());
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(context), new ImageAnalysis.Analyzer() {
@Override
public void analyze(@NonNull ImageProxy imageProxy) {
if (!isPaused) {
analyzeImage(imageProxy);
} else {
imageProxy.close();
}
}
});
camera = cameraProvider.bindToLifecycle((LifecycleOwner) context, cameraSelector, preview, imageAnalysis);
}
public void startCamera() {
- 定义一个名为startCamera
的公共方法。ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(context);
- 获取ProcessCameraProvider
的实例,它是用来控制摄像头的类。cameraProviderFuture.addListener(() -> {
- 为cameraProviderFuture
添加一个监听器,当获取ProcessCameraProvider
实例完成时调用。try { cameraProvider = cameraProviderFuture.get(); bindPreview(); }
- 在尝试块中获取ProcessCameraProvider
实例并调用bindPreview
方法。catch (ExecutionException | InterruptedException e) {
- 捕捉执行和中断异常。Log.e(TAG, "摄像头初始化失败。", e);
- 如果发生异常,记录错误日志。Toast.makeText(context, "摄像头初始化失败。", Toast.LENGTH_SHORT).show();
- 显示初始化失败的吐司消息。}, ContextCompat.getMainExecutor(context));
- 指定监听器在主线程上执行。
private void bindPreview() {
- 定义一个名为bindPreview
的私有方法。Preview preview = new Preview.Builder().build();
- 创建一个新的Preview
实例,它用来管理摄像头预览。CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();
- 创建一个CameraSelector
实例,选择后置摄像头。preview.setSurfaceProvider(previewView.getSurfaceProvider());
- 设置预览视图的表面提供者。ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build();
- 创建一个ImageAnalysis
实例,用于处理摄像头的图像数据。imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(context), new ImageAnalysis.Analyzer() { ... });
- 设置图像分析器,使用主线程执行分析任务。if (!isPaused) { analyzeImage(imageProxy); } else { imageProxy.close(); }
- 如果扫描没有暂停,则调用analyzeImage
方法分析图像,否则关闭imageProxy
。cameraProvider.bindToLifecycle((LifecycleOwner) context, cameraSelector, preview, imageAnalysis);
- 将摄像头绑定到生命周期所有者,开始预览和图像分析。
2. 二维码识别
- ML Kit:用于文本识别和条形码扫描
@ExperimentalGetImage
private void analyzeImage(ImageProxy imageProxy) {
// 调用二维码/条形码识别
BarcodeScanning.getClient()
.process(image)
.addOnSuccessListener(barcodes -> {
if (!barcodes.isEmpty()) {
for (Barcode barcode : barcodes) {
Log.d(TAG, "检测到条码: " + barcode.getDisplayValue());
Toast.makeText(context, "扫描结果: " +
barcode.getDisplayValue(), Toast.LENGTH_SHORT).show();
pauseScanning();
}
} else {
Log.d(TAG, "未检测到条码");
}
})
.addOnFailureListener(e -> {
Log.e(TAG, "条码扫描失败。错误信息:" + e.getMessage(), e);
})
.addOnCompleteListener(task -> {
if (pendingTasks.decrementAndGet() == 0) {
imageProxy.close();
}
Log.d(TAG, "条码扫描完成");
});
}
@ExperimentalGetImage
- 注解,表示使用实验性的getImage
方法。private void analyzeImage(ImageProxy imageProxy) {
- 定义一个名为analyzeImage
的私有方法。Image mediaImage = imageProxy.getImage();
- 获取ImageProxy
中的Image
实例。if (mediaImage != null) {
- 检查mediaImage
是否为空。InputImage image = InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees());
- 从mediaImage
创建一个InputImage
实例,并根据图像信息设置旋转角度。BarcodeScanner scanner = BarcodeScanning.getClient();
- 获取条码扫描客户端。scanner.process(image)
- 处理图像进行条码扫描。.addOnSuccessListener(barcodes -> { ... })
- 成功时的回调,处理检测到的条码。for (Barcode barcode : barcodes) { Log.d(TAG, "检测到条码: " + barcode.getDisplayValue()); Toast.makeText(context, "扫描结果: " + barcode.getDisplayValue(), Toast.LENGTH_SHORT).show(); }
- 遍历并显示检测到的条码。imageProxy.close();
- 关闭imageProxy
以释放资源。.addOnFailureListener(e -> { Log.e(TAG, "条码扫描失败。错误信息:" + e.getMessage(), e); imageProxy.close(); });
- 失败时的回调,记录错误并关闭imageProxy
。} else { imageProxy.close(); }
- 如果mediaImage
为空,关闭imageProxy
。
业务底层逻辑
- 摄像头初始化:通过
ProcessCameraProvider
实例化摄像头并绑定生命周期。 - 预览配置:通过
Preview
和PreviewView
设置摄像头预览。 - 文本识别和二维码扫描:使用ML Kit的文本识别和条形码扫描器对图像进行处理,提取所需的信息。
下面是Activity中调用了,其中这个类继承了
AppCompatActivity
类定义与成员变量
private static final int CAMERA_PERMISSION_REQUEST_CODE = 1001;
private QRCodeScanner qrCodeScanner;
private static final int CAMERA_PERMISSION_REQUEST_CODE = 1001;
- 定义一个静态常量,用于标识摄像头权限请求的请求码。private QRCodeScanner qrCodeScanner;
- 定义一个QRCodeScanner
类型的私有成员变量,用于管理二维码扫描功能。
onCreate
方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_qrcodescanner);
PreviewView previewView = findViewById(R.id.previewView);
qrCodeScanner = new QRCodeScanner(this, previewView);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
qrCodeScanner.startCamera();
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE);
}
}
@Override
- 标记方法重写。protected void onCreate(Bundle savedInstanceState) {
- 定义onCreate
方法,当活动创建时调用。super.onCreate(savedInstanceState);
- 调用父类的onCreate
方法。setContentView(R.layout.activity_qrcodescanner);
- 设置活动的布局文件为activity_qrcodescanner
。PreviewView previewView = findViewById(R.id.previewView);
- 获取布局中的PreviewView
视图,用于显示摄像头预览。qrCodeScanner = new QRCodeScanner(this, previewView);
- 创建QRCodeScanner
对象,传入上下文和PreviewView
。if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
- 检查是否已授予摄像头权限。qrCodeScanner.startCamera();
- 如果已授予权限,则启动摄像头。} else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE); }
- 否则请求摄像头权限。
onRequestPermissionsResult
方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (qrCodeScanner != null) {
qrCodeScanner.startCamera();
} else {
Toast.makeText(this, "初始化失败,请重试", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "需要摄像头权限", Toast.LENGTH_SHORT).show();
}
}
}
@Override
- 标记方法重写。public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- 定义onRequestPermissionsResult
方法,当权限请求结果返回时调用。super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- 调用父类的onRequestPermissionsResult
方法。if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
- 检查请求码是否与摄像头权限请求码匹配。if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- 检查权限请求结果是否为授予权限。if (qrCodeScanner != null) { qrCodeScanner.startCamera(); }
- 如果qrCodeScanner
不为空,则启动摄像头。else { Toast.makeText(this, "初始化失败,请重试", Toast.LENGTH_SHORT).show(); }
- 如果qrCodeScanner
为空,显示初始化失败的吐司消息。} else { Toast.makeText(this, "需要摄像头权限", Toast.LENGTH_SHORT).show(); }
- 如果未授予权限,显示需要摄像头权限的吐司消息。
总结
Activity
是一个活动类,负责管理摄像头权限请求和启动摄像头预览。onCreate
方法中初始化了QRCodeScanner
对象,并检查和请求摄像头权限。onRequestPermissionsResult
方法处理权限请求的结果,并根据结果启动摄像头或显示相应的消息。
最后前段页面
在avtivity-mian.xml中写上前端代码,就ok了
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:orientation="vertical"
tools:context=".Activity">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:accessibilityTraversalAfter="@id/previewView"
android:accessibilityTraversalBefore="@id/previewView" />
</RelativeLayout>
最后总结其他的方法实现:
1. 使用ZXing库
步骤:
- 添加依赖:
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
implementation 'com.google.zxing:core:3.4.1'
- 初始化和使用:
IntentIntegrator integrator = new IntentIntegrator(this);
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE);
integrator.setPrompt("扫描二维码");
integrator.setCameraId(0); // Use a specific camera of the device
integrator.setBeepEnabled(true);
integrator.setBarcodeImageEnabled(true);
integrator.initiateScan();
- 接收结果:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if (result != null) {
if (result.getContents() == null) {
Log.d("QRCodeScanner", "Cancelled scan");
Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show();
} else {
Log.d("QRCodeScanner", "Scanned");
Toast.makeText(this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show();
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
2. 使用Google Vision API
步骤:
- 添加依赖:
implementation 'com.google.android.gms:play-services-vision:20.1.3'
- 初始化和使用:
BarcodeDetector barcodeDetector = new BarcodeDetector.Builder(context)
.setBarcodeFormats(Barcode.ALL_FORMATS)
.build();
CameraSource cameraSource = new CameraSource.Builder(context, barcodeDetector)
.setFacing(CameraSource.CAMERA_FACING_BACK)
.setRequestedPreviewSize(1280, 1024)
.setRequestedFps(2.0f)
.setAutoFocusEnabled(true)
.build();
- 处理结果:
barcodeDetector.setProcessor(new Detector.Processor<Barcode>() {
@Override
public void release() {}
@Override
public void receiveDetections(Detector.Detections<Barcode> detections) {
final SparseArray<Barcode> barcodes = detections.getDetectedItems();
if (barcodes.size() != 0) {
// Process the barcode
Log.d(TAG, "检测到条码: " + barcodes.valueAt(0).displayValue);
}
}
});
3. 使用OpenCV
步骤:
- 添加依赖:
implementation project(':opencv')
- 使用OpenCV处理图像数据:
public void processFrame(Mat frame) {
// Convert frame to grayscale
Imgproc.cvtColor(frame, frame, Imgproc.COLOR_BGR2GRAY);
// Detect QR code
Mat points = new Mat();
QRCodeDetector qrCodeDetector = new QRCodeDetector();
String decodedText = qrCodeDetector.detectAndDecode(frame, points);
if (!decodedText.isEmpty()) {
Log.d(TAG, "检测到二维码: " + decodedText);
}
}
4. 使用Firebase ML Kit
步骤:
- 添加依赖:
implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
implementation 'com.google.firebase:firebase-ml-vision-barcode-model:16.0.2'
- 使用Firebase的ML Kit进行二维码扫描:
FirebaseVisionBarcodeDetectorOptions options =
new FirebaseVisionBarcodeDetectorOptions.Builder()
.setBarcodeFormats(FirebaseVisionBarcode.FORMAT_QR_CODE)
.build();
FirebaseVisionBarcodeDetector detector = FirebaseVision.getInstance()
.getVisionBarcodeDetector(options);
detector.detectInImage(image)
.addOnSuccessListener(new OnSuccessListener<List<FirebaseVisionBarcode>>() {
@Override
public void onSuccess(List<FirebaseVisionBarcode> barcodes) {
for (FirebaseVisionBarcode barcode : barcodes) {
Log.d(TAG, "检测到条码: " + barcode.getDisplayValue());
}
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.e(TAG, "条码扫描失败。错误信息:" + e.getMessage(), e);
}
});