EEltaCrew
Home › Blog

A crash from one line of image downsampling: inSampleSize starts at 0

Published 2026-10-13Measured 2026-10-13About 3 min readelta · EltaCrew

To save memory, we changed the document thumbnails in our QR scanner app to load downsampled. In the next version, the app closed every time the document list opened. The cause was that BitmapFactory.Options.inSampleSize defaults to 0. The code we changed, the crash data, the fix, and how we checked for the same mistake elsewhere.

It started with a memory recommendation

On October 10, after looking at Play Console's memory-related recommendations, we went through our live apps and fixed places that decoded images at full size. The document list in our QR scanner app was one of them.

Each row in the document list has a small thumbnail. That thumbnail file turned out to be the original JPEG of the scanned page. To draw one small row, the app was loading the full-resolution image into memory.

The code we changed

We changed it to read the size first, then halve the image until the short side would drop below 320px.

BitmapFactory.Options bounds = new BitmapFactory.Options();
bounds.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, bounds);

BitmapFactory.Options opts = new BitmapFactory.Options();
int minDim = Math.min(bounds.outWidth, bounds.outHeight);
while (minDim / (opts.inSampleSize * 2) >= 320) opts.inSampleSize *= 2;
Bitmap bmp = BitmapFactory.decodeFile(path, opts);

The build passed, and the change shipped with other fixes in version 1.1.25 on October 11.

The app closed every time the document list opened

The next day, a new crash showed up in Crashlytics.

java.lang.ArithmeticException: divide by zero
    at ...ui.document.DocumentAdapter.lambda$onBindViewHolder$1

Over the 7 days up to 9 AM on October 13 (KST), there were 27 events from 4 users, all on 1.1.25. If there was even one document with a thumbnail, the app closed the moment the document list opened. Thumbnail decoding runs on a background thread, and nothing there caught the exception, so the whole app went down.

The cause: the default is 0

In options created with new BitmapFactory.Options(), inSampleSize is 0. The decoder treats any value of 1 or less as 1, so a 0 is harmless when you only decode. That's why you normally never notice it's 0.

The problem was using that value directly in arithmetic. The first pass of the loop computes minDim / (0 * 2), which is minDim / 0.

The example in the Android documentation starts its calculation with a local variable, int inSampleSize = 1;. Our code used the options field as the loop variable directly and left out the line that starts it at 1.

Why testing didn't catch it is also clear. It was a change where "the screen stays the same and only the internals change", so we looked only at the build passing and shipped it without actually opening a document list that had thumbnails.

The fix

BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 1;
int minDim = Math.min(bounds.outWidth, bounds.outHeight);
while (minDim > 0 && minDim / (opts.inSampleSize * 2) >= 320) opts.inSampleSize *= 2;

We submitted 1.1.26 on October 13. This time we installed the release build we were submitting on a real device, created a document from a gallery photo, and checked that the thumbnail appeared in the document list.

Is the same mistake anywhere else?

Our other apps also have downsampling code, so we searched all our live app code for inSampleSize * 2.

There was only one place with the problem.

Summary

The app in this post is QR Code Scanner & Reader (Google Play).

AndroidBitmapFactoryCrashlyticsMemoryCrashes

Related

The crashes Crashlytics didn't show: it only sees the builds you added it toThree days after adding Firebase Crashlytics to 21 apps, we found a user-facing crash in Play Console first. Crashlytics only reports from builds that include the SDK, and crashes from older versions still on users' phones pile up only in Play vitals. Results from checking all 21 apps, and the order we check in now.The day a sync deleted 8,802 stores: one renamed API field and a 100-row listIn our lottery retailer map app, a sync turned every store name into "Unknown" and wiped the 8,802 stores bundled with the app. Three causes — a renamed field in a public data API, a sync that deleted rows based on only the first 100 results, and a geocoder reading keys that do not exist — and how each was fixed.Why users quit at the TV pairing code screen: a 6-character code and a Korean keyboardAfter fixing a pairing bug in our TV remote app, the share of users who cancelled on the code entry screen went from 9% to 19%. How the GA4 failure reasons were read, how one input field turned out to open the Korean keyboard, and what was changed.