← Back to list

Surviving Android’s 16KB Page Size Mandate: A React Native War Story

How we migrated a production React Native app to 16KB page alignment, swapped out a deprecated camera library, and lived to tell the tale.

Tushar Bhogade · 2026-05-23 07:40 · 0 claps · 2.9 min read
#react-native #android-development #mobile-app-development #software-engineering #android-15
Open on Medium ↗
Wiki topics: SAF · Safety & Alignment 🌐 · Web Development 📱 · Mobile Development 📚 · Books & Reading 📷 · Photography

Surviving Android’s 16KB Page Size Mandate: A React Native War Story

How we migrated a production React Native app to 16KB page alignment, swapped out a deprecated camera library, and lived to tell the tale.

TL;DR

Starting with Android 15, Google requires every .so file in your APK/AAB to be aligned to 16KB page boundaries instead of the historical 4KB. What sounded like a one-line Gradle flag became a multi-week journey: NDK 29 upgrades, ML Kit API breakages, a complete camera library migration, a custom Kotlin patch to merge CameraX surfaces, and a 100MB jump in APK size.

If your app ships native camera, ML Kit, or older third-party libraries — this post is for you.

Why 16KB Page Size Matters

Modern ARM64 kernels can use either 4KB or 16KB memory pages. Android historically standardized on 4KB, but newer devices (Pixel 8+, OnePlus, Samsung flagships) ship with 16KB-page kernels.

The catch: native libraries (.so files) must match the host kernel page size. A 4KB-aligned .so crashes at load time on 16KB devices.

Google’s enforcement timeline:

  • Android 15 (API 35): 16KB devices become mainstream.
  • Nov 2025: Play Store submissions targeting SDK 35+ must ship 16KB-aligned libraries.
  • Non-compliant apps crash or are rejected.

No exceptions.

The Starting Point

Our app:

  • React Native 0.79.1
  • Camera via react-native-camera@4.2.1 (deprecated)
  • ML Kit for barcode, face detection, OCR
  • ~30 native libraries across 4 ABIs

What we expected: flip a Gradle flag, ship. Reality: 8 commits, 3 branches, a custom Kotlin patch, full camera library replacement.

Phase 1 — The “Easy” Gradle Flips

# android/gradle.properties
android.experimental.enablePageSizeAlignment=true
// android/app/build.gradle
packaging {
    jniLibs {
        useLegacyPackaging = false
    }
}
  • Align .so files to 16KB boundaries.
  • Store .so uncompressed for direct mmap.
  • Upgrade toolchain: AGP 8.13.0, NDK 29, compileSdk 36.

Result: app still crashed on devices.

Phase 2 — ML Kit Breakages

react-native-camera broke in three ways:

  1. NoClassDefFoundError for Detector → upgraded ML Kit, updated imports.
  2. Barcode class moved → patched 3 Java files.
  3. NoSuchMethodError due to forced vision-common version → reverted force directives, set app-level dependencies.

Bonus: some .so files not 16KB-aligned. Excluded from build to comply.

Phase 3 — Camera Library Migration

Legacy Camera1 API broke on Android 15+ → black preview screens.

Solution: migrate to **react-native-vision-camera** (Camera2/CameraX).

  • Handles modern Android correctly
  • Supports frame processors for OCR and barcode scanning
<Camera
  isActive={isCameraOn && isForeground}
  codeScanner={codeScanner}
  frameProcessor={frameProcessor}
/>

Key detail: isForeground prevents session leaks.

Phase 4 — Two-Surface Problem

Budget devices only support 2 concurrent surfaces (Preview + Analyzer).

Solution: Combined Analyzer Kotlin patch merges OCR + barcode into one surface.

  • Prevents black screens
  • Handles refCount correctly to avoid silent failures

Phase 5 — NDK 29 Crashes

libVisionCamera.so failed due to __cxa_init_primary_exception.

Solution: copy NDK 29 libc++_shared.so into jniLibs with preBuild Gradle task to guarantee proper symbol resolution.

OCR Gotchas

  • Regex failures after whitespace stripping
  • Date.now() reset on re-renders → use useRef
  • JS throttling only; never throttle native frame processor

APK Size Jump

Metric                 Before      After
------------------------------------------------
Release APK            ~120 MB     ~225 MB
.so files per ABI      23          27
Total native libs      ~100 MB     ~180 MB

Mitigations: ship as AAB, ABI splits, unbundled ML Kit.

Lessons Learned

  1. Gradle flag is easy; dependencies are not.
  2. Don’t force ML Kit globally; use app-level implementation.
  3. Camera1 is deprecated; migrate early.
  4. Test on budget devices (2-surface limit matters).
  5. Refcount lifecycles are unforgiving.
  6. NDK upgrades break ABI assumptions.
  7. APK size matters; optimize with AAB and ABI splits.

TL;DR

  • All .so files now 16KB-aligned
  • ML Kit upgraded and patched
  • Camera library migrated to VisionCamera
  • Combined Analyzer patch solves 2-surface issues
  • Gradle + NDK + packaging fully compliant

Your app is future-proof for Android 15+.


메타데이터
post_id
51ed7d3e59cc
slug
surviving-androids-16kb-page-size-mandate-a-react-native-war-story-51ed7d3e59cc
url
https://medium.com/@tusharbhogade/surviving-androids-16kb-page-size-mandate-a-react-native-war-story-51ed7d3e59cc
canonical_url
https://medium.com/@tusharbhogade/surviving-androids-16kb-page-size-mandate-a-react-native-war-story-51ed7d3e59cc
author_url
https://medium.com/@tusharbhogade
status
ok
fetched_at
2026-06-09 15:37:30