KC Blog

【Android】Using Google MLKit & Android X Camera to Implement a Fast QR Code Scanner on Android

6 min read
AndroidDev#Android
Sample Code in This Post

If you want to see how the code is written directly, you can: Go to Code Analysis

In this project, Kotlin, Google's MLKit, and the native AndroidX camera are used to implement the QR scanner.

Implementation Effect
Introduction
In the past, we used some third-party libraries to create QR code scanners

such as zxing, zbar, etc.

Recently, in a project, QA feedback indicated that the current QR code scanner's accuracy was not very high.

So I looked into it and tested it, and finally understood why the accuracy was not very high.

In fact, it was because:

The project used the old android.hardware.Camera with zbar to parse QR codes

and scanned from a side angle or from a distance.

This made the QR code scanner feel slower compared to iOS.

So I decided to try implementing it using Google's MLKit.

What's Different About MLKit?
- This API requires `Android API 21` or above. - The API provides two versions

One can dynamically download the module from gms

One uses the module that comes with the library, as shown:

  • I referred to examples and used the new native camera library androidx.camera with ML Kit.

    Compared to the original project using zbar with android.hardware.Camera:

    ↪ Both camera libraries are supported from Android API 21 and above.

    ↪ The version using zbar with android.hardware.Camera had three workarounds.

    I suspect the original project or previous examples used this approach to solve camera initialization issues in the activity.

    So they wrote it like this:

    ↪ After looking into it, android.hardware.Camera is not thread-safe and has been @Deprecated.

    ↪ On the other hand, the new androidx.camera.core.Camera comes with lifecycle management to handle camera lifecycle issues.

    This can avoid some strange lifecycle problems.

✅ So I speculate that switching to the new androidx.camera.core.Camera can more effectively avoid lifecycle issues (because you can control it yourself).

  • The new androidx.camera.core.Camera comes with lifecycle management to handle camera lifecycle issues, avoiding some strange lifecycle problems.

  • The scanner decoding uses MLKit's BarcodeScannerOptions.

It enhances the types of barcodes that can be parsed.

You can refer to this link for the supported formats. Below is the scanner decoding code:

Implementing MLKit and Actual Testing
- Tested on `LG K520 Android 11`
  • The overall runtime of the camera on LG K520 Android 11 did not differ much

In practice, from init camera to parsing QR code takes about 1 second

However, it can avoid the camera crash risk caused by lifecycle

  • The actual test shows a slightly faster response speed compared to the original solution (not noticeable on better phones, minimal difference)

For example, zbar + android.hardware.Camera will completely focus before starting to decode the QR code

mlkit + androidx.camera.core.Camera can start decoding the QR code halfway through focusing

  • The image on the left uses zbar, and the right uses ML kit (this gif is slowed down to 0.25x)
  • androidx.camera.core.Camera can handle lifecycle-related issues

Slowing down the new solution to 0.25x makes it feel like: waiting for a while before displaying the camera (right image)

  • However, the noticeable improvement is that it is easier to scan when using odd angles or when the camera is farther away
Adjusting the New Camera android.hardware.Camera in the App
- Add target resolution setting when creating ImageAnalysis `.setTargetResolution(Size(screenResolutionForCamera.x, screenResolutionForCamera.y))` - The screenResolutionForCamera is set using the screen resolution to set the camera target resolution

The difference between setting the camera target resolution or not is higher success rate of scanning from a distance

  • Additionally: the API Java doc mentions
    1. The default resolution of the camera without TargetResolution set is 640*480
    2. After setting, there will be a priority to choose the actual Resolution
    3. Here is the outline: Priority is given to resolutions close to and greater than the Target Resolution > Priority is given to resolutions close to and smaller than the Target Resolution > The ratio of the resolution will be primarily determined by the provided Size
    4. Therefore, it is speculated that due to hardware limitations, the distance that can be resolved may vary for each device
Results
- After the above adjustments and optimizations, when asking QA colleagues to help test

They reported that the accuracy of the app has indeed improved compared to the previous version

Upon further inquiry, they said the success rate of scanning from the side or from a farther distance or being detected has indeed increased Thus... a problem was solved XD

Code Development Tutorial
- Thank you all for reading the above analysis

I have placed the source code at the top of this article

If interested, you can clone it directly and modify it

  • Below I will share the main code analysis of the QR code scanner

Feel free to refer to it if interested

BaseCameraLifecycleObserver

Code Explanation

The BaseCameraLifecycleObserver class is mainly used for inheritance by others

It contains some basic camera initialization content

It also handles some lifecycle-related content

(e.g., handling the camera according to the lifecycle)

It also provides external methods to manually turn the camera on or off

cameraProviderDeferred

Worth mentioning is the use of cameraProviderDeferred here

It is used to obtain ProcessCameraProvider

Since this operation is time-consuming, asynchronous operation is used for more flexibility

(However, I have also tried directly instantiating ProcessCameraProvider

In most cases, it is not easy to encounter ANR or stuck situations

Only occasionally 1 or 2 times on older models, such as Android 5.0)

The concept here is

CompletableDeferred uses .apply

Within the apply scope, it creates ProcessCameraProvider

Use await() to wait where needed

Achieving asynchronous operations while waiting for the actual creation before proceeding

The difference between completableDeferred and regular Deferred is

It can be manually controlled to complete using complete()

Which is suitable for this example

Need to wait for the ProcessCameraProvider listener to return

To know that initialization is complete

ScannerLifecycleObserver

Code Explanation

ScannerLifecycleObserver is relatively simple

Inherits from BaseCameraLifecycleObserver

Mainly writes features that might be needed based on project requirements

Such as some extended content

Like getCameraDisplayOrientation

Can be used to adjust the supported resolution of the camera

Or getImageAnalysis to get ImageAnalysis

ImageAnalysis is mainly used in MLKit to set which barcode types can be parsed

ScannerAnalyzer

Code Explanation

ScannerAnalyzer is used to implement barcode parsing

Inherits from BaseCameraLifecycleObserver

Since we are parsing QR codes, we use FORMAT_QR_CODE