Android 项目开发过程中,扫码场景使用最多的开源库是 ZXing ,Github 上针对 ZXing 的优化和二次封装不胜枚举,但是 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 Mode | Android/iOS | Scan Kit处理 | Scan Kit提供 | 相机扫码(可以调用Bitmap mode增加导入图片扫码功能)。 |
Customized View Mode | Android/iOS | Scan Kit处理 | 开发者自定义 | 相机扫码(可以叠加Bitmap mode增加导入图片扫码功能)。 |
Bitmap Mode | Android/iOS | 开发者应用处理 | 开发者自定义 | 相机扫码、导入图片扫码。 |
MultiProcessor Mode | Android | 开发者应用处理 | 开发者自定义 | 相机扫码、导入图片扫码,支持同时检测多个码。 |
- 对于希望快速构建强大扫码功能的开发者,建议选择 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”文件,添加对应的编译依赖。华为官方给我们提供了两种类型的依赖包:
区别比较明显,包的大小和识别能力两个方面的影响,这里为了展示 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(
this, false, false,
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
对象; -
将图像信息转换为
MLFrame
,MLFrame
为ML Kit封装的图像信息类; -
调用
HmsScanAnalyzer
对象的analyseFrame
扫码方法发起扫码请求并获取扫码结果。
fun startMultiProcessorMode(view: View) {
EasyPhotos.createAlbum(
this, false, false,
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