Taming 4KB vs 16KB Page Sizes on Android: A Developer’s Learning Log

7 min read
#Android #Jetpack Compose

Taming 4KB vs 16KB Page Sizes on Android: A Developer’s Learning Log

Part of my ongoing AI-assisted dev notes. This post documents how I updated an Android SDK’s native libraries and packaging to run cleanly on both 4KB and 16KB page-size devices, plus the decision-making, dead-ends, and final checklist.


Context: What I’m Building (and Why It Broke)

I maintain an Android SDK that ships native code (C/C++ via the NDK). Things worked fine on most devices—until I started testing on devices/emulators with 16KB kernel page size (increasingly common on Android 15+). My checker script began reporting UNALIGNED for some 64-bit .so files, and a few builds had runtime load issues.

Symptoms I saw:

Goals:

  1. Make sure 64-bit ABIs (arm64-v8a, x86_64) are ELF-aligned for 16KB pages.
  2. Ensure the APK stores native libs uncompressed and is zip-aligned to 16KB.
  3. Keep everything backward-compatible with 4KB devices.

What I Learned / Architecture Decisions

  1. ABI filters aren’t page-size switches. They only control which CPU architectures we target. Page-size compatibility is about how we build and package the native libs.
  2. 64-bit libs need 16KB ELF alignment (>= 2**14). 32-bit libs can stay at 4KB (2**12).
  3. NDK r28+ makes 16KB ELF alignment easier for 64-bit out of the box. On NDK r27, I must set linker flags.
  4. Packaging matters: Native libs should be uncompressed in the APK and zip-aligned to 16KB for proper mapping on 16KB devices.
  5. Backward compatibility: 16KB-aligned 64-bit libs are safe on 4KB devices; 16KB is a multiple of 4KB.
  6. Ownership detection: To fix third-party .so files, I need to locate the responsible AAR from the Gradle cache or via Android Studio’s Analyze APK and update/vendor-request.

Key Concepts Explained

Page Size (Analogy First)

Imagine the OS hands out memory in boxes. A 4KB device has small boxes; a 16KB device has bigger boxes. If your package (a segment in your .so) expects the wrong box size or isn’t aligned to the box boundaries, it’s harder (or impossible) to place neatly. That’s where ELF alignment comes in.

ABIs (Who Speaks Which Language)

ABI defines how compiled code speaks to the CPU. On Android:

Rule of thumb: 64-bit ABIs need ELF p_align >= 16KB. 32-bit ABIs can remain at 4KB.

ELF Alignment vs Zip Alignment (Two Layers)

jniLibs (Two Meanings)


Code Snippets & Scenarios

Why: Simplest path. NDK r28 aligns 64-bit ELF segments appropriately by default.

No extra flags required for 16KB on 64-bit in many cases, but still verify with the checker.

// build.gradle (module)
android {
  defaultConfig {
    externalNativeBuild {
      cmake {
        cppFlags "-std=c++17"
        // With NDK r28+, 64-bit ELF alignment is typically handled.
      }
    }
  }
  externalNativeBuild {
    cmake {
      path "src/main/cpp/CMakeLists.txt"
      version "3.22.1"
    }
  }
}

Scenario B — NDK r27 (Add Flags)

Why: If you’re on r27 (or want explicit control), enable flexible page sizes + 16KB max page size on the linker.

// build.gradle (module)
android {
  defaultConfig {
    externalNativeBuild {
      cmake {
        cppFlags "-std=c++17"
        arguments "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON",
                  "-DCMAKE_SHARED_LINKER_FLAGS=-Wl,-z,max-page-size=0x4000"
      }
    }
  }
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.22.1)

set(CMAKE_ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES ON)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=0x4000")

add_library(yoursdk SHARED
  native-lib.cpp
)

target_link_libraries(yoursdk
  android
  log
)

Scenario C — Packaging for Zip Alignment (APK)

Make sure native libs are uncompressed so they can be zip-aligned to 16KB. Modern AGP versions handle this better.

// build.gradle (module)
android {
  packagingOptions {
    jniLibs {
      useLegacyPackaging = false // keep .so uncompressed for proper 16KB zip alignment
    }
  }
}

Then install newer build-tools to validate zip alignment:

sdkmanager "build-tools;35.0.0-rc3"
# Ensure $ANDROID_HOME/build-tools/35.0.0-rc3 is first on PATH

Scenario D — Finding Which AAR Owns a .so

If libAndroidSdk.so or libfcgdsxh.so isn’t in your repo, locate the vendor AAR:

CACHE="$HOME/.gradle/caches/modules-2/files-2.1"

# Who ships libAndroidSdk.so?
find "$CACHE" -type f -name "*.aar" -print0 \
| xargs -0 -I{} sh -c 'unzip -l "{}" | grep -q "libAndroidSdk.so" && echo "{}"'

# Who ships libfcgdsxh.so?
find "$CACHE" -type f -name "*.aar" -print0 \
| xargs -0 -I{} sh -c 'unzip -l "{}" | grep -q "libfcgdsxh.so" && echo "{}"'

From the path, you’ll see groupId/artifactId/version. Update that dependency to a 16KB-ready release or ask the vendor to rebuild with the flags above (or using NDK r28+).

Scenario E — Verifying Alignment

1) ELF (internal)

Expect ALIGNED (2**14) (or higher like 2**16) for 64-bit libs:

# Quick scan (custom script output)
./check_elf_alignment.sh app-arm64-v8a.apk | grep -E 'ALIGNED|UNALIGNED'

# Manual check p_align for a single .so
unzip -p app-debug.apk lib/arm64-v8a/libSomething.so > /tmp/libSomething.so
readelf -l /tmp/libSomething.so | grep -A1 'LOAD'  # p_align should be 0x4000 for 64-bit

2) Zip (packaging)

After installing build-tools 35+, re-run the zip portion of your checker. Confirm .so files are stored (uncompressed) and 16KB zip-aligned inside the APK.


What Worked, What Didn’t

Worked:

Didn’t (or was misleading):


What I’d Do Differently

  1. Automate the checks in CI: Fail builds if any arm64-v8a/x86_64 .so shows < 2**14, or if zip alignment/packaging regress.
  2. Vendor SLAs: Ask third-party providers to confirm 16KB readiness in release notes. Keep a constraint map in Gradle to force minimum versions.
  3. Early device coverage: Always test on at least one 4KB and one 16KB emulator/device before cutting a release.

References / Further Reading

(Tip: for production docs, link to the exact Android Dev pages and your internal runbooks.)


Final Checklist (Copy/Paste for CI)


If you found this useful or have a different setup, ping me—always curious to compare notes.

About the Author

Aniket Indulkar is an Android Engineer based in London with a Master's in Artificial Intelligence. He writes about AI, ML, Android development, and his continuous learning journey.

Connect on LinkedIn →