在安卓系统中使用快速响应(QR)代码进行数据加密
快速响应(QR)码是一种条形码,或者说是可扫描系列,可以存储一系列数据,包括网络链接、信用卡信息、联系信息,甚至是赠品。二维码被用来以只有机器才能理解的格式对信息进行编码。
在本教程中,我们将学习如何生成QR码,在Android应用程序中存储简单的文本格式数据。
为什么使用快速响应代码?
二维码有以下优点。
- 它们不会像传统的条形码那样存在安全风险。
- 二维码可容纳大量的数据;超过2500个字符。
- 它们最大限度地减少了处理错误,因为它们很难被篡改。
- 与传统的条形码不同,二维码以二维方式存储数据(垂直和水平方向)。
前提条件
要跟上这个教程,你需要熟悉。
- 使用[Kotlin编程语言]创建Android应用程序。
- Android中的命令式范式(XML)。
- [Android中的ViewBinding]。
- [安卓权限]。
创建一个安卓项目
运行Android Studio并建立一个新的Empty Activity 项目,名为QR Code 。

设置该项目
在我们开始编码之前,首先让我们通过以下方式丰富我们的项目。
添加摄像头权限
Android操作系统禁止未经授权使用摄像头。由于我们在扫描二维码时需要它,所以在清单文件中添加以下权限。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.demo.qrcode">
<uses-permission android:name="android.permission.CAMERA" />
</manifest>
添加所需的依赖性和存储库
ZXing库
ZXing (是 "Zebra Crossing "的缩写)是一个开源的、多格式的一维/二维条码图像处理工具,用Java构建,与其他编程语言兼容。
implementation 'com.google.zxing:core:3.4.0'
implementation 'com.journeyapps:zxing-android-embedded:4.1.0'
布迪耶夫代码扫描器
这个库提供了一个易于使用的API,它有一个现成的带有额外控制按钮的代码扫描仪。
implementation 'com.github.yuriy-budiyev:code-scanner:2.1.0'
JitPack资源库
由于jcenter() 的废弃,Budiyev代码扫描器的开发者建议使用jitpack.io 存储库,因为他们计划迁移到mavenCentral() 。
// in the top-level build.gradle file
repositories {
...
maven { url 'https://jitpack.io' }
}
视图绑定
以更安全的方式轻松地访问UI中的视图。
android{
...
buildFeatures{
viewBinding true
}
}
系统要求
ZXing创建者建议使用Android API-24作为最小的SDK版本。否则,该应用将无法编译。
android {
...
defaultConfig {
...
minSdk 24
}
}
数据编码/加密
为了说明二维码解码的工作原理,我们要创建一个名为GenerateQR 的活动和其对应的XML布局文件,名为activity_generate_qr 。你也可以重新命名默认的MainActivity.kt 和它的XML文件。
activity_generate_qr.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="50dp"
app:counterEnabled="true"
app:counterMaxLength="50"
app:helperText="Enter text to be encoded"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type something"
tools:text="Hello Android" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btnGenerateQRCode"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="16dp"
android:text="Generate"
app:layout_constraintEnd_toEndOf="@+id/textInputLayout"
app:layout_constraintStart_toStartOf="@+id/textInputLayout"
app:layout_constraintTop_toBottomOf="@+id/textInputLayout" />
<ImageView
android:id="@+id/ivOutput"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginVertical="16dp"
android:adjustViewBounds="true"
app:layout_constraintBottom_toTopOf="@+id/btnScan"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnGenerateQRCode"
tools:src="@tools:sample/avatars" />
<Button
android:id="@+id/btnScan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Scan"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
在上面的代码中,我们已经创建了一个文本输入字段,两个按钮和一个ImageView,一旦生成QR码就会显示出来。
预览。

GenerateQR.kt
这就是应用生成QR码的逻辑的地方。这个类的目的是将输入(文本)编码成BitMap并在ImageView中显示。
使用ViewBinding设置点击监听器
private var _genQRBinding: ActivityGenerateQrBinding? = null
private val genQRBinding get() = _genQRBinding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_genQRBinding = ActivityGenerateQrBinding.inflate(layoutInflater)
setContentView(genQRBinding.root)
// Handle button clicks
genQRBinding.btnGenerateQRCode.setOnClickListener {
val input = genQRBinding.inputText.text!!.toString().trim()
genQRBinding.ivOutput.setImageBitmap(generateQRCode(input))
}
genQRBinding.btnScan.setOnClickListener {
// This activity will be created later
startActivity(Intent(this, ScanQR::class.java))
}
}
生成二维码
private fun generateQRCode(inputText: String?): Bitmap? {
val writer = MultiFormatWriter()
var bitmap: Bitmap? = null
if (!inputText.isNullOrEmpty()) {
try {
// init bit matrix
val matrix = writer.encode(inputText, BarcodeFormat.QR_CODE, 350, 350)
// init barcode encoder
val encoder = BarcodeEncoder()
// generate bitmap
bitmap = encoder.createBitmap(matrix)
} catch (e: WriterException) {
// log error here
Log.e("GENERATE QR CODE ACTIVITY", e.toString())
}
} else {
genQRBinding.textInputLayout.error = "* required"
}
return bitmap
}
解释一下。
在上面的代码中,我们捕获了用户输入的文本,并将其编码为一个正方形的BitMap。
如果输入不是空的,该函数返回BitMap,否则返回null,并在textField中显示错误信息。
如果在编码过程中发生错误,异常会显示在logcat上,以方便调试。
数据矩阵 - 矩阵是一个由黑色和白色单元格组成的二维代码,以正方形排列(也有矩形的)。列和行的数量与代码中存储的数据量成比例增长。
使用ZXing提供的编码器将矩阵转换为BitMap。
运行该应用程序后,你应该看到与此类似的东西。

这就是关于从文本(字符串)生成QR码的全部内容。
数据解码/解密
接下来,让我们看看如何扫描一个已经存在的QR码。这是与编码相反的。
注意:你可以扫描任何QR码,包括由其他系统生成的QR码。解码算法将始终产生相同的结果。
再创建一个名为ScanQR 的活动,以及其相应的名为activity_scan_qr 的XML布局文件。
activity_scan_qr.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.budiyev.android.codescanner.CodeScannerView
android:id="@+id/codeScannerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:autoFocusButtonColor="@android:color/white"
app:autoFocusButtonVisible="true"
app:flashButtonColor="@android:color/white"
app:flashButtonVisible="true"
app:frameAspectRatioHeight="1"
app:frameAspectRatioWidth="1"
app:frameColor="@android:color/white"
app:frameCornersRadius="8dp"
app:frameCornersSize="60dp"
app:frameSize="0.75"
app:frameThickness="2dp"
app:maskColor="#75000000" />
</androidx.constraintlayout.widget.ConstraintLayout>
上面的代码生成了一个代码扫描器,用于在与框架正确对齐时捕获图片。这个代码扫描器提供了两个动作按钮;聚焦按钮和手电筒按钮。
这两个可以通过改变属性中各自的值或使用扫描仪的属性来启用。
预览。

ScanQR.kt文件
与生成QR码时不同,扫描涉及以下讨论的几个阶段。
i) 设置ViewBinding和CodeScanner
膨胀UI并定义代码扫描器变量。
private lateinit var scanBinding: ActivityScanQrBinding
private var codeScanner: CodeScanner? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
scanBinding = ActivityScanQrBinding.inflate(layoutInflater)
setContentView(scanBinding.root)
checkPermissions()
}
ii) 检查权限
如前所述,我们需要向系统申请使用摄像头的权限。
private fun checkPermissions() {
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) ==
PackageManager.PERMISSION_GRANTED
) {
initiateScan()
} else {
// request for Camera permission
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), 1)
}
}
iii) 处理权限回调
这是在权限对话框被驳回后立即调用的。如果用户授予所请求的权限,扫描器就被激活,否则,它就保持不活动。
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show()
initiateScan()
} else {
Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show()
}
}
}
iv) 初始化一个扫描过程
指派扫描器
这涉及到通过传递上下文和代码扫描器视图来实例化CodeScanner 类。这个对象在扫描过程中一直被使用,每个进程都有一个新的对象,除非应用程序没有被完全杀死。
val scannerView = scanBinding.codeScannerView
codeScanner = CodeScanner(this, scannerView)
默认的扫描器值
以下是代码扫描器采取的默认值。你可以使用点(.)符号来查看可用的选项/值。
codeScanner?.camera = CodeScanner.CAMERA_BACK
codeScanner?.formats = CodeScanner.ALL_FORMATS
codeScanner?.apply {
isAutoFocusEnabled = true
isFlashEnabled = false
autoFocusMode = AutoFocusMode.SAFE
scanMode = ScanMode.SINGLE
}
解码回调(结果)
以下代码在捕获一个代码后被调用。注意,要成功捕获一个QR码,它必须在扫描器的火焰中对齐。
💡 提示:方向并不重要 😎
codeScanner!!.decodeCallback = DecodeCallback {
runOnUiThread {
Snackbar.make(scannerView, "Scan result: ${it.text}", 5000).show()
}
}
结果显示在一个持续5秒钟的Snackbar中。同样,你也可以用你想要的方式处理结果。
处理解密错误
codeScanner?.errorCallback = ErrorCallback {
runOnUiThread {
Toast.makeText(
this, "Camera initialization error: ${it.message}",
Toast.LENGTH_LONG
).show()
}
}
只要在解码时发生错误,就会运行这个程序。在大多数情况下,如果QR码无法解码,扫描仪往往会忽略它。这样一来,你就很少得到异常。
当扫描器被敲击时重新启动扫描
scannerView.setOnClickListener {
codeScanner!!.startPreview()
}
将上面的部分合并为一个函数
private fun initiateScan() {
val scannerView = scanBinding.codeScannerView
codeScanner = CodeScanner(this, scannerView)
// The default values
codeScanner?.camera = CodeScanner.CAMERA_BACK
codeScanner?.formats = CodeScanner.ALL_FORMATS
codeScanner?.apply {
isAutoFocusEnabled = true
isFlashEnabled = false
autoFocusMode = AutoFocusMode.SAFE
scanMode = ScanMode.SINGLE
}
// Decode Callback (Results)
codeScanner!!.decodeCallback = DecodeCallback {
runOnUiThread {
Snackbar.make(scannerView, "Scan result: ${it.text}", 5000).show()
}
}
// Error CallBack
codeScanner?.errorCallback = ErrorCallback {
runOnUiThread {
Toast.makeText(
this, "Camera initialization error: ${it.message}",
Toast.LENGTH_LONG
).show()
}
}
scannerView.setOnClickListener {
codeScanner!!.startPreview()
}
}
处理Android生命周期的回调
// When the app resumes
override fun onResume() {
super.onResume()
codeScanner?.startPreview()
}
// just before the app is paused
override fun onPause() {
codeScanner?.releaseResources()
super.onPause()
}
运行应用程序
运行应用程序后,导航到ScanQR 活动,你应该期望看到一个与下面类似的二维码扫描仪。


结语
在本教程中,我们已经介绍了如何在Android应用中使用快速响应(QR)码进行数据编码的基本原理。我们还学习了如何使用CodeScanner 来扫描/解码QR码。
在本教程中获得的知识可以应用于其他适用于QR码的场景。