In October 2012, OpenSignal published a study showing 3,997 distinct Android devices in use. That number has since become famous. At the time it was just our daily reality.
Android 4.0 (Ice Cream Sandwich) had been out a year. Android 4.1 (Jelly Bean) had just launched. But the installed base was still 40% on Gingerbread (2.3) and 15% on Froyo (2.2). Each version had different APIs. Different vendors - Samsung, HTC, Sony Ericsson, LG - had patched and customized their builds. Some broke standard Android APIs. Some added APIs that didn't exist elsewhere.
We were building a camera-based document scanner. The camera API was the worst place to be in 2012 Android.
What fragmentation actually broke
The camera API
Android's Camera class (the old, pre-Camera2 API) was documented but implemented differently by every major OEM:
// This is what the Android docs said would work
Camera.Parameters params = camera.getParameters();
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
camera.setParameters(params);
camera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (success) captureImage();
}
});
Samsung Galaxy S2 (GT-I9100): worked perfectly.
HTC One X: autoFocus callback never fired if the surface wasn't fully initialized. Required a 200ms delay after setPreviewDisplay before calling startPreview.
Sony Xperia S: FOCUS_MODE_AUTO silently fell back to FOCUS_MODE_FIXED without throwing an error. Images came back blurry. No exception, no log message.
Some MediaTek devices (cheap Chinese OEMs): camera returned null from open(Camera.CameraInfo.CAMERA_FACING_BACK) unless you called it on the main thread. Off-main-thread camera open worked on every other device.
The fix: defensive programming with device-specific workarounds:
private void openCameraWithFallback() {
Camera cam = null;
try {
cam = Camera.open(0);
} catch (RuntimeException e) {
// Some devices throw here if called from non-main thread
if (Looper.myLooper() != Looper.getMainLooper()) {
Handler h = new Handler(Looper.getMainLooper());
h.post(() -> openCameraWithFallback());
return;
}
throw e;
}
Camera.Parameters p = cam.getParameters();
List<String> supportedFocus = p.getSupportedFocusModes();
// Prioritize: continuous > auto > fixed - use what's actually available
String focusMode;
if (supportedFocus.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE;
} else if (supportedFocus.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
focusMode = Camera.Parameters.FOCUS_MODE_AUTO;
} else {
focusMode = Camera.Parameters.FOCUS_MODE_FIXED;
}
p.setFocusMode(focusMode);
cam.setParameters(p);
}
Screen density and layout
Android introduced density buckets: ldpi (120dpi), mdpi (160dpi), hdpi (240dpi), xhdpi (320dpi). Devices mapped to these buckets, but OEMs didn't always report accurate density - some reported hdpi on physically mdpi screens to get "better" icons.
We maintained separate drawable folders and tested each density class manually:
res/
drawable-mdpi/ → 48×48px baseline
drawable-hdpi/ → 72×72px (1.5×)
drawable-xhdpi/ → 96×96px (2×)
layout/ → base layout
layout-sw600dp/ → tablets (600dp smallest width)
The sp unit for text and dp for everything else was mandatory - hardcoding px meant different physical sizes on every device. A button that fit on the Nexus S looked enormous on a 5-inch 480p phone.
Our testing device matrix
With zero budget for a device lab, we built a pragmatic coverage strategy:
Tier 1 (in-house devices, tested on every build):
- Samsung Galaxy S2 - dominant Android device in 2012, custom Samsung camera
- Samsung Galaxy Ace (2.3) - Gingerbread, low-end market
- HTC Desire HD (2.3) - different manufacturer, different customizations
- Nexus S (4.0) - "pure Android" reference baseline
- Galaxy Tab 10.1 - tablet layout verification
Tier 2 (borrowed from team/clients, tested before release):
- Sony Xperia S, LG Optimus 2X, Huawei Ascend G300
Tier 3 (emulator only):
- Android 2.2 (Froyo) - 15% market share but no physical devices
- Large tablets, unusual densities
For Tier 3 we accepted that emulator results were indicative, not definitive. The 2012 Android emulator was notoriously slow (30x slower than real hardware) and didn't simulate OEM customizations.
The one thing that caught most issues
A physical device rotation test before every release: open the app, go through the main user flow, rotate the device at each step.
Activity recreation on rotation (onDestroy → onCreate) exposed every state management bug. In Gingerbread, the activity was destroyed and recreated by default on rotation. Developers who only tested on ICS+ (where some devices had changed default behavior) would ship with rotation crashes that only appeared on Gingerbread.
// Common bug: not saving state in onSaveInstanceState
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("scanned_text", mScannedText); // Save text
outState.putParcelable("current_image", mCurrentImage); // Save image reference
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mScannedText = savedInstanceState.getString("scanned_text");
mCurrentImage = savedInstanceState.getParcelable("current_image");
}
}
Not doing this: worked on every test run (nobody rotates deliberately). Crashed for users in the wild constantly - because users rotate their phones without thinking.
What Android fragmentation taught us about resilience
The core habit: assume the device will not behave like the spec. Check whether an API is actually supported before calling it. Never assume a feature exists because the OS version says it should. Use try-catch around hardware access. Have a fallback path.
This is still valid in 2025. iOS has its own fragmentation (different hardware capabilities across generations, different iOS minor versions). React Native still has device-specific edge cases. The specific devices and APIs have changed. The principle - test on real hardware, code defensively, prioritize by market share - is identical.
The 3,997 devices figure was alarming at the time. In hindsight, the devices that actually mattered for our users numbered about 15. The discipline was figuring out which 15 - then testing those thoroughly rather than worrying about the long tail.
Aunimeda develops mobile applications for iOS and Android - from MVP to production-ready apps with full backend integration.
Contact us to discuss your mobile project. See also: Mobile App Development, Mobile Game Development