Knowledge Prerequisites
- Experience with the Android Studio IDE or the SDK Manager command line.
- CMake or other build tools for compiling native modules
What are page sizes?
Page Sizes is a way that operating systems allocate memory for applications.
If an application requests 1 byte to be allocated, the application will allocate 1 byte of memory from a page that is assigned to the application, if available. If there is no memory available to be allocated from a page, a new page will be created.
Historically, all android devices were configured to use 4KB page sizes. So when the android OS needs to create a new page of memory for an application, it will allocate 4kb of RAM for the application.
Starting with Android 15, devices may be configured to use 16kb page sizes instead. This means these devices will be allocating larger pages, so that it can reduce the amount of pages the device needs to manage or create.
Devices configured with 16KB page sizes will not load up native binaries that was compiled with 4kb page size memory.
How does this affect me as an Android Developer?
Well if you're android projects contain purely Java and/or Kotlin code, then you don't need to do anything within your own code base. You might have to check if any dependencies you import contains native modules, and if so, they will need be updated with a version that supports 16kb page memory.
Attempting to load a 4kb page size module into a 16kb page size device will yield runtime errors such as the one below:
java.lang.UnsatisfiedLinkError: dlopen failed: empty/missing DT_HASH/DT_GNU_HASH in "/data/app/~~8yW8cmL4SiL7B4WQLUoSOA==/com.example.package.testapp-yOfVv6oKzfxtGAE_SZwGqQ==/base.apk!/lib/x86_64/libmylib.so" (new hash type from the future?)
These binaries that produce this error needs to be recompiled with 16kb page support.
Enabling 16KB page support
Enabling 16kb page support is as adding the CMake's ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES
flag. NDK 27 will be required for this flag.
{
namespace = "..."
compileSdk = 34
ndkVersion = "27.0.12077973"
defaultConfig {
externalNativeBuild {
cmake {
version = "3.22.1"
arguments += listOf("-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON")
cppFlags += ""
}
}
}
}
If you do not use the libc++ library, or cannot upgrade to NDK 27, or don't use CMake. Then you can enable 16kb page size binaries by using the compiler flag: -Wl,-z,max-page-size=16384
.
If you DO use the libc++ library (aka libc++_shared.so
), then NDK 27 is required. Older NDK versions will not have a 16kb compatible libc++ library.
In my testing, building for 16kb page size support is backwards compatible to 4kb page size devices.
Optimising for 16kb page sizes
Because your native modules is not known where they will be ran ahead of time, and even though Android 15 supports a 16kb page size configuration, it doesn't mean they will be 16kb page size configured.
Therefore, it's recommended to check for the page size at runtime instead of relying on compiler flags. Android can use the getpagesize method to do this.
It will be inefficient to allocate a 4kb buffer when the OS might allocate you an entire 16kb page for that buffer. It's far more efficient to allocate a page worth of memory, or in multiples of a page for your buffers.
#include <unistd.h>
#include <stdio.h>
const int PAGE_SIZE = getpagesize();
const unsigned long bufferSize = PAGE_SIZE * 1; // Number of pages we wanted to allocate
char buffer[bufferSize]; //4096 or 16384 buffer
Testing on 16kb virtual machines
Lastly even if you don't have a physical device, there are 16kb simulators available.
You'll need at least Android Studio Koala 2024.1.2 or later (Currently Beta 1, at the time of writing) and the following packages:
Android SDK Platform VanillaIceCream
rev 4 or later
Google APIs Experimental 16k Page Size
(Intel x86_64 if on Intel/AMD CPU, or arm64 if on M-based macOS)
Android SDK Build-Tools 35
NDK 27.0.12077973
(Do not use the RC versions, they were broken for me)
Android SDK Command-line Tools
13.0 or later
CMake
3.22.1 or later
Android Emulator
34.2.16 or later
Android SDK Platform-Tools
or later
Note: Android Studio Koala 2024.1.1 will work for building and running, but the debugger will not attach.
Note: there is an API 35 16k emulator, which currently fails to boot for me. The arm64 version however does work on my M-based MacOS. The VanillaIceCream version works in both environments.
With the above configuration, you should be able to create a 16kb page simulator and test to see if your native binaries will load properly to prepare for the next generation of Android devices.
About the author
Norman Breau is an IT professional with over 10 years of field experience.
He has a wide range of skill sets, ranging from web & software development &
architecture, to Server & Database administration. Presently he works at
TotalPave where he plays
a lead role in software projects as well as manages the server infrastructure.
On his own time, he volunteers to some open source projects, namely Apache Cordova, as many projects
throughout his career were dependent on the Cordova project. Open source projects works the best when there are volunteers that depend on such software is able to contribute in some form.
Additionally, he has been working on a newer project called Fuse,
a framework for building native-web hybrid mobile applications. The framework
intends to fill a void somewheres between CapacitorJS and pure native development.
Fuse is simply just a native library, with a bundleable webview JS API.
Unlike Cordova and CapacitorJS, Fuse is more tailored to native developers,
who want to still utilise the webview for their application's UI but retain
full control over the native project and native code. The only requirement
is that Fuse needs to be installed in the application along with a compatible
Fuse JS runtime. Fuse offers the webview and a bridging API that natively
supports binary and is incredibly fast.
Like all websites, web hosting costs money. If the above article has helped you, please considering offering a small tip as a token of appreciation.