Skip to main content

Understanding platform APIs

Android distinguishes between public NDK APIs (stable and supported) and private platform APIs (internal implementation details that may change).
Using private platform APIs can cause your app to break on future Android versions. Only use public NDK APIs in production apps.

Public vs private APIs

Public NDK APIs

Public APIs
Stable
These APIs are officially supported, documented, and guaranteed to remain compatible across Android versions.
Characteristics of public APIs:
  • Documented in the NDK API Reference
  • Headers included in the NDK
  • Linked against public libraries (libandroid.so, liblog.so, etc.)
  • Stability guarantees across Android versions

Private platform APIs

Private APIs
Unstable
Internal Android platform APIs that are not part of the NDK. No compatibility guarantees.
Starting with Android 7.0 (API 24), the platform actively restricts access to private APIs. Restrictions have been strengthened in each release.
Characteristics of private APIs:
  • Not documented for NDK developers
  • May change or be removed without notice
  • May be blocked or restricted at runtime
  • Can cause app crashes or rejections from Google Play

Private API restrictions timeline

Android 7.0 (API 24) - Initial restrictions

Introduced restrictions on accessing private platform symbols:
// This will fail on Android 7.0+
void* handle = dlopen("libandroid_runtime.so", RTLD_NOW);
// Returns NULL - private library not accessible

Android 9.0 (API 28) - Strict enforcement

Android 9.0 introduced strict enforcement of private API restrictions for all apps targeting API 28+.
Restriction categories:
ListDescriptionBehavior
WhitelistPublic APIsAlways accessible
Light greyPrivate APIsAccessible with warning
Dark greyPrivate APIsAccessible only if targetSdkVersion < 28
BlacklistPrivate APIsNever accessible

Android 10+ (API 29+) - Progressive restrictions

Each Android version moves more APIs from grey lists to blacklist:
// Example: Attempting to access private API
void* symbol = dlsym(RTLD_DEFAULT, "_ZN7android14SurfaceControl7getLayerEv");

if (symbol == NULL) {
    // Symbol blocked due to private API restrictions
    __android_log_print(ANDROID_LOG_ERROR, "App", 
        "Private API blocked: %s", dlerror());
}

Public API categories

Core Android libraries

These libraries provide public APIs that you can safely link against:
libandroid.so
library
Core Android APIs: NativeActivity, Asset Manager, Configuration, etc.
liblog.so
library
Android logging APIs for integration with logcat.
libz.so
library
Compression library (zlib).
libEGL.so
library
EGL graphics initialization and management.
libGLESv2.so
library
OpenGL ES 2.0 graphics API.
libGLESv3.so
library
OpenGL ES 3.0+ graphics API.
libvulkan.so
library
Vulkan graphics API (API 24+).

Media and audio libraries

libaaudio.so
library
AAudio high-performance audio API (API 26+).
libOpenSLES.so
library
OpenSL ES audio API.
libmediandk.so
library
Media codec and format APIs (API 21+).
libcamera2ndk.so
library
Camera2 NDK API (API 24+).

Neural Networks and ML

libneuralnetworks.so
library
Neural Networks API for hardware-accelerated ML (API 27+).

Library linking examples

CMake configuration

# Link against public Android libraries
target_link_libraries(${CMAKE_PROJECT_NAME}
    # Core Android
    android
    log
    
    # Graphics
    EGL
    GLESv3
    
    # Audio (API 26+)
    aaudio
    
    # Media (API 21+)
    mediandk
    
    # Compression
    z
)

ndk-build configuration

# Android.mk
LOCAL_LDLIBS := -landroid -llog -lEGL -lGLESv3 -lz

# For API level specific libraries
ifeq ($(shell test $(APP_PLATFORM_LEVEL) -ge 26; echo $$?),0)
    LOCAL_LDLIBS += -laaudio
endif

Detecting API availability at runtime

Code example: Runtime API detection

#include <dlfcn.h>
#include <android/log.h>

#define LOG_TAG "APIDetection"

typedef struct {
    bool has_aaudio;
    bool has_vulkan;
    bool has_ndk_camera;
} api_availability_t;

api_availability_t detect_api_availability() {
    api_availability_t apis = {false, false, false};
    
    // Check for AAudio (API 26+)
    void* aaudio = dlopen("libaaudio.so", RTLD_NOW | RTLD_NOLOAD);
    if (aaudio != NULL) {
        apis.has_aaudio = true;
        dlclose(aaudio);
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "AAudio available");
    }
    
    // Check for Vulkan (API 24+)
    void* vulkan = dlopen("libvulkan.so", RTLD_NOW | RTLD_NOLOAD);
    if (vulkan != NULL) {
        apis.has_vulkan = true;
        dlclose(vulkan);
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Vulkan available");
    }
    
    // Check for Camera2 NDK (API 24+)
    void* camera = dlopen("libcamera2ndk.so", RTLD_NOW | RTLD_NOLOAD);
    if (camera != NULL) {
        apis.has_ndk_camera = true;
        dlclose(camera);
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Camera2 NDK available");
    }
    
    return apis;
}

Code example: Weak linking for optional APIs

// Weakly link against optional API (available API 26+)
__attribute__((weak)) int AAudio_createStreamBuilder(...);

void use_audio_api() {
    if (AAudio_createStreamBuilder != NULL) {
        // AAudio is available, use it
        __android_log_print(ANDROID_LOG_INFO, "Audio", "Using AAudio");
        // Call AAudio functions...
    } else {
        // Fall back to OpenSL ES
        __android_log_print(ANDROID_LOG_INFO, "Audio", "Falling back to OpenSL ES");
        // Use OpenSL ES instead...
    }
}

API availability by category

Graphics APIs

APILibraryMinimum APINotes
OpenGL ES 2.0libGLESv2.so21Widely supported
OpenGL ES 3.0libGLESv3.so21Check device support
OpenGL ES 3.1libGLESv3.so21Check device support
OpenGL ES 3.2libGLESv3.so24Limited device support
Vulkan 1.0libvulkan.so24Check device support
Vulkan 1.1libvulkan.so28Check device support
EGL 1.4libEGL.so21Required for OpenGL ES

Audio APIs

APILibraryMinimum APINotes
OpenSL ESlibOpenSLES.so21Legacy, but stable
AAudiolibaaudio.so26Preferred for low latency

Camera APIs

APILibraryMinimum APINotes
Camera2 NDKlibcamera2ndk.so24Modern camera API

ML and compute

APILibraryMinimum APINotes
NNAPIlibneuralnetworks.so27Hardware-accelerated ML

Avoiding private API usage

Common mistakes

These patterns indicate private API usage and should be avoided.
Don’t do this:
# DON'T: These are private libraries
target_link_libraries(app
    android_runtime  # Private!
    binder           # Private!
    ui               # Private!
)
Do this instead:
# Use public APIs only
target_link_libraries(app
    android
    log
    EGL
    GLESv3
)
Don’t do this:
// DON'T: Internal Android headers
#include <ui/GraphicBuffer.h>
#include <binder/IBinder.h>
Do this instead:
// Use public NDK headers
#include <android/hardware_buffer.h>
#include <android/native_window.h>
Don’t do this:
// DON'T: Access private symbols
void* handle = dlopen("libgui.so", RTLD_NOW);
void* private_func = dlsym(handle, "_ZN7android11Surface4lockEPNS_11Surface13SurfaceInfoEPNS_6RegionE");
Do this instead: Use public NDK APIs or JNI to call Java APIs when needed.

Migration strategies

If you’re currently using private APIs, here are migration strategies:

1. Use public NDK equivalents

// Before: Private SurfaceControl API
// SurfaceControl::createSurface() // Private!

// After: Public ANativeWindow API
#include <android/native_window.h>

void use_public_api(ANativeWindow* window) {
    // Use public native window APIs
    int32_t width = ANativeWindow_getWidth(window);
    int32_t height = ANativeWindow_getHeight(window);
    int32_t format = ANativeWindow_getFormat(window);
}

2. Use JNI to call Java APIs

// If no NDK equivalent exists, use JNI to call Java APIs
#include <jni.h>

void call_java_api(JNIEnv* env, jobject activity) {
    // Get Java class
    jclass activity_class = (*env)->GetObjectClass(env, activity);
    
    // Get method ID
    jmethodID method = (*env)->GetMethodID(env, activity_class, 
        "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
    
    // Call Java method
    jstring service_name = (*env)->NewStringUTF(env, "window");
    jobject window_manager = (*env)->CallObjectMethod(env, activity, 
        method, service_name);
    
    // Use the Java object...
}

3. Request new public APIs

If you need functionality that’s not available in the NDK:

File NDK feature request

Request new public APIs through the NDK GitHub repository

Testing for private API usage

Using veridex

Android provides a tool called veridex to detect private API usage:
# Download veridex from Android SDK
${ANDROID_SDK}/cmdline-tools/latest/bin/sdkmanager "platforms;android-34"

# Run veridex on your APK
${ANDROID_SDK}/platforms/android-34/veridex.sh \
    --dex-file=app.apk \
    --imprecise

# Output will show any private API usage detected

Lint warnings

Android Studio will show lint warnings for known private API usage:
// Android Studio will warn about this
void* private_lib = dlopen("libandroid_runtime.so", RTLD_NOW);
// Warning: Using private Android library

Official resources

Android changes for NDK developers

Dynamic linker changes and restrictions

Private API restrictions

Official documentation on private API restrictions

Best practices

Stick to APIs documented at developer.android.com/ndk/reference. If an API isn’t documented, assume it’s private.
Private API restrictions vary by Android version. Test your app on API 24, 28, 29, and the latest version.
When using newer APIs, use weak linking to gracefully degrade on older devices.
Subscribe to Android developer announcements to stay informed about API changes and deprecations.

Next steps

NDK API overview

Learn about available NDK APIs and stability guarantees

Dynamic linker

Understanding the Android dynamic linker

Android-specific APIs

Public Android-specific native APIs

Bionic status

C library API availability and POSIX compliance

Build docs developers (and LLMs) love