查看原文
其他

完美替代ZXing,统一扫码服务

技术最TOP 2022-08-26

The following article is from OpenCV or Android Author 易冬

Android 项目开发过程中,扫码场景下使用最多的开源库是 ZXing ,Github 上针对 ZXing 的优化和二次封装不胜枚举,但是 Zxing 的缺陷在于只是实现了扫码的一些基础操作,对于更为复杂的扫码环境比如强光,弯曲,形变等情况,并不能很好地支持。

今天,我们就介绍下 ZXing 的完美替代品 —— 华为统一扫码服务( Scan Kit )。关于 ZXing 和 Scan Kit 的对比,论坛内有一篇文章写得很全面:

https://developer.huawei.com/consumer/cn/forum/topic/0201248342859390343?fid=18,感兴趣的读者可以阅读一下。


顺便说一下,为了不影响读者阅读,后续文章关闭广告穿插,统一放置开头,谢谢。

简介

华为统一扫码服务(Scan Kit)提供便捷的条形码和二维码扫描、解析、生成能力,帮助开发者快速构建应用内的扫码功能。

得益于华为在计算机视觉领域能力的积累,Scan Kit 可以实现远距离码或小型码的检测和自动放大,同时针对常见复杂扫码场景(如反光、暗光、污损、模糊、柱面)做了针对性识别优化,提升扫码成功率与用户体验。

Scan Kit 支持 Android 和 iOS 系统集成。其中,Android 系统集成 Scan Kit 后支持横屏扫码能力。

支持的设备

平台设备类型OS版本
Android华为手机、华为平板EMUI 3.1以上
Android非华为手机Android 4.4及以上
iOS手机iOS 9.0以上

场景介绍

扫码

Scan Kit 支持扫描13种全球主流的码制式。如果开发者的应用只处理部分特定的码制式,开发者也可以在接口中指定制式以便加快扫码速度。已支持的码制式:

  • 一维码:EAN-8、EAN-13、UPC-A、UPC-E、Codabar、Code 39、Code 93、Code 128、ITF-14
  • 二维码:QR Code、Data Matrix、PDF417、Aztec

Scan Kit 提供多种调用模式,开发者可根据需求选择一个合适的模式构建扫码功能。

调用方式支持平台扫码流程扫码界面功能
Default View ModeAndroid/iOSScan Kit处理Scan Kit提供相机扫码(可以调用Bitmap mode增加导入图片扫码功能)。
Customized View ModeAndroid/iOSScan Kit处理开发者自定义相机扫码(可以叠加Bitmap mode增加导入图片扫码功能)。
Bitmap ModeAndroid/iOS开发者应用处理开发者自定义相机扫码、导入图片扫码。
MultiProcessor ModeAndroid开发者应用处理开发者自定义相机扫码、导入图片扫码,支持同时检测多个码。
  • 对于希望快速构建强大扫码功能的开发者,建议选择 Default View Mode 或者 Customized View Mode。在这两种模式下,Scan Kit 直接控制相机实现最优的相机 Zoom 控制、自适应的曝光调节、自适应对焦调节等操作,保障最佳的扫码体验,减少开发者的工作量。Default View Mode 和 Customized View Mode 的区别在于后者支持开发者自定义扫码界面 UI。
  • 对于希望完全自定义扫码流程并自行控制相机的开发者,可以选择 Bitmap Mode 构建扫码功能。Bitmap Mode 需要开发者自行控制相机,且提供相机扫码和导入图片扫码两种模式,在调用扫码接口时设置。当用户在扫描较小或者较远的二维码时,Scan Kit 会返回需要放大的倍数给您的应用,开发者只需按照返回值调整相机焦距就能快速获取满足条件的图片。请注意,Scan Kit 返回的放大倍数是通过检测结果和用户场景计算得出,建议开发者不要更改此放大倍数,否则可能会降低实际使用效果。
  • MultiProcessor Mode 适用于需要同时扫描多个码的场景。因为 MultiProcessor 处理效率低于其他三种调用方式,如果没有上述需求,建议使用其他三种调用方式。MultiProcessor Mode 提供同步和异步两种模式,可以根据实际需求选择一个合适的模式。

码值解析

Scan Kit可以将码的原始内容返回给开发者,还会针对使用特定内容格式编码的二维码/条形码进行分析并提取结构化数据,帮助开发者快速构建关联服务。已支持如下场景:联系人信息Wi-Fi 连接信息网页日历日程ID 卡短信电话邮件地理位置商品条码ISBN

码生成

Scan Kit 支持将字符串转换为一维码或二维码,目前已支持的码制式为EAN-8、EAN-13、UPC-A、UPC-E、Codabar、Code 39、Code 93、Code 128、ITF-14、QR Code、Data Matrix、PDF417、Aztec。开发者只需要提供字符串、码制式和尺寸要求即可获得相应的码图。

集成步骤

官方教程中【配置AppGallery Connect】步骤,如果应用无上架需求,可以忽略。

集成HMS Core SDK

配置HMS Core SDK的Maven仓地址

打开Android Studio项目级 build.gradle 文件

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    ext.kotlin_version = "1.5.0"
    repositories {
        google()
        jcenter()
        // 配置HMS Core SDK的Maven仓地址。
        maven {url 'https://developer.huawei.com/repo/'}
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.2.1"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

allprojects {
    repositories {
        google()
        jcenter() 
        // 配置HMS Core SDK的Maven仓地址。
        maven {url 'https://developer.huawei.com/repo/'}

    }
}

添加编译依赖

打开应用级的 build.gradle 文件,添加对应的编译依赖。华为官方给我们提供了两种类型的依赖包:

SDK

区别比较明显,包的大小和识别能力两个方面的影响,这里为了展示 Scan Kit 的能力,我们选用能力更全面的 scanplus 系列。

implementation 'com.huawei.hms:scanplus:1.3.2.300'

应用开发

动态权限请求处理,不单独介绍,穿插于代码中,重点介绍下 Scan Kit 的四种模式:

  • Default View Mode
  • Customized View Mode
  • Bitmap Mode
  • MultiProcessor Mode

Default View Mode

Default View Mode 提供相机扫码和导入图片扫码两个功能,提供完整的 Activity ,不需要开发者开发扫码界面的 UI 。一键开启扫码,处理扫码结果即可,非常适合定制化要求不高的一般扫码场景。主要步骤:

  • 创建扫码选项参数;
  • 启动扫码;
  • 实现回调接口接收扫码结果。
fun startDefaultMode(view: View) {
    // 扫码选项参数
    val options =
        HmsScanAnalyzerOptions.Creator().setHmsScanTypes(HmsScan.ALL_SCAN_TYPE).create()
    ScanUtil.startScan(
        this, REQUEST_CODE_SCAN_DEFAULT_MODE,
        options
    )
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (resultCode != RESULT_OK || data == null) {
        return
    }
    when (requestCode) {
        REQUEST_CODE_SCAN_DEFAULT_MODE -> {
            val hmsScan: HmsScan? = data.getParcelableExtra(ScanUtil.RESULT) // 获取扫码结果 ScanUtil.RESULT
            if (!TextUtils.isEmpty(hmsScan?.getOriginalValue())) {
                mBinding.tvResult.text = hmsScan?.getOriginalValue()
            }
        }
  ……
    }
}

Customized View Mode

Customized View 支持开发者自定义扫码界面,扫码过程和相机控制将由 Scan Kit 完成。这种模式更适合对扫码界面有定制化要求的场景,借助 Scan Kit 的扫码能力完成 UI 定制化需求。主要步骤:

  • 自定义扫码页面元素;
  • 通过 Customized View 实现相机扫码功能【详细步骤请看注释】。
class CustomizedModeActivity : AppCompatActivity() {
    companion object {
        const val SCAN_RESULT = "scanResult"
        private const val SCAN_FRAME_SIZE = 300
    }

    private var remoteView: RemoteView? = null
    var mScreenWidth = 0
    var mScreenHeight = 0

    private val mBinding by lazy {
        ActivityCustomizedModeBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)

        // 1. 获取屏幕密度计算 viewfinder 的矩形
        val dm = resources.displayMetrics
        // 2. 获取屏幕尺寸
        val density = dm.density
        mScreenWidth = dm.widthPixels
        mScreenHeight = dm.heightPixels
        val scanFrameSize = (SCAN_FRAME_SIZE * density)
        // 3. 计算 viewinder 的矩形,放在布局中央
        val rect = Rect()
        apply {
            rect.left = (mScreenWidth / 2 - scanFrameSize / 2).toInt()
            rect.right = (mScreenWidth / 2 + scanFrameSize / 2).toInt()
            rect.top = (mScreenHeight / 2 - scanFrameSize / 2).toInt()
            rect.bottom = (mScreenHeight / 2 + scanFrameSize / 2).toInt()
        }
        // 4. 初始化RemoteView, 并且设置回调监听,这里可能设置扫码选项
        remoteView = RemoteView.Builder().setContext(this).setBoundingBox(rect)
            .setFormat(HmsScan.ALL_SCAN_TYPE).build()
        remoteView?.onCreate(savedInstanceState)
        remoteView?.setOnResultCallback { result ->
            if (result != null && result.isNotEmpty() && result[0] != null && !TextUtils.isEmpty(
                    result[0].getOriginalValue()
                )
            ) {
                val intent = Intent()
                intent.apply {
                    putExtra(SCAN_RESULT, result[0])
                }
                setResult(Activity.RESULT_OK, intent)
                this.finish()
            }
        }
        // 5. 添加 RemoteView 至布局.
        val params = FrameLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.MATCH_PARENT
        )
        mBinding.rim1.addView(remoteView, params)
    }

    // 6. 管理RemoteView 的生命周期
    override fun onStart() {
        super.onStart()
        remoteView?.onStart()
    }

    override fun onResume() {
        super.onResume()
        remoteView?.onResume()
    }

    override fun onPause() {
        super.onPause()
        remoteView?.onPause()
    }

    override fun onDestroy() {
        super.onDestroy()
        remoteView?.onDestroy()
    }

    override fun onStop() {
        super.onStop()
        remoteView?.onStop()
    }
}

Bitmap Mode

顾名思义,此模式下的识别对象是 Bitmap ,不管这个 Bitmap 从何而来。前面两种模式,我们只需要关系 UI 界面和扫码选项,而 Bitmap 模式更加灵活,你可以通过相机取帧的方式生成 Bitmap 进行检测,也可以通过图片文件转换成 Bitmap 进行检测等等,能 Bitmap 就能 检测,适用于图片识码等场景。主要步骤:

  • 将数据转成 Bitmap (相机数据、文件等);
  • 初始化HmsScanAnalyzerOptions,设置支持识别的码制式和设置 Bitmap 模式为图片扫码模式;
  • 调用 ScanUtil的静态方法decodeWithBitmap发起扫码请求并获取扫码结果对象HmsScan
fun startBitmapMode(view: View) {
    EasyPhotos.createAlbum(
        thisfalsefalse,
        GlideEngine.getInstance()
    )
        .setFileProviderAuthority(BuildConfig.APPLICATION_ID)
        .setCount(1)
        .start(object : SelectCallback() {
            override fun onResult(photos: ArrayList<Photo>?, isOriginal: Boolean) {
                photos?.let {
                    val path = photos.first().path
                    if (TextUtils.isEmpty(path)) {
                        return
                    }
                    // 1. 转换为 Bitmap
                    val bitmap = ScanUtil.compressBitmap(this@MainActivity, path)
                    // 2. 调用 decodeWithBitmap 方法识别 Bitmap.
                    val result = ScanUtil.decodeWithBitmap(
                        this@MainActivity,
                        bitmap,
                        HmsScanAnalyzerOptions.Creator().setHmsScanTypes(0).setPhotoMode(false)
                            .create()
                    )
                    // 3. 显示识别结果
                    if (result != null && result.isNotEmpty()) {
                        if (!TextUtils.isEmpty(result[0].getOriginalValue())) {
                            mBinding.tvResult.text = result[0].getOriginalValue()
                        }
                    }
                }
            }

            override fun onCancel() {
                Toast.makeText(
                    this@MainActivity,
                    "图片选取失败",
                    Toast.LENGTH_SHORT
                )
                    .show()
            }

        })
}

MultiProcessor Mode

相机扫码、导入图片扫码,支持同时检测多个码,支持同步异步两种方式。这种模式是 Bitmap Mode 的延续,该模式下使用的 MLFrame需要使用 Bitmap 生成,当然我们也有其他方式生成MLFrame,但是 Bitmap 居多。主要步骤:

  • 将数据转成 Bitmap (相机数据、文件等);

  • 初始化HmsScanAnalyzerOptions并设置支持识别的码制式;

  • 初始化HmsScanAnalyzer对象;

  • 将图像信息转换为MLFrameMLFrame为ML Kit封装的图像信息类;

  • 调用HmsScanAnalyzer对象的analyseFrame扫码方法发起扫码请求并获取扫码结果。

 fun startMultiProcessorMode(view: View) {
        EasyPhotos.createAlbum(
            thisfalsefalse,
            GlideEngine.getInstance()
        )
            .setFileProviderAuthority(BuildConfig.APPLICATION_ID)
            .setCount(1)
            .start(object : SelectCallback() {
                override fun onResult(photos: ArrayList<Photo>?, isOriginal: Boolean) {
                    photos?.let {
                        val path = photos.first().path
                        if (TextUtils.isEmpty(path)) {
                            return
                        }
                        // 1. 转换为 Bitmap
                        val bitmap = ScanUtil.compressBitmap(this@MainActivity, path)
       // 2. 配置可选项
                        val options =
                            HmsScanAnalyzerOptions.Creator().setHmsScanTypes(HmsScan.ALL_SCAN_TYPE)
                                .create()
                        // 3. 初始化 HmsScanAnalyzer 对象
                        val scanAnalyzer = HmsScanAnalyzer(options)
                        // 4. 构建 MLFrame
                        val image = MLFrame.fromBitmap(bitmap)
                        // 5. 扫码识别
                        /*  同步模式                      
                            val result: SparseArray<HmsScan> = scanAnalyzer.analyseFrame(image)
                            Log.d(TAG, result.toString())
                        */

                        // 异步模式
                        scanAnalyzer.analyzInAsyn(image).addOnSuccessListener {
                            if (it != null && it.size > 0) {
                                var resultStr = ""
                                it.forEach { value ->
                                    resultStr = resultStr.plus(value.originalValue).plus("\n")
                                }
                                mBinding.tvResult.text = resultStr
                            }
                        }.addOnFailureListener {
                            it?.printStackTrace()
                            Log.d(TAG, it.message ?: "")
                        }
                    }
                }

                override fun onCancel() {
                    Toast.makeText(
                        this@MainActivity,
                        "图片选取失败",
                        Toast.LENGTH_SHORT
                    )
                        .show()
                }

            })
    }

效果



源码地址:

https://github.com/onlyloveyd/ScanKitSample


---END---


推荐阅读:
Android转场动画的前世今生
Jetpack太香了,系统App也想用,怎么办?
Android注解三大框架Dagger、Hilt 和 Koin 有何不同?
假装内卷,才是互联网人的噩梦
Jetpack Compose 架构如何选?MVP 、 MVVM 还是 MVI ?
一道面试题:Glide 做了哪些优化?
性能优化之合并多个FileProvider
看片神器人人视频APP突遭下架,官方回应:一切都会变好的~
Kotlin 2021 roadmap

更文不易,点个“在看”支持一下👇

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存