Skip to content

Conversation

navaronbracke
Copy link
Collaborator

@navaronbracke navaronbracke commented Aug 8, 2024

This PR adds the following changes:

  • Fixes a possible leak on Android relating to keeping an orphaned barcode scanner around (the one that is created when the scanner variable is declared, but not yet used)
  • Fixed another possible leak where not all camera observers were released
  • Reflows some part of the Android code to use early returns
  • Updates analyzeImage() to take configurable barcode scanner options & use a short lived scanner
  • Adds a way to mock the barcode scanner for testing (will be used for the NaN error fix)

For future readers, iOS does not have an equivalent release method for the scanner in the MLKit library.
It's possible that uses an autorelease pool or destructor.

Fixes #988
Part of #1069
Part of #1134

import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.core.TorchState
import androidx.camera.core.resolutionselector.AspectRatioStrategy
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import

private val mobileScannerCallback: MobileScannerCallback,
private val mobileScannerErrorCallback: MobileScannerErrorCallback
private val mobileScannerErrorCallback: MobileScannerErrorCallback,
private val barcodeScannerFactory: (options: BarcodeScannerOptions?) -> BarcodeScanner = ::defaultBarcodeScannerFactory,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new callback is mainly for tests. I'm adding a new test soon that needs this, in a follow up PR for the rounding bug #1134

private var preview: Preview? = null
private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null
private var scanner = BarcodeScanning.getClient()
private var scanner: BarcodeScanner? = null
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This client was never disposed

/**
* Create a barcode scanner from the given options.
*/
fun defaultBarcodeScannerFactory(options: BarcodeScannerOptions?) : BarcodeScanner {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default for the barcode scanner creator function


scanner.process(inputImage)
.addOnSuccessListener { barcodes ->
scanner?.let {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scanner is nullable now, so used a let block

it.process(inputImage).addOnSuccessListener { barcodes ->
if (detectionSpeed == DetectionSpeed.NO_DUPLICATES) {
val newScannedBarcodes = barcodes.mapNotNull { barcode -> barcode.rawValue }.sorted()
val newScannedBarcodes = barcodes.mapNotNull {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting of mapNotNull to use braces

return@addOnSuccessListener
}
if (newScannedBarcodes.isNotEmpty()) lastScanned = newScannedBarcodes
if (newScannedBarcodes.isNotEmpty()) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same formatting change here

barcodeMap.add(barcode.data)
}
} else {
if (scanWindow == null) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inverting the check here made more sense

val bitmap = Bitmap.createBitmap(mediaImage.width, mediaImage.height, Bitmap.Config.ARGB_8888)

val imageFormat = YuvToRgbConverter(activity.applicationContext)
if (isBarcodeInScanWindow(scanWindow!!, barcode, imageProxy)) {
Copy link
Collaborator Author

@navaronbracke navaronbracke Aug 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also invert the check here, which drops a redundant continue statement

}

imageFormat.yuvToRgb(mediaImage, bitmap)
if (barcodeMap.isEmpty()) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Early return 2x

@navaronbracke navaronbracke force-pushed the fix_barcode_scanner_leak branch from 39ec5a8 to 6b06c5d Compare August 8, 2024 11:07
val owner = activity as LifecycleOwner
camera?.cameraInfo?.torchState?.removeObservers(owner)
// Release the camera observers first.
camera?.cameraInfo?.let {
Copy link
Collaborator Author

@navaronbracke navaronbracke Aug 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a bug, we didn't remove all observers. removeObservers does nothing if the owner is not a listener, so calling this is safe for all three LiveData instances.

But we are listening to torch & zoom

cameraProvider?.unbindAll()
textureEntry?.release()

cameraProvider = null
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following is just reflowing code so that the relevant things are together.

cameraProvider = null

// Release the scanner.
scanner?.close()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is new, we release the scanner when the users stops the camera. No need to keep it around when the preview / camera is gone.

// Release the scanner.
scanner?.close()
scanner = null
lastScanned = null
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting lastScanned to null is to keep things consistent.

* Analyze a single image.
*/
fun analyzeImage(image: Uri, onSuccess: AnalyzerSuccessCallback, onError: AnalyzerErrorCallback) {
fun analyzeImage(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Analyze image now takes configurable options and uses a short lived scanner.

Using the short lived scanner is part of the fix for the retain cycle thing and allowing options is part of the fix for analyze image allowing all formats.

fun dispose(activityPluginBinding: ActivityPluginBinding) {
methodChannel?.setMethodCallHandler(null)
methodChannel = null
mobileScanner?.dispose()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the user calls MobileScannerController.dispose() we should also dispose the underlying camera / scanner. There is no guarantee that stop() is called before dispose().

We might get only dispose() when the user leaves a scanning page for example.

@navaronbracke navaronbracke changed the title Fix barcode scanner leak fix: fix barcode scanner leak on Android Aug 8, 2024
@@ -1,3 +1,6 @@
## NEXT
* Fixed a leak of the barcode scanner on Android.
Copy link
Collaborator Author

@navaronbracke navaronbracke Aug 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm planning on fixing the NaN error & analyze image format issue in the same release, so I'm using the NEXT tag here for a moment.

We should probably also look into doing the Impeller migration for this release.

@navaronbracke navaronbracke marked this pull request as ready for review August 8, 2024 11:23
@navaronbracke navaronbracke merged commit 35ce2fa into juliansteenbakker:master Aug 8, 2024
5 of 6 checks passed
@navaronbracke navaronbracke deleted the fix_barcode_scanner_leak branch August 8, 2024 11:34
joaopedro735 pushed a commit to joaopedro735/mobile_scanner that referenced this pull request Aug 22, 2024
…ode_scanner_leak

fix: fix barcode scanner leak on Android
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

StrictMode DiskReadViolation blocking main thread
1 participant