安卓开发使用MLKit库实现扫描二维码功能实例

9,126 阅读8分钟

楔子

很多安卓er开发时的一大业务就是扫码支付,而在我开发过程中遇到的则是扫描绑定设备这个场景,但归根结底,它们都是基于扫描二维码这一功能实现的,那么如何去做这块功能就成了是最重要的。

正文

在目前这个大环境下,我想大多数开发者不会去主动手撸一个扫码的轮子,通常还是会主动寻求开源库的帮助,所以我们的需求也理所当然的变成了如何在各式各样五花八门的库中选取最合适的那个,然后去使用它,这一过程不同的开发者有不同的选择,也就会有不同的实现方式,所以接下来就来跟随作者的视角看看我是如何完成这个功能吧。

准备工作

因为扫描二维码需要使用相机,所以第一步我们要向用户申请这一权限,然后是导入我们选择的开源库依赖,再配合CameraX库(用于使用安卓机相机功能的官方开源库)的使用去实现扫描二维码,分析内容,最后向用户生成结果(这一步随着业务不同变化,结果的处理多种多样,这里就用网站链接二维码示例,最后分析好跳到浏览器访问网页或者打开webView访问),这里梳理好实现步骤,接下来一步步往下走即可。

开源库的选择

我再查询了一番资料后,基本了解了一些开源库的功能来源以及效果,所以我选择了Google的MLkit库,这个库是用于各种机器学习功能的,这里我们使用它的扫描和解析二维码这部分功能,因为是官方出的,还有很多配套的功能库,比如Google Play Services Vision 库,但是需要有配套的Google play框架服务之类的,总之在能用谷歌时是非常厉害的,当然还有很多好的库可以用,比如Zxing库等,这里不再展开说。

在导入依赖前可以在清单文件AndroidManifest.xml中加入:

<!-- 扫描二维码所需权限 -->
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FLASHLIGHT" />

<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature
    android:name="android.hardware.camera"
    android:required="false" />

之后我们这里直接把依赖导入app下的build.gradle文件中:

dependencies {

   ...
    // 相机相关依赖
    implementation 'androidx.camera:camera-camera2:1.1.0'
    implementation 'androidx.camera:camera-lifecycle:1.1.0'
    implementation 'androidx.camera:camera-view:1.1.0-beta02'

    // MLKit Barcode Scanning依赖
    implementation 'com.google.mlkit:barcode-scanning:17.0.0'

    //动态权限获取
    implementation 'pub.devrel:easypermissions:3.0.0'

//    implementation 'com.google.zxing:core:3.5.1'
//    implementation 'me.dm7.barcodescanner:zxing:1.9.13'
//    implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
}

这里简单说一下导入的几个依赖:

  1. CameraX库依赖

这里相机相关的依赖添加了三个,都是CameraX库的,它们分别是:

  1. Camera2 API:CameraX 库使用 Camera2 API 来访问相机硬件。implementation 'androidx.camera:camera-camera2:1.1.0' 依赖包含 Camera2 API 的实现。
  2. Lifecycle 组件:CameraX 库使用 Lifecycle 组件来管理相机的生命周期。implementation 'androidx.camera:camera-lifecycle:1.1.0' 依赖包含 Lifecycle 组件的实现。
  3. Viewfinder 组件:CameraX 库提供了一个名为 Viewfinder 的组件,它可以用来显示相机预览。implementation 'androidx.camera:camera-view:1.1.0-beta02' 依赖包含 Viewfinder 组件的实现。

这些依赖之所以需要一起使用,是因为它们共同组成了 CameraX 库的核心部分,而且这些依赖之间也存在依赖关系,camera-view 依赖需要依赖 camera-camera2 和 camera-lifecycle 依赖。

当然,它们也可以独立使用,比如:如果我们需要在应用中使用 CameraX 的相机预览功能,则应该添加 camera-view 依赖;想要访问相机硬件并捕获图像的话,就得添加 camera-camera2 依赖; camera-lifecycle 依赖则提供了相机生命周期的管理功能,可以帮助我们更好地管理相机资源。

为了方便起见,我们还是把三个依赖都添加进项目中吧。

  1. MLKit Barcode Scanning依赖

这个就是我们用来实现扫描功能的重要依赖,是导入了MLKit库的 Barcode Scanning部分,用来扫描和解析条形码和二维码的。

  1. 简化 Android 权限请求依赖

这个库的导入是用于简化 Android 权限请求的过程。该库由 Google 开发和维护,旨在提供一种简单、易用的方式来请求用户授权,以便应用程序可以访问敏感权限,使用这个库我们可以不必编写复杂的权限请求逻辑,而是可以在几行代码中完成权限请求和处理。

上面就是我们添加的几个依赖的简单的说明补充,同步好Gradle(Sync Project with Gradle Files ),接下来进入业务层。

代码开发

首先当然先把UI布局大概框架写好:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.camera.view.PreviewView
        android:id="@+id/preview_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/result_text_view"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/result_text_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:background="@android:color/white"
        android:gravity="center"
        android:textColor="@android:color/black"
        android:textSize="18sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

这里我们简单的处理了一下UI,在约束布局ConstraintLayout中添加了用来预览相机画面的PreviewView组件和用来输出二维码分析结果的TextView。

接下来就是处理Activity层代码:
这部分代码过长,就不放进文章了可以去作者的Github地址上看看本文件内容(QrScanActivity.java)MyTest/app/src/main/java/com/example/mytest/QrScanActivity.java at main · ObliviateOnline/MyTest · GitHub

这地方的逻辑处理其实也不复杂,我们慢慢分析。

第一步:动态申请相机权限

首先我们因为需要调用摄像头,所以应该动态向用户申请权限:

image.png

image.png

image.png

image.png

这里是调用了我们的EasyPermissions库,这块代码可以参考作者之前的文章(安卓开发小技巧——动态获取权限 - 掘金 (juejin.cn)),效果如下:

8823cadf924b9ae23ef4e0bb35a85541 (1).gif

有了相机调用权限后,这里先在QrScanActivity中创建这些一会要用的对象:

private PreviewView previewView;
private TextView resultTextView;
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
private BarcodeScanner barcodeScanner;

其中previewView用来处理摄像头预览画面;
resultTextView是扫描结果输出;
而cameraProviderFuture是一个 ListenableFuture<ProcessCameraProvider> 类型的对象,它是用于获取 ProcessCameraProvider 实例,ProcessCameraProvider 类提供了一个相机生命周期的管理器,可用于绑定相机的不同组件(例如 PreviewImageAnalysis)以及相机的生命周期。通过使用 cameraProviderFuture.get() 方法,可以在需要时获取 ProcessCameraProvider 实例。由于相机初始化需要一定的时间,因此 cameraProviderFuture 可以异步地初始化相机,并在相机准备好后通知应用程序,如下:

cameraProviderFuture = ProcessCameraProvider.getInstance(this);

cameraProviderFuture.addListener(() -> {
    try {
        ProcessCameraProvider cameraProvider = cameraProviderFuture.get();

        Preview preview = new Preview.Builder().build();
        preview.setSurfaceProvider(previewView.getSurfaceProvider());

        CameraSelector cameraSelector = new CameraSelector.Builder()
                .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                .build();

        Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview);
// 绑定ImageAnalysis实例到相机生命周期中
cameraProvider.bindToLifecycle(this, cameraSelector, imageAnalysis);

最后的barcodeScanner就是我们MLKit库用来处理二维码的,我们可以通过BarcodeScanning.getClient() 创建了一个 BarcodeScanner 实例。然后在 ImageAnalysis.Analyzer 的 analyze() 方法中,我们通过 BarcodeScanner 实例的 process() 方法,将相机捕捉到的图像作为输入,执行二维码的识别操作。如果识别成功,我们可以在回调中获取到识别结果,并将结果显示在 UI 界面上: 即如下这块代码:

// 创建BarcodeScanner实例
barcodeScanner = BarcodeScanning.getClient();

// 创建ImageAnalysis实例
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
        .build();

// 设置ImageAnalysis分析器
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), new ImageAnalysis.Analyzer() {
    @Override
    @OptIn(markerClass = ExperimentalGetImage.class)
    public void analyze(@NonNull  ImageProxy image) {
        // 获取ImageProxy对象
        Image mediaImage = image.getImage();
        if (mediaImage != null) {
            // 创建InputImage对象
            InputImage inputImage = InputImage.fromMediaImage(mediaImage, image.getImageInfo().getRotationDegrees());

            // 使用BarcodeScanner识别二维码
            barcodeScanner.process(inputImage)
                    .addOnSuccessListener(barcodes -> {
                        for (Barcode barcode : barcodes) {
                            if (barcode.getFormat() == Barcode.FORMAT_QR_CODE) {
                                String qrCodeValue = barcode.getRawValue();
                                // 在UI线程中更新TextView
                                runOnUiThread(() -> resultTextView.setText(qrCodeValue));
                            }
                        }
                    })
                    .addOnFailureListener(e -> {
                        // 处理扫描失败的情况
                        Log.e("QrScanActivity", "Failed to scan barcode", e);
                    })
                    .addOnCompleteListener(task -> image.close());
        } else {
            image.close();
        }
    }
});

完成上述代码后,我们就能运行程序,实现扫描二维码并识别,完整效果流程如下:

掘金可能不给放扫描二维码的页面。。。删了,后面再看看

可以看到页面最底下显示了二维码的识别后内容,我这里是在二维码里放了一句诗,当然,这个扫二维码的过程是连续的,也就是可以及时的,二维码变化可以立即重新出结果:

image.png

cameraProviderFuture.addListener(() -> {}, ContextCompat.getMainExecutor(this));

原理就是前面的那段代码中setupCamera方法实现的,当相机捕捉到新的帧时,ImageAnalysis.Analyzer 的 analyze() 方法会被触发。在 analyze() 方法中,我们通过 BarcodeScanner 实例的 process() 方法对当前帧进行二维码识别。由于这个方法是在异步线程中执行的,因此即使相机不断捕捉新的帧,也不会阻塞 UI 线程。如果在当前帧中检测到了二维码,我们就可以在回调中获取到相应的识别结果,并将结果显示在 UI 界面上。这样,只要相机不断捕捉新的帧,并且帧中包含二维码,就可以一直进行二维码的实时识别。

结语

写完发现自己讲的还不是很细致,有些东西还是没有说明白,但大致也通过这则示例展现出来了,依赖一导入,后面内容就是CameraX库的使用和MLKit库的使用了,结合在一起就实现了扫码功能,在此基础上,是将分析好的结果如何做下一步处理都是需求的问题了,附加一句,我们自己的开发项目用的是Zxing库实现的,看了一遍,是自定义View去做的,处理分析结果的代码也挺复杂,所以感觉使用CameraX库+Google MLKit库的方式就显得格外轻松了。

欢迎掘友们指正!