Самый быстрый способ создания сканера QR-кодов для Android
Узнайте о самом быстром способе создания сканера QR-кодов для Android. Пошаговый предварительный просмотр камеры реализации, а также как интегрировать SDK для сканирования QR-кодов.
Приложение для сканирования QR-кодов в основном состоит из двух частей: предварительного просмотра с помощью камеры и сканирования QR-кода. Существует множество приложений для сканирования QR-кодов для Android, которые можно скачать в Google Play Store. Однако интереснее создать сканер QR-кода самостоятельно, чем использовать уже существующий. Цель этой статьи — рассказать о самом быстром способе создания сканера QR-кодов для Android. Вы увидите, как шаг за шагом реализовать предварительный просмотр камеры, а также как интегрировать SDK для сканирования QR-кодов.
Предварительные условия
Для реализации сканера QR-кодов необходимы следующие библиотеки Android. Вы можете свободно заменить их своими собственными библиотеками.
- Camera Preview SDK
- CameraX
Поскольку Android Camera2 API чрезвычайно сложен для новичков, Google выпустил CameraX, чтобы упростить разработку приложений для камер. Учебник codelab является хорошей отправной точкой для изучения CameraX.
Установка
В AndroidManifest.xml, добавить разрешение на камеру:
<uses-feature android:name="android.hardware.camera.any" /> <uses-permission android:name="android.permission.CAMERA" />
В app/build.gradle, добавьте зависимость:
dependencies {
...
def camerax_version = "1.0.1"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:1.0.0-alpha27"
}
Dynamsoft Camera Enhancer
Similar to CameraX, [Dynamsoft Camera Enhancer](https://www.dynamsoft.com/camera-enhancer/docs/introduction/) is also a wrapper for Android Camera2 API. In addition to basic camera capabilities, it features frame filtering for better image quality. We use Dynamsoft Camera Enhancer to make a comparison with CameraX.
Dynamsoft Camera Enhancer
Подобно CameraX, [Dynamsoft Camera Enhancer] также является оберткой для Android Camera2 API. В дополнение к базовым возможностям камеры, в ней предусмотрена фильтрация кадров для улучшения качества изображения. Мы используем Dynamsoft Camera Enhancer для сравнения с CameraX.
Установка
В «settings.gradle» добавьте пользовательский репозиторий Maven:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
...
maven{url "https://download.dynamsoft.com/maven/dce/aar"}
}
}
В app/build.gradle, добавьте зависимость:
dependencies {
...
implementation 'com.dynamsoft:dynamsoftcameraenhancer:2.1.0@aar'
}
SDK для сканирования QR-кодов
Dynamsoft Barcode Reader:
SDK для считывания штрихкодов, поддерживающий все основные форматы линейных и двумерных штрихкодов.
Установка
В settings.gradle, добавить пользовательский репозиторий Maven:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
...
maven{url "https://download.dynamsoft.com/maven/dbr/aar"}
}
}
В app/build.gradle, добавьте зависимость:
dependencies {
...
implementation 'com.dynamsoft:dynamsoftbarcodereader:8.9.0@aar'
}
Вам также необходим лицензионный ключ для активации SDK для штрихкодов.
Создание предварительного просмотра камеры Android за 5 минут
Три шага для реализации предварительного просмотра камеры с помощью CameraX
Официальное руководство по CameraX написано на языке Kotlin. Здесь мы используем Java.
- Создайте макет пользовательского интерфейса, который содержит представление предварительного просмотра CameraX:
<?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" tools:context=".CameraXActivity"> <androidx.camera.view.PreviewView android:id="@+id/camerax_viewFinder" android:layout_width="match_parent" android:layout_height="match_parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
2. Проверьте и запросите разрешения на съемку:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camerax_main);
previewView = findViewById(R.id.camerax_viewFinder);
if (!CameraUtils.allPermissionsGranted(this)) {
CameraUtils.getRuntimePermissions(this);
} else {
startCamera();
}
}
private static String[] getRequiredPermissions(Context context) {
try {
PackageInfo info =
context.getPackageManager()
.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
String[] ps = info.requestedPermissions;
if (ps != null && ps.length > 0) {
return ps;
} else {
return new String[0];
}
} catch (Exception e) {
return new String[0];
}
}
public static boolean allPermissionsGranted(Context context) {
for (String permission : getRequiredPermissions(context)) {
if (!isPermissionGranted(context, permission)) {
return false;
}
}
return true;
}
public static void getRuntimePermissions(Activity activity) {
List<String> allNeededPermissions = new ArrayList<>();
for (String permission : getRequiredPermissions(activity)) {
if (!isPermissionGranted(activity, permission)) {
allNeededPermissions.add(permission);
}
}
if (!allNeededPermissions.isEmpty()) {
ActivityCompat.requestPermissions(
activity, allNeededPermissions.toArray(new String[0]), PERMISSION_REQUESTS);
}
}
private static boolean isPermissionGranted(Context context, String permission) {
if (ContextCompat.checkSelfPermission(context, permission)
== PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "Permission granted: " + permission);
return true;
}
Log.i(TAG, "Permission NOT granted: " + permission);
return false;
}
3. Запустите предварительный просмотр камеры:
private void startCamera() {
ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
ProcessCameraProvider.getInstance(getApplication());
cameraProviderFuture.addListener(
() -> {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
Preview.Builder builder = new Preview.Builder();
Preview previewUseCase = builder.build();
previewUseCase.setSurfaceProvider(previewView.getSurfaceProvider());
CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;
cameraProvider.unbindAll();
cameraProvider.bindToLifecycle(this, cameraSelector, previewUseCase);
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Unhandled exception", e);
}
},
ContextCompat.getMainExecutor(getApplication()));
}
Второй шаг для реализации предварительного просмотра камеры с помощью Dynamsoft Camera Enhancer
Используя Dynamsoft Camera Enhancer, вы напишите меньше кода, чем при использовании CameraX для реализации той же функциональности.
- Создайте макет пользовательского интерфейса, который содержит представление предварительного просмотра DCE:
<?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" tools:context=".CameraXActivity"> <com.dynamsoft.dce.DCECameraView android:id="@+id/dce_viewFinder" android:layout_width="match_parent" android:layout_height="match_parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
2. Запустите предварительный просмотр камеры:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dce_main);
previewView = findViewById(R.id.dce_viewFinder);
cameraEnhancer = new CameraEnhancer(this);
cameraEnhancer.setCameraView(previewView);
cameraEnhancer.addListener(this);
}
@Override
protected void onResume() {
super.onResume();
try {
cameraEnhancer.open();
} catch (CameraEnhancerException e) {
e.printStackTrace();
}
}
@Override
protected void onPause() {
super.onPause();
try {
cameraEnhancer.close();
} catch (CameraEnhancerException e) {
e.printStackTrace();
}
}
Все в одном
Мы создаем входную активность для запуска CameraX и Dynamsoft Camera Enhancer соответственно:
package com.example.qrcodescanner;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
public class EntryChoiceActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.entry_choice);
findViewById(R.id.camerax_entry_point).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(EntryChoiceActivity.this, CameraXActivity.class);
startActivity(intent);
}
});
findViewById(R.id.dce_entry_point).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(EntryChoiceActivity.this, DceActivity.class);
startActivity(intent);
}
});
}
}
Превращение камеры Android в сканер QR-кодов
Для сканирования QR-кодов нам необходимо постоянно получать кадры предварительного просмотра камеры и передавать их в детектор QR-кодов.
Как установить обратный вызов кадра камеры
При использовании CameraX мы можем использовать класс «ImageAnalysis» для получения кадров камеры:
ImageAnalysis analysisUseCase = new ImageAnalysis.Builder().build();
analysisUseCase.setAnalyzer(cameraExecutor,
imageProxy -> {
// image processing
// Must call close to keep receiving frames.
imageProxy.close();
});
cameraProvider.bindToLifecycle(this, cameraSelector, previewUseCase, analysisUseCase);
В отличие от него, Dynamsoft Camera Enhancer намного проще. Функция обратного вызова похожа на ту, что используется в Android Camera1:
public class DceActivity extends AppCompatActivity implements DCEFrameListener {
@Override
public void frameOutputCallback(DCEFrame dceFrame, long l) {
// image processing
}
}
Типы данных, возвращаемые их функциями обратного вызова, различны. Для дальнейшего использования требуется преобразование типов данных.
Декодирование QR-кода
В CameraX мы сначала преобразуем «ByteBuffer» в «byte[]», а затем вызываем метод «decodeBuffer()»:
analysisUseCase.setAnalyzer(cameraExecutor,
imageProxy -> {
TextResult[] results = null;
ByteBuffer buffer = imageProxy.getPlanes()[0].getBuffer();
int nRowStride = imageProxy.getPlanes()[0].getRowStride();
int nPixelStride = imageProxy.getPlanes()[0].getPixelStride();
int length = buffer.remaining();
byte[] bytes = new byte[length];
buffer.get(bytes);
try {
results = reader.decodeBuffer(bytes, imageProxy.getWidth(), imageProxy.getHeight(), nRowStride * nPixelStride, EnumImagePixelFormat.IPF_NV21, "");
} catch (BarcodeReaderException e) {
e.printStackTrace();
}
// Must call close to keep receiving frames.
imageProxy.close();
});
В то время как в Dynamsoft Camera Enhancer мы получаем Bitmap изDCEFrame и затем вызываем метод decodeBufferedImage()
public void frameOutputCallback(DCEFrame dceFrame, long l) {
TextResult[] results = null;
try {
results = reader.decodeBufferedImage(dceFrame.toBitmap(), "");
} catch (BarcodeReaderException e) {
e.printStackTrace();
}
}
Использование зума и фонарика для повышения качества кадра
На точность распознавания всегда влияет качество входного изображения. Если QR-код слишком мал, мы можем увеличить масштаб камеры, чтобы увеличить изображение. Если входное изображение слишком темное, мы можем включить фонарик, чтобы осветлить изображение. И CameraX, и Dynamsoft Camera Enhancer полностью поддерживают управление камерой.
Зум камеры Android
Для запуска зума мы используем жест «щипок пальцем». Таким образом, первым шагом будет создание детектора жестов и передача ему метода «onTouchEvent()»:
public class ZoomController {
public final static String TAG = "ZoomController";
private float currentFactor = 1.0f;
private float minZoomRatio = 1.0f, maxZoomRatio = 1.0f;
private ZoomStatus zoomStatus;
private ScaleGestureDetector scaleGestureDetector;
private ScaleGestureDetector.OnScaleGestureListener scaleGestureListener = new ScaleGestureDetector.OnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
Log.i(TAG, "onScale: " + detector.getScaleFactor());
currentFactor = detector.getScaleFactor() * currentFactor;
if (currentFactor < minZoomRatio) currentFactor = minZoomRatio;
if (currentFactor > maxZoomRatio) currentFactor = maxZoomRatio;
if (zoomStatus != null) {
zoomStatus.onZoomChange(currentFactor);
}
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
};
public ZoomController(Activity activity) {
scaleGestureDetector = new ScaleGestureDetector(activity, scaleGestureListener);
}
public interface ZoomStatus {
void onZoomChange(float ratio);
}
public void addListener(ZoomStatus zoomStatus) {
this.zoomStatus = zoomStatus;
}
public void initZoomRatio(float minZoomRatio, float maxZoomRatio) {
this.minZoomRatio = minZoomRatio;
this.maxZoomRatio = maxZoomRatio;
}
public boolean onTouchEvent(MotionEvent event) {
return scaleGestureDetector.onTouchEvent(event);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
zoomController.onTouchEvent(event);
return super.onTouchEvent(event);
}
При обнаружении жеста мы получаем масштабный коэффициент и используем его в качестве коэффициента масштабирования.
Установка коэффициента масштабирования камеры с помощью CameraX
if (camera != null) {
camera.getCameraControl().setZoomRatio(ratio);
}
Настройка коэффициента масштабирования камеры с помощью Dynamsoft Camera Enhancer
try {
cameraEnhancer.setZoom(ratio);
} catch (CameraEnhancerException e) {
e.printStackTrace();
}
Фонарик для камеры Android
Для автоматического включения фонарика мы отслеживаем значение освещенности, возвращаемое датчиком освещенности.
public class AutoTorchController implements SensorEventListener {
public final static String TAG = "AutoTorchController";
private SensorManager sensorManager;
private TorchStatus torchStatus;
public interface TorchStatus {
void onTorchChange(boolean status);
}
public AutoTorchController(Activity activity) {
sensorManager = (SensorManager)activity.getSystemService(SENSOR_SERVICE);
}
public void onStart() {
Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
if(lightSensor != null){
sensorManager.registerListener(
this,
lightSensor,
SensorManager.SENSOR_DELAY_NORMAL);
}
}
public void onStop() {
sensorManager.unregisterListener(this);
}
@Override
public void onSensorChanged(SensorEvent event) {
if(event.sensor.getType() == Sensor.TYPE_LIGHT){
if (event.values[0] < 20) {
if (torchStatus != null) torchStatus.onTorchChange(true);
}
else {
if (torchStatus != null) torchStatus.onTorchChange(false);
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
public void addListener(TorchStatus torchStatus) {
this.torchStatus = torchStatus;
}
}
Переключить фонарь камеры с помощью CameraX
if (camera != null) camera.getCameraControl().enableTorch(status);
Переключение вспышки камеры с помощью Dynamsoft Camera Enhancer
if (status) {
try {
cameraEnhancer.turnOnTorch();
} catch (CameraEnhancerException e) {
e.printStackTrace();
}
}
else {
try {
cameraEnhancer.turnOffTorch();
} catch (CameraEnhancerException e) {
e.printStackTrace();
}
}

















