QR Code Scanner –适用于Android的条形码扫描仪
许多应用程序中均存在用于Android功能的QR码扫描仪或者条形码扫描仪,以读取一些有用的数据。
在本教程中,我们将讨论和实现Google Mobile Vision API中存在的Barcode API。
要了解使用Vision API进行人脸检测的实现,请参阅此处。
适用于Android的条形码扫描仪
随着Google Vision API的引入,开发人员在应用程序中实现条形码变得更加容易。
以下是Vision API支持的主要格式。
- 一维条形码:EAN-13,EAN-8,UPC-A,UPC-E,Code-39,Code-93,Code-128,ITF,Codabar
- 2D条码:QR码,数据矩阵,PDF-417,AZTEC
条形码可以扫描各种内容,包括URL,联系信息,地理位置,WIFI,驾驶执照ID。
QR码是更流行的格式,在许多应用程序中都很常见。
下面,我们将开发一个应用程序,该程序从位图图像中扫描QR Code值,并通过摄像头检测QR Code并执行相关操作。
适用于Android项目结构的QR Code扫描仪
为条形码库配置Android Studio
将以下内容添加到build.gradle文件中。
implementation 'com.google.android.gms:play-services-vision:11.8.0'
在" AndroidManifest.xml"文件应用程序标签内添加以下内容,以在应用程序中启用条形码检测。
<meta-data
android:name="com.google.android.gms.vision.DEPENDENCIES"
android:value="barcode"
来自图像的QR码扫描仪
下面给出了" activity_main.xml"布局文件的代码。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<Button
android:id="@+id/btnTakePicture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/take_barcode_picture"
<Button
android:id="@+id/btnScanBarcode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btnTakePicture"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/activity_horizontal_margin"
android:text="@string/scan_barcode"
</RelativeLayout>
MainActivity.java的代码如下。
package com.theitroad.barcodevisionapi;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
Button btnTakePicture, btnScanBarcode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews() {
btnTakePicture = findViewById(R.id.btnTakePicture);
btnScanBarcode = findViewById(R.id.btnScanBarcode);
btnTakePicture.setOnClickListener(this);
btnScanBarcode.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnTakePicture:
startActivity(new Intent(MainActivity.this, PictureBarcodeActivity.class));
break;
case R.id.btnScanBarcode:
startActivity(new Intent(MainActivity.this, ScannedBarcodeActivity.class));
break;
}
}
}
MainActivity.java包含两个按钮。
第一个启动活动,该活动在从相机捕获的位图图像中扫描QR码,并返回QR码中存在的数据(如果有)。
第二个扫描QRCode并实时检测它们。
在进入应用程序的业务逻辑之前,我们需要向AndroidManifest.xml文件添加以下权限。
<uses-feature
android:name="android.hardware.camera"
android:required="true"
<uses-permission android:name="android.permission.CAMERA"
<uses-feature android:name="android.hardware.camera.autofocus"
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
<uses-permission android:name="android.permission.INTERNET"
要共享和访问其他应用程序创建的文件,我们需要在AndroidManifest.xml文件的应用程序标签内添加以下provider标签。
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"
</provider>
这是在Android应用程序的QR码扫描仪中检索相机捕获的图像所必需的。
让我们从第一个开始,即" PictureBarcodeActivity.java"。
xml布局" activity_barcode_picture.xml"的代码如下。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<ImageView
android:id="@+id/imageView"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_centerHorizontal="true"
android:src="@mipmap/theitroad_logo"
<TextView
android:id="@+id/txtResultsHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/imageView"
android:layout_centerHorizontal="true"
android:text="Results"
android:textSize="18sp"
android:textStyle="bold"
<TextView
android:id="@+id/txtResultsBody"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/txtResultsHeader"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/activity_horizontal_margin"
android:gravity="center"
<Button
android:id="@+id/btnOpenCamera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_horizontal_margin"
android:text="@string/open_camera"
</RelativeLayout>
下面给出了" PictureCodeActivity.java"类的代码。
package com.theitroad.barcodevisionapi;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.vision.Frame;
import com.google.android.gms.vision.barcode.Barcode;
import com.google.android.gms.vision.barcode.BarcodeDetector;
import java.io.File;
import java.io.FileNotFoundException;
public class PictureBarcodeActivity extends AppCompatActivity implements View.OnClickListener {
Button btnOpenCamera;
TextView txtResultBody;
private BarcodeDetector detector;
private Uri imageUri;
private static final int REQUEST_CAMERA_PERMISSION = 200;
private static final int CAMERA_REQUEST = 101;
private static final String TAG = "API123";
private static final String SAVED_INSTANCE_URI = "uri";
private static final String SAVED_INSTANCE_RESULT = "result";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_barcode_picture);
initViews();
if (savedInstanceState != null) {
if (imageUri != null) {
imageUri = Uri.parse(savedInstanceState.getString(SAVED_INSTANCE_URI));
txtResultBody.setText(savedInstanceState.getString(SAVED_INSTANCE_RESULT));
}
}
detector = new BarcodeDetector.Builder(getApplicationContext())
.setBarcodeFormats(Barcode.DATA_MATRIX | Barcode.QR_CODE)
.build();
if (!detector.isOperational()) {
txtResultBody.setText("Detector initialisation failed");
return;
}
}
private void initViews() {
txtResultBody = findViewById(R.id.txtResultsBody);
btnOpenCamera = findViewById(R.id.btnTakePicture);
txtResultBody = findViewById(R.id.txtResultsBody);
btnOpenCamera.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnTakePicture:
ActivityCompat.requestPermissions(PictureBarcodeActivity.this, new
String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQUEST_CAMERA_PERMISSION:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
takeBarcodePicture();
} else {
Toast.makeText(getApplicationContext(), "Permission Denied!", Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CAMERA_REQUEST && resultCode == RESULT_OK) {
launchMediaScanIntent();
try {
Bitmap bitmap = decodeBitmapUri(this, imageUri);
if (detector.isOperational() && bitmap != null) {
Frame frame = new Frame.Builder().setBitmap(bitmap).build();
SparseArray<Barcode> barcodes = detector.detect(frame);
for (int index = 0; index < barcodes.size(); index++) {
Barcode code = barcodes.valueAt(index);
txtResultBody.setText(txtResultBody.getText() + "\n" + code.displayValue + "\n");
int type = barcodes.valueAt(index).valueFormat;
switch (type) {
case Barcode.CONTACT_INFO:
Log.i(TAG, code.contactInfo.title);
break;
case Barcode.EMAIL:
Log.i(TAG, code.displayValue);
break;
case Barcode.ISBN:
Log.i(TAG, code.rawValue);
break;
case Barcode.PHONE:
Log.i(TAG, code.phone.number);
break;
case Barcode.PRODUCT:
Log.i(TAG, code.rawValue);
break;
case Barcode.SMS:
Log.i(TAG, code.sms.message);
break;
case Barcode.TEXT:
Log.i(TAG, code.displayValue);
break;
case Barcode.URL:
Log.i(TAG, "url: " + code.displayValue);
break;
case Barcode.WIFI:
Log.i(TAG, code.wifi.ssid);
break;
case Barcode.GEO:
Log.i(TAG, code.geoPoint.lat + ":" + code.geoPoint.lng);
break;
case Barcode.CALENDAR_EVENT:
Log.i(TAG, code.calendarEvent.description);
break;
case Barcode.DRIVER_LICENSE:
Log.i(TAG, code.driverLicense.licenseNumber);
break;
default:
Log.i(TAG, code.rawValue);
break;
}
}
if (barcodes.size() == 0) {
txtResultBody.setText("No barcode could be detected. Please try again.");
}
} else {
txtResultBody.setText("Detector initialisation failed");
}
} catch (Exception e) {
Toast.makeText(getApplicationContext(), "Failed to load Image", Toast.LENGTH_SHORT)
.show();
Log.e(TAG, e.toString());
}
}
}
private void takeBarcodePicture() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File photo = new File(Environment.getExternalStorageDirectory(), "pic.jpg");
imageUri = FileProvider.getUriForFile(PictureBarcodeActivity.this,
BuildConfig.APPLICATION_ID + ".provider", photo);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, CAMERA_REQUEST);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
if (imageUri != null) {
outState.putString(SAVED_INSTANCE_URI, imageUri.toString());
outState.putString(SAVED_INSTANCE_RESULT, txtResultBody.getText().toString());
}
super.onSaveInstanceState(outState);
}
private void launchMediaScanIntent() {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(imageUri);
this.sendBroadcast(mediaScanIntent);
}
private Bitmap decodeBitmapUri(Context ctx, Uri uri) throws FileNotFoundException {
int targetW = 600;
int targetH = 600;
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(ctx.getContentResolver().openInputStream(uri), null, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
int scaleFactor = Math.min(photoW/targetW, photoH/targetH);
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
return BitmapFactory.decodeStream(ctx.getContentResolver()
.openInputStream(uri), null, bmOptions);
}
}
从上面的代码得出的推论很少,下面给出。
以下代码创建一个条形码检测器。
可以在setBarcodeFormats()方法中设置要扫描的格式类型。
takeBarcodePicture()函数是启动相机的位置。
要检索图像,我们使用launchMediaScanIntent(),它调用广播Intent使用图像URI搜索图像。一个Frame.Builder用于创建位图图像的框架。
在框架上,条形码检测器扫描可能的QR码。
上面的代码中的以下行从位图创建帧。
- 通过在条形码检测器上调用
detect()方法,我们创建了一个SparseArray,其中包含图像中可能存在的所有QR码。
要获取QR码的格式,请在"条形码"实例上调用" valueFormat"字段,如下所示。
要获取显示的值和原始值,将调用以下内容。
返回的相关值显示在TextView中。
对于位图中的一个条形码,其值将附加到当前的TextView中。
ScannedBarcodeActivity.java类通过摄像机扫描条形码。
我们从这里生成了两个自己的自定义QR码。
下面给出了" activity_scan_barcode.xml"的布局代码。
detector = new BarcodeDetector.Builder(getApplicationContext())
.setBarcodeFormats(Barcode.DATA_MATRIX | Barcode.QR_CODE)
.build();
相机的Android条码扫描仪
下面给出了ScannedBarcodeActivity.java的代码。
Frame frame = new Frame.Builder().setBitmap(bitmap).build();
从上面的代码得出的推论很少,下面给出。
SurfaceView非常适合显示摄像机预览图像,因为它可以快速呈现GUI。
接口" SurfaceHolder.Callback"用于接收有关表面上发生的更改的信息(在这种情况下为相机预览)。SurfaceHolder.Callback实现了三种方法:SurfaceChanged:当表面的大小或者格式改变时,调用此方法。
SurfaceCreated:首先创建表面时,将调用此方法。
SurfaceDestroyed:破坏表面时调用。
" CameraSource"与底层探测器一起管理摄像机。
这里的SurfaceView是基础检测器。
" CameraSource.start()"将打开相机并开始将预览帧发送到SurfaceView。
CameraSource是通过以下方式创建的:
- 我们已使用
setProcessor()在条形码检测器上分配了处理器。
该接口包含对方法receiveDetections()的回调,该方法从摄像机预览接收QR码并将其添加到SparseArray中。
由于在后台线程中检测到条形码,因此QR代码的值将使用Runnable在TextView中显示。
在此示例中,我们使用生成器创建了两个条形码。
一个包含URL。
第二个包含一个电子邮件地址。
点击按钮后,我们将根据检测到的QR码值启动URL或者将电子邮件发送到从QR码检测到的相关电子邮件地址。

