Bug Report
Plugin(s)
@capacitor/camera 8.2.4 (also 8.2.2), @capacitor/core 8.5.2, Android targetSdk 36
Capacitor Version
$ npx cap doctor
💊 Capacitor Doctor 💊
Latest Dependencies:
@capacitor/cli: 8.5.2
@capacitor/core: 8.5.2
@capacitor/android: 8.5.2
@capacitor/ios: 8.5.2
Installed Dependencies:
@capacitor/cli: 8.5.2
@capacitor/android: 8.5.2
@capacitor/core: 8.5.2
@capacitor/ios: 8.5.2
[success] Android looking great! 👌
Platform(s)
Android
Current Behavior
IonCameraFlow.kt handleMediaResult calls BitmapFactory.decodeFile(mediaResult.uri) (line ~726, and again ~806) with no BitmapFactory.Options. On a 12 MP photo that allocates roughly 48 MB as ARGB_8888. LegacyCameraFlow.java does the same with decodeStream.
Expected Behavior
The decode should not allocate pixel memory it never uses.
In both places the decoded Bitmap is used only as a null check — "is this file an
image?". The very next line, ImageUtils.getExifData(context, bitmap, uri), reads the EXIF
from the uri and ignores the bitmap argument entirely. So a bounds-only decode
(inJustDecodeBounds = true) answers the same question for a few bytes instead of ~48 MB.
Where a bitmap really is needed later, BitmapFactory.Options.inSampleSize should be
computed from the requested width/height before the full decode — which the library
already knows how to do: IONCAMRImageEditorView.decodeSampledBitmapFromUri does exactly
that. These call sites bypass it.
Code Reproduction
No special app is needed — any Capacitor app with the plugin reproduces it:
npm i @capacitor/camera@8.2.4 && npx cap sync android
- Call
Camera.pickImages({ quality: 90, width: 2400, height: 2400 }) and choose three
12 MP photos (4000×3000).
- Watch the heap in Android Studio's Memory Profiler, or
adb shell dumpsys meminfo <package> | grep -i "Native Heap" before and during the pick.
Each picked photo shows a transient allocation of roughly width × height × 4 bytes —
about 48 MB per 12 MP image — before any resizing happens. Passing a smaller width/
height does not prevent it: the full decode happens first.
A breakpoint on IonCameraFlow.handleMediaResult shows the bitmap local going unused
after the null check.
Other Technical Details
@capacitor/camera 8.2.4 (and 8.2.2 — I unpacked both npm tarballs and compared; the
decode is identical)
io.ionic.libs:ioncamera-android 1.0.2, the newest on Maven Central, which the plugin
pins
- Android targetSdk 36, AGP 8.13.0, Gradle 8.14.3
- Affected lines:
IonCameraFlow.kt ~726 (handleMediaResult) and ~806 (the multi-pick
path); LegacyCameraFlow.java uses BitmapFactory.decodeStream without inSampleSize
in processPickedImage / processPickedImages
Additional Context
Google Play reports this. The Play Console flags it as a bitmap-downsampling
recommendation with the obfuscated symbols w4.k.c, w4.k.l, w4.m.d, and names these
entry points in the deprecated-API section:
com.capacitorjs.plugins.camera.LegacyCameraFlow.processPickedImage / processPickedImages
io.ionic.libs.ioncameralib.view.IONCAMRImageEditorView.decodeSampledBitmapFromUri / loadBitmapSafely
io.ionic.libs.ioncameralib.helper.IONCAMRImageHelper.getBitmapForInputStream / decodeFile
io.ionic.libs.ioncameralib.helper.IONCAMRMediaHelper.getImageResolution
This has a deadline. Google's August 2026 app-quality post makes bitmap memory a
bad-behaviour threshold enforced from February 2027, alongside anonymous RSS + swap.
Apps over the threshold can lose visibility.
Why it bites us. Our app scans multi-page recipes, so a session decodes one full-size
bitmap per page. On a low-RAM device the WebView renderer is killed mid-scan.
Workaround we ship, via patch-package on IonCameraFlow.kt — happy to send this as a PR:
// was: val bitmap = BitmapFactory.decodeFile(mediaResult.uri)
// if (bitmap == null) { sendError(IONCAMRError.PROCESS_IMAGE_ERROR); return }
val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeFile(mediaResult.uri, bounds)
if (bounds.outWidth <= 0 || bounds.outHeight <= 0) {
sendError(IONCAMRError.PROCESS_IMAGE_ERROR)
return
}
val exif = ImageUtils.getExifData(context, null, uri)
Verified on an Android emulator: camera capture, gallery pick and a three-page recipe scan
of 4000×3000 JPEGs all behave identically, with no error and no crash.
Note ioncamera-android is shipped as an AAR, so the IONCAMR* call sites above cannot be
patched this way — only an upstream fix reaches them.
Bug Report
Plugin(s)
@capacitor/camera8.2.4 (also 8.2.2),@capacitor/core8.5.2, Android targetSdk 36Capacitor Version
Platform(s)
Android
Current Behavior
IonCameraFlow.kthandleMediaResultcallsBitmapFactory.decodeFile(mediaResult.uri)(line ~726, and again ~806) with noBitmapFactory.Options. On a 12 MP photo that allocates roughly 48 MB as ARGB_8888.LegacyCameraFlow.javadoes the same withdecodeStream.Expected Behavior
The decode should not allocate pixel memory it never uses.
In both places the decoded
Bitmapis used only as a null check — "is this file animage?". The very next line,
ImageUtils.getExifData(context, bitmap, uri), reads the EXIFfrom the
uriand ignores the bitmap argument entirely. So a bounds-only decode(
inJustDecodeBounds = true) answers the same question for a few bytes instead of ~48 MB.Where a bitmap really is needed later,
BitmapFactory.Options.inSampleSizeshould becomputed from the requested
width/heightbefore the full decode — which the libraryalready knows how to do:
IONCAMRImageEditorView.decodeSampledBitmapFromUridoes exactlythat. These call sites bypass it.
Code Reproduction
No special app is needed — any Capacitor app with the plugin reproduces it:
npm i @capacitor/camera@8.2.4 && npx cap sync androidCamera.pickImages({ quality: 90, width: 2400, height: 2400 })and choose three12 MP photos (4000×3000).
adb shell dumpsys meminfo <package> | grep -i "Native Heap"before and during the pick.Each picked photo shows a transient allocation of roughly
width × height × 4bytes —about 48 MB per 12 MP image — before any resizing happens. Passing a smaller
width/heightdoes not prevent it: the full decode happens first.A breakpoint on
IonCameraFlow.handleMediaResultshows thebitmaplocal going unusedafter the null check.
Other Technical Details
@capacitor/camera8.2.4 (and 8.2.2 — I unpacked both npm tarballs and compared; thedecode is identical)
io.ionic.libs:ioncamera-android1.0.2, the newest on Maven Central, which the pluginpins
IonCameraFlow.kt~726 (handleMediaResult) and ~806 (the multi-pickpath);
LegacyCameraFlow.javausesBitmapFactory.decodeStreamwithoutinSampleSizein
processPickedImage/processPickedImagesAdditional Context
Google Play reports this. The Play Console flags it as a bitmap-downsampling
recommendation with the obfuscated symbols
w4.k.c,w4.k.l,w4.m.d, and names theseentry points in the deprecated-API section:
com.capacitorjs.plugins.camera.LegacyCameraFlow.processPickedImage/processPickedImagesio.ionic.libs.ioncameralib.view.IONCAMRImageEditorView.decodeSampledBitmapFromUri/loadBitmapSafelyio.ionic.libs.ioncameralib.helper.IONCAMRImageHelper.getBitmapForInputStream/decodeFileio.ionic.libs.ioncameralib.helper.IONCAMRMediaHelper.getImageResolutionThis has a deadline. Google's August 2026 app-quality post makes bitmap memory a
bad-behaviour threshold enforced from February 2027, alongside anonymous RSS + swap.
Apps over the threshold can lose visibility.
Why it bites us. Our app scans multi-page recipes, so a session decodes one full-size
bitmap per page. On a low-RAM device the WebView renderer is killed mid-scan.
Workaround we ship, via patch-package on
IonCameraFlow.kt— happy to send this as a PR: