Android NDK arm64 16KB Page Alignment: Fix & Verify
Step-by-step guide to find, diagnose and fix libsc-native-lib.so 16KB page-size alignment for arm64-v8a. Tools, linker flags, packaging, CI checks and NDK r28+
How can I ensure libsc-native-lib.so uses a 16 KB page-size alignment for arm64-v8a on Android? I need APK 16 KB page alignment: the lib that appears to come with the NDK/Android SDK is 16 KB aligned under armeabi-v7a but not for arm64-v8a. I’m using AGP 8.13.2, Gradle 8.13 and NDK r28. Where can I find these native libraries in the SDK/NDK, and how can I force the linker or the build/packaging process to select or produce 16 KB page-size–compliant native libs for arm64? What tools, build flags, or manual inspection/packaging steps can I use to diagnose and fix the alignment mismatch?
Android NDK r28 defaults to 16 KB–aligned shared libraries for 64‑bit ABIs, but prebuilt or dependency .so files can still be 4 KB aligned. To fix libsc-native-lib.so for arm64-v8a: locate the arm64 copy (NDK prebuilt, AAR or merged APK), inspect it with llvm-objdump or readelf, then rebuild or relink that library with the linker flag -Wl,-z,max-page-size=16384 (or add target_link_options / ndk-build flags) and ensure your Gradle/AGP uses NDK r28 via ndkVersion.
Contents
- Quick checklist
- Find libsc-native-lib.so in SDK/NDK and in the APK/AAR
- Diagnose alignment: tools and commands
- Force 16 KB page-size alignment in Android NDK builds (arm64-v8a)
- Third‑party/prebuilt libraries and packaging fixes
- Automate checks in CI and pre-upload scripts
- Sources
- Conclusion
Quick checklist
- Build a release APK/AAB and inspect lib/arm64-v8a/libsc-native-lib.so inside it (Android Studio → Build → Analyze APK or unzip the artifact).
- Run
llvm-objdump -porreadelf -lon the arm64 .so and confirm the PT_LOAD alignment is 0x4000 (16 KB). - If the arm64 .so is 4 KB: determine whether it’s (a) built by your code, (b) bundled in an AAR, or © a prebuilt from the NDK/SDK.
- Fix by rebuilding/relinking with
-Wl,-z,max-page-size=16384for that library (best), or replace the packaged .so with a 16 KB–aligned copy (workaround). - Make sure your app/module uses the intended NDK (set
android.ndkVersionin build.gradle) and Build Tools 35+. See the official guidance for details.
Find libsc-native-lib.so in SDK/NDK and APK
Where these files live (common places to search)
- In the final APK/AAB: lib/arm64-v8a/libsc-native-lib.so (or inside split APKs). Use Android Studio → Build → Analyze APK or:
- unzip app-release.apk -d /tmp/apk && ls /tmp/apk/lib/arm64-v8a/
- In your app’s build output (merged native libs):
- app/build/intermediates/merged_native_libs/
/out/lib/arm64-v8a/ - In local dependency AARs (third‑party SDKs):
- unzip path/to/dependency.aar -l | grep libsc-native-lib.so
- In the NDK/toolchain/sysroot/prebuilt trees (prebuilt runtime libs):
- $(NDK_ROOT)/toolchains/llvm/prebuilt/
/lib64 or under the NDK sysroot paths (see the NDK r28 notes).
Quick find commands (adjust paths for your host: linux-x86_64, darwin-x86_64, windows-x86_64):
# search SDK / NDK
find "$ANDROID_SDK_ROOT" "$ANDROID_NDK_ROOT" -type f -name "libsc-native-lib.so" -print
# inspect an AAR
unzip -l path/to/library.aar | grep libsc-native-lib.so
# inspect merged native libs after a build
ls -la app/build/intermediates/merged_native_libs/release/out/lib/arm64-v8a/
If you find multiple copies (armeabi-v7a vs arm64-v8a), note their origins — arm64 is the one Google Play checks for 16 KB alignment on modern devices.
Diagnose alignment: tools and commands
Tools you’ll use
- llvm-objdump (shipped with the Android NDK)
- readelf (on Linux/macOS)
- Android Studio APK Analyzer or unzip
- Android check scripts (AOSP/NDK examples like check_elf_alignment.sh / atest elf_alignment_test)
How to check alignment (clear, reproducible commands)
- Extract the arm64 .so from the APK/AAR (example):
unzip -p app-release.apk lib/arm64-v8a/libsc-native-lib.so > /tmp/libsc-arm64.so
- Check with llvm-objdump from your NDK:
NDK=/path/to/android-ndk-r28
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objdump -p /tmp/libsc-arm64.so | grep -A1 LOAD
Look for an alignment value that equals 16384 or for text like ALIGNMENT 2**14 (some versions display the power-of-two).
- Or check with readelf:
readelf -l /tmp/libsc-arm64.so
# In the Program Headers table look at the "Align" column for LOAD entries.
# Align = 0x1000 => 4096 (bad)
# Align = 0x4000 => 16384 (good)
Example interpretation:
- Align column shows 0x1000 → library is 4 KB aligned (problem for pure 16 KB devices).
- Align column shows 0x4000 → library is 16 KB aligned (OK for arm64/x86_64 targets).
Quick script that fails on any non‑16KB .so inside an APK (adapt host paths):
NDK=/path/to/android-ndk-r28
OBJDUMP=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objdump
unzip -q app-release.apk -d /tmp/apk
for f in /tmp/apk/lib/*/*.so; do
echo "Checking $f"
$OBJDUMP -p "$f" | grep -q 'p_align' && \
$OBJDUMP -p "$f" | grep -q 'p_align 16384' || \
echo "BAD: $f is not 16KB aligned"
done
(If your version of llvm-objdump prints alignment differently, fall back to readelf parsing.)
References that explain the checks and recommended tools are in the Android docs and NDK changelog — see the Sources below.
Force 16 KB page-size alignment in Android NDK builds (arm64-v8a)
First rule: if you control the native source, relink/rebuild the library — that’s the correct fix. Changing ELF headers after-the-fact is unsafe.
- Ensure your project actually uses NDK r28 (or newer) and Build Tools 35+
- In module build.gradle:
android {
compileSdk 34
buildToolsVersion "35.0.0"
ndkVersion "28.0.12433566" // pin the r28 NDK you have installed
}
AGP + a pinned ndkVersion makes Gradle use the r28 toolchain across modules and CI.
- Add the linker option that forces a 16 KB max page size
- Best placed in the native module’s CMakeLists.txt so it affects that library’s link step:
# target-specific (preferred)
target_link_options(my_native_target PRIVATE "-Wl,-z,max-page-size=16384")
# or globally (if you can't edit the target)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
- For ndk-build (Application.mk / Android.mk):
# Application.mk
APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true # r27 guidance; see NDK docs for variable semantics
# In Android.mk add to LOCAL_LDFLAGS for the module:
LOCAL_LDFLAGS += -Wl,-z,max-page-size=16384
- If the native module is built by a dependency (Prefab / third‑party CMake), you must apply the flags to that module itself. Common problems:
- Prefab consumers or prebuilt modules ignore flags added only at the app level. Add flags in the native library’s CMake, or rebuild the dependency with r28.
- Some toolchains default to flexible behavior on older NDKs — explicitly pass the linker flag.
- When you can’t rebuild the module inside its own tree
- Use
app/src/main/jniLibs/arm64-v8a/libsc-native-lib.soto override dependency libs at packaging time. Put a 16 KB–aligned lib there (Gradle will merge/override). This is a stopgap until the real rebuild happens.
- Confirming success
- Rebuild the AAB/APK, extract the arm64 .so and re-run the readelf/llvm-objdump checks. The Align column must read 0x4000.
Why this works: the linker option -Wl,-z,max-page-size=16384 tells the GNU/Clang linker to set PT_LOAD p_align to 16384 (2**14) when producing the ELF, which makes the shared object 16 KB–aligned. The NDK r28 change makes this the default for 64‑bit ABIs, but prebuilt or older-built binaries won’t benefit until rebuilt (see NDK r28 changelog).
Helpful links:
- Android page on 16 KB page sizes with examples and checks: https://developer.android.com/guide/practices/page-sizes
- NDK r28 changelog / notes about default alignment: https://github.com/android/ndk/wiki/Changelog-r28
- Build system guidance for linker flags: https://android.googlesource.com/platform/ndk/+/master/docs/BuildSystemMaintainers.md
Third‑party / prebuilt libraries and packaging fixes
If the arm64 .so came prebuilt (inside an AAR or SDK), you have three practical options:
-
Ask the SDK/vendor for a 16 KB–aligned arm64 build. That’s the cleanest solution.
-
Rebuild the offending library from source using NDK r28+ or by adding
-Wl,-z,max-page-size=16384to its linker flags (recommended if you control the source). -
Replace the packaged arm64 .so at build time (workaround)
- Unpack the AAR, swap in a fixed lib, rezip and publish a local AAR, or place the fixed .so in your app at:
- app/src/main/jniLibs/arm64-v8a/libsc-native-lib.so
Gradle will package the app’s jniLibs version ahead of the dependency’s library, so this effectively overrides it. - Example AAR repack workflow (quick hack):
unzip library.aar -d /tmp/aar
cp fixed/lib/arm64-v8a/libsc-native-lib.so /tmp/aar/jni/arm64-v8a/libsc-native-lib.so
cd /tmp/aar
zip -r ../library-fixed.aar *
Caveats: repacking is a temporary fix and may violate vendor licensing; re-linking at source remains best.
Why you can’t just patch the ELF header reliably: changing p_align after linking is not guaranteed safe. Android’s guidance and practice is to relink with the correct max page size.
Automate checks in CI and pre-upload scripts
Fail the build if any 64-bit native library is not 16 KB aligned. Example GitHub Actions job (conceptual; adapt to your runners and NDK path):
jobs:
check-16kb:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK
uses: actions/setup-java@v3
with: java-version: '17'
- name: Build release AAB
run: ./gradlew :app:bundleRelease
- name: Unzip AAB and extract native libs
run: |
unzip app/build/outputs/bundle/release/app-release.aab -d /tmp/aab
# adjust to exact path for base.apk inside the aab or unpack produced APKs
- name: Check 16KB alignment
run: |
NDK=/opt/android-ndk-r28
OBJDUMP=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objdump
find /tmp/aab -name '*.so' | while read f; do
echo "Checking $f"
$OBJDUMP -p "$f" | grep -q 'p_align.*16384' || { echo "$f is NOT 16KB aligned"; exit 1; }
done
You can adapt the script to use readelf parsing if your objdump prints alignment differently. Several community posts show reproducible GitHub Actions examples for this check.
See a community CI example and tooling tips here: https://medium.com/@shiva999421/androids-16kb-page-size-revolution-a-complete-developer-s-guide-for-react-native-apps-2c0cd43abdd5
Sources
- Support 16 KB page sizes | Compatibility | Android Developers
- android ndk - native lib 16kb alignment – libsc-native-lib.so under arm64-va - Stack Overflow
- android - Some .so files not supported 16 kb - Stack Overflow
- Recompile your app with 16 KB native library alignment - SDL Development
- Libraries that do not support 16kb alignment? | B4X Programming Forum
- How to Fix React Native .so Alignment Errors for Android’s 16KB Page Size
- The native library arm64-v8a/libc++_shared.so and x86/libjnidispatch.so is not 16 KB aligned · Issue #68 · LottieFiles/dotlottie-android
- [Android] 16KB Page Size Compatibility Issue in React Native Core Libraries · Issue #52594 · facebook/react-native
- android - Testing library for 16kb page size - Stack Overflow
- Android 16KB Page Size CI/CD example and guide (Medium)
- Changelog r28 · android/ndk Wiki · GitHub
- Build System Maintainers Guide | android/ndk | Android Open Source
- Transition to using 16 KB page sizes for Android apps and games using Android Studio — Android Developers Blog (July 2025)
- Fix React Native 16 KB Page Size Rejection Guide | Creole Studios
- 16 KB Page Size in Android 15: guide (Medium)
- 16 KB page size | Android Open Source Project
- Android 16 KB Page Size: The Complete Practical Guide - DevSecOps Now!!!
- Understanding Android NDK Versions: r27, r28 and Beyond (Medium)
- android/ndk r28 on newreleases.io
- 16KB memory page size for Flutter Android and ndkVersion requirement? - Stack Overflow
Conclusion
In short: verify which copy of libsc-native-lib.so is actually ending up in lib/arm64-v8a of your APK/AAB, inspect it with llvm-objdump or readelf, then rebuild/relink that library with -Wl,-z,max-page-size=16384 (or add the equivalent target_link_options / ndk-build LDFLAGS). Make sure Gradle/AGP is pinned to NDK r28 (or newer) so that your Android NDK toolchain produces 16 KB–aligned arm64 binaries by default; if you can’t rebuild a third‑party .so, replace it at packaging time with a fixed copy or request a vendor update.