Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 7.4.2

**Bug Fixes**

* [iOS/macOS] Fixed a use-after-free crash (`EXC_BAD_ACCESS`) when the app is terminated while the scanner is running. The texture registry was notified directly from the capture output queue, which could call into the Flutter engine while it was being destroyed on the main thread. The notification is now dispatched to the main queue, so it is serialized against engine teardown. The plugin also implements `detachFromEngineForRegistrar:` to release the camera and texture deterministically.

## 7.4.1

**Bug Fixes**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
#endif
}

public func detachFromEngine(for registrar: FlutterPluginRegistrar) {
// Release the camera deterministically when the engine detaches this plugin, so the
// capture session stops delivering frames instead of outliving the engine.
releaseCamera()
releaseTexture()
}

init(_ registry: FlutterTextureRegistry) {
self.registry = registry
super.init()
Expand Down Expand Up @@ -161,8 +168,30 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
return
}
latestBuffer = imageBuffer
registry.textureFrameAvailable(textureId)


// Notify the texture registry on the main thread.
//
// This method runs on `sampleBufferQueue`, while the Flutter engine is torn down
// synchronously on the main thread (`-[FlutterViewController appOrSceneWillTerminate]` ->
// `-[FlutterEngine destroyContext]`). Calling `textureFrameAvailable` directly from this
// queue can therefore land in the middle of the engine's `dealloc`, which crashes with
// EXC_BAD_ACCESS. `FlutterTextureRegistryRelay` holds its parent weakly, but a weak
// reference is only cleared after `dealloc` completes, so it does not guard against a call
// that arrives during teardown.
//
// Hopping to the main queue serializes this call against the teardown itself, instead of
// relying on notification or plugin-detach ordering. A block that is enqueued after the
// engine is gone finds the relay's parent already nil and becomes a no-op.
let frameTextureId: Int64 = textureId
DispatchQueue.main.async { [weak self] in
// Drop the frame if the texture was released while this block was queued.
guard let self, self.textureId != nil else {
return
}

self.registry.textureFrameAvailable(frameTextureId)
}

let currentTime = Date().timeIntervalSince1970
let eligibleForScan = currentTime > nextScanTime && !imagesCurrentlyBeingProcessed
if ((detectionSpeed == DetectionSpeed.normal || detectionSpeed == DetectionSpeed.noDuplicates) && eligibleForScan || detectionSpeed == DetectionSpeed.unrestricted) {
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: mobile_scanner
description: A universal Flutter barcode and QR code scanner using CameraX/ML Kit for Android, AVFoundation/Apple Vision for iOS & macOS, and ZXing for web.
version: 7.4.1
version: 7.4.2
repository: https://github.com/juliansteenbakker/mobile_scanner

screenshots:
Expand Down