What the 16kB page size policy means
Google Play requires apps that ship native code (.so) to run correctly on devices that use a 16kB memory page size. If your APK/AAB assumes 4kB pages, loading native libraries can fail on those devices. Compliance boils down to:
- Build native libraries that don’t assume 4kB pages (linker page-size alignment).
- Package libraries so the loader can map them on 16kB devices (page-aligned, uncompressed when applicable).
How it affects React Native apps
React Native apps often include native libraries (Hermes/JSC, RN core, and third-party modules). Even if your app is mostly JavaScript, the APK likely contains .so files under lib/<abi>/. You must ensure these libraries:
- Are linked with a max page size compatible with 16kB devices.
- Are packaged in a way the system can load directly (or safely extracted on older OS versions).
Quickstart
- Use a recent Android Gradle Plugin (AGP) and NDK (r23+ recommended).
- If publishing an AAB, enable uncompressed native libs so Play aligns them appropriately.
- Ensure packaging doesn’t force legacy extraction on devices that support direct loading.
- Link your native code with a 16kB max page size.
- Verify with readelf and zipalign.
What you change and where
Change | Purpose | Where |
---|---|---|
Use NDK r23+ | Modern linker defaults, better 16k support | android/ndkVersion or local NDK |
Enable uncompressed native libs | Allows direct mapping and proper page alignment | gradle.properties (AAB) |
Disable legacy JNI packaging (API 23+) | Keep libs uncompressed in APKs | app/build.gradle |
Link with -Wl,-z,max-page-size=16384 | Produce 16k-compatible .so | CMakeLists.txt or ndkBuild |
Verify alignment | Catch misaligned libs early | CI shell script |
Minimal working example
The snippets below show a minimal RN Android setup that complies. Adjust names/paths to your project.
gradle.properties
# Ensures Play delivers uncompressed, page-aligned native libs from AAB
android.bundle.enableUncompressedNativeLibs=true
app/build.gradle (Groovy)
android {
defaultConfig {
// Prefer minSdk 23+ to fully benefit from direct loading
minSdkVersion 23
ndk {
abiFilters "arm64-v8a", "armeabi-v7a", "x86_64"
}
}
// For AGP 7.x
packagingOptions {
jniLibs {
// false = uncompressed, page-aligned JNI libs on API 23+
useLegacyPackaging = false
}
}
// If you build a native module via CMake
externalNativeBuild {
cmake {
path file("src/main/cpp/CMakeLists.txt")
}
}
}
AndroidManifest.xml (application tag)
<application
android:name=".MainApplication"
android:extractNativeLibs="false">
<!-- ... -->
</application>
CMakeLists.txt (example native module)
cmake_minimum_required(VERSION 3.18)
project(reactnative16k)
add_library(mylib SHARED native-lib.cpp)
# Critical: ensure segments are compatible with 16k page size
# Works with LLD from NDK r23+
if (CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|armv7|x86_64")
target_link_options(mylib PRIVATE "-Wl,-z,max-page-size=16384")
endif()
target_link_libraries(mylib log)
src/main/cpp/native-lib.cpp
#include <jni.h>
extern "C" JNIEXPORT jint JNICALL
Java_com_example_MainActivity_nativeAdd(JNIEnv*, jobject, jint a, jint b) {
return a + b;
}
Verification
Run these checks on the final artifact you will ship.
- Verify ELF segment alignment (each .so):
readelf -l app/build/intermediates/merged_native_libs/release/out/lib/arm64-v8a/libmylib.so | grep -E "LOAD|Align"
Look for Align values that are >= 0x4000 (16384) for PT_LOAD segments.
- Verify APK page alignment (for direct loading):
# Build a release APK or extract an APK from your AAB using bundletool, then:
zipalign -c 16384 app-release.apk
Expect “Verification successful”. If it fails, your libs aren’t aligned to 16k pages.
- Check for uncompressed native libraries in the APK:
unzip -l app-release.apk | grep "^\s*0\s*lib/.*\.so$" || true
A size of 0 in the compressed-size column indicates uncompressed entries.
- Play pre-launch report Upload to an internal testing track and confirm no “16kB page size” warnings.
Step-by-step (recommended path for RN apps)
- Upgrade toolchain
- Use AGP 7.0+ and Gradle compatible with your RN version.
- Set ndkVersion in android/build.gradle or install NDK r23+ via SDK Manager.
- Enable uncompressed native libs (AAB)
- In gradle.properties: android.bundle.enableUncompressedNativeLibs=true
- Publish AAB to Play; it will deliver page-aligned, uncompressed libs to API 23+ devices.
- Disable legacy packaging on API 23+
- app/build.gradle: packagingOptions.jniLibs.useLegacyPackaging = false
- Manifest: android:extractNativeLibs="false"
- Rebuild native modules
- If you maintain native code, add -Wl,-z,max-page-size=16384.
- For ndk-build, add to LOCAL_LDFLAGS; for CMake, use target_link_options.
- Audit third-party AARs
- If a vendor ships prebuilt .so with 4k-only alignment, request updated artifacts or recompile from source.
- Verify artifacts
- Use readelf on .so files and zipalign on the APK.
- Test on a device/emulator fleet if possible.
- Ship and monitor
- Upload to internal test track; check Play Console warnings.
Pitfalls and fixes
- Old toolchains: NDK < r21 or custom linkers may produce 4k-only segments. Fix by upgrading NDK and adding the linker flag.
- minSdk < 23: Direct loading of uncompressed libs is limited on older OS versions. You can still link with 16k max page size and allow extraction on API < 23; keep useLegacyPackaging=true for those builds if needed.
- Debug vs release differences: Debug APKs might be packaged differently. Always verify the release artifact.
- Mixed ABI settings: Ensure all ABIs you ship are rebuilt with the new flags.
- Prebuilt third-party .so: These are the most common source of failures. Audit and replace if necessary.
Performance notes
- Uncompressed, page-aligned native libs allow the system to mmap sections directly, reducing install time and storage overhead.
- 16k alignment can add small padding to the APK; expect a negligible size increase.
- Faster cold start is typical when avoiding extraction on API 23+.
FAQ
- Do pure-JS React Native apps need this? If your APK has no .so files, you’re unaffected. Most RN apps include .so (e.g., Hermes/JSC), so check.
- Is AAB required? Strongly recommended. Play can optimize delivery (including aligning libs) from AABs.
- Do I still need the linker flag if Play aligns my APK? Yes. Alignment at packaging time doesn’t fix ELF segment alignment set at link time.
- Which ABIs are impacted? Primarily arm64-v8a; apply changes to all ABIs you ship.
- How do I detect failing libs? Use readelf on each .so; check PT_LOAD Align values and rebuild offenders.
- Will this break older Android versions? No. Properly linked libs work across page sizes. For API < 23, you may rely on extraction if needed.