Skip to main content
Bionic is Android’s C library, math library, and dynamic linker. Understanding bionic’s characteristics and differences from standard C libraries like glibc is essential for writing portable and compatible NDK code.

What is bionic?

Bionic is Android’s implementation of the C standard library, providing:
  • Standard C library functions: malloc, printf, file I/O, etc.
  • POSIX APIs: Threading, sockets, file operations
  • Math library (libm): Mathematical functions
  • Dynamic linker: Loads shared libraries at runtime
  • Android-specific extensions: Additional APIs for Android platform integration
Bionic is BSD-licensed (not GPL), which aligns with Android’s licensing requirements and allows broader use in proprietary code.

Why bionic exists

Android created bionic instead of using existing C libraries for several reasons:
  • BSD license instead of GPL (glibc’s license)
  • Allows proprietary code without GPL restrictions
  • More flexible for device manufacturers and app developers
  • Smaller memory footprint than glibc
  • Critical for resource-constrained mobile devices
  • Stripped-down implementation focused on Android needs
  • Original glibc: ~2-3 MB, bionic: ~500-800 KB
  • Optimized for ARM processors (primary Android architecture)
  • Fast thread-local storage (TLS)
  • Efficient memory allocator for mobile workloads
  • Support for Android-specific kernel features
  • Built-in FORTIFY_SOURCE protections
  • Stack canaries and buffer overflow detection
  • Secure random number generation
  • Modern security features from the ground up

Key differences from glibc

Understanding these differences helps avoid portability issues:

Missing or limited functionality

Bionic intentionally omits some glibc features:
// Limited locale support
#include <locale.h>

// This works but has limited effect
setlocale(LC_ALL, "fr_FR.UTF-8");

// Many locale-specific functions have limited functionality:
// - strcoll(): Acts like strcmp() in older Android versions
// - strxfrm(): Limited transformation support
// - Monetary and numeric formatting: Limited support
Before Android 5.0 (API 21), locale support was extremely limited. From API 21+, ICU4C provides comprehensive internationalization support.
Solution: Use ICU4C for internationalization:
#include <unicode/ucol.h>  // ICU collation
#include <unicode/udat.h>  // ICU date formatting

Implementation differences

Some functions exist but behave differently than glibc:
// DNS resolution differences
#include <netdb.h>

// getaddrinfo() uses Android's DNS resolver
// - May respect per-app VPN settings
// - Different caching behavior
// - May use DNS-over-TLS depending on Android version

// gethostbyname() is deprecated but available
// Prefer getaddrinfo() for new code

API levels and compatibility

Bionic evolves with Android versions. Functions are added and behavior changes across API levels:

Understanding API levels

Each Android version has an API level:
Android VersionAPI LevelRelease Year
Android 15352024
Android 14342023
Android 13332022
Android 12L322022
Android 12312021
Android 11302020
Android 10292019
Android 9 (Pie)282018
Android 8.1 (Oreo)272017
Android 8.0 (Oreo)262017
Android 7.1 (Nougat)252016
Android 7.0 (Nougat)242016
Android 6.0 (Marshmallow)232015
Android 5.1 (Lollipop)222015
Android 5.0 (Lollipop)212014
Your app’s minSdkVersion determines which APIs you can use. Features from higher API levels won’t be available on older devices.

Major API level milestones

Key bionic improvements by API level:
Major improvements:
  • 64-bit support (arm64-v8a, x86_64)
  • Significantly improved locale support via ICU
  • Full C11 threading support
  • Position-independent executables (PIE) required
// Now available in API 21+
#include <threads.h>  // C11 threads

thrd_t thread;
thrd_create(&thread, thread_func, arg);
API 21 is often considered the minimum for modern NDK development due to 64-bit and improved standards compliance.
Major changes:
  • Runtime permissions affect file access
  • Better FORTIFY_SOURCE protections
  • Enhanced DNS resolution
// Enhanced security checks
char buffer[10];
// This will abort at runtime if overflow detected:
strcpy(buffer, long_string);  // Caught by FORTIFY_SOURCE
Major changes:
  • Private API restrictions begin
  • Cannot use non-public symbols from platform libraries
  • File-based encryption affects file paths
Starting in API 24, directly linking against private platform libraries (like libandroid_runtime.so) is restricted. Use only public NDK APIs.
Major restrictions:
  • Strict enforcement of public API access
  • Gray-list restrictions on non-SDK interfaces
  • Enhanced stack protections
// API 28+ has stronger symbol restrictions
// Cannot dlopen() private libraries
void* handle = dlopen("libutils.so", RTLD_NOW);  // Will fail!
// Use only public NDK libraries
Features:
  • Neural Networks API 1.2
  • Scoped storage affects file access
  • APEX modularization (bionic can be updated independently)
#include <android/sharedmem.h>
// Enhanced shared memory API
int fd = ASharedMemory_create("myshm", size);
Ongoing improvements:
  • Continued security hardening
  • New standard library features
  • Performance optimizations
  • Regular bionic updates via APEX

Standard library support

Bionic supports most C and C++ standards:

C standard library

Fully supported (all API levels):
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>

// C99 features available
int64_t value = INT64_MAX;
bool flag = true;
printf("%" PRId64, value);

C++ standard library

Bionic works with libc++ (LLVM’s C++ standard library):
// C++17 features available with recent NDK
#include <filesystem>
#include <optional>
#include <variant>
#include <string_view>

std::optional<int> maybe_value = std::nullopt;
std::string_view sv = "hello";

// C++20 features (NDK r23+)
#include <span>
#include <ranges>
The NDK uses libc++ (LLVM’s C++ library), not GNU libstdc++. This is important for C++ ABI compatibility.

Common compatibility issues

Using conditionals for API availability

#include <android/api-level.h>

void platform_specific_code() {
    #if __ANDROID_API__ >= 28
        // Compiled in if building for API 28+
        use_api28_function();
    #else
        // Fallback for older API levels
        use_legacy_function();
    #endif
    
    // Runtime check
    if (android_get_device_api_level() >= 28) {
        // Runtime decision based on actual device
        use_newer_feature();
    }
}

Handling missing functions

// Provide fallback for functions not in older API levels
#include <dlfcn.h>

#if __ANDROID_API__ < 21
// Provide your own implementation
int my_missing_function() {
    // Fallback implementation
    return -1;
}
#else
// Use system implementation
extern int my_missing_function();
#endif

Weak linking for optional features

// Weak linking allows graceful degradation
__attribute__((weak)) int optional_function(int arg);

void use_optional_feature() {
    if (optional_function != NULL) {
        optional_function(42);
    } else {
        // Function not available, use alternative
        fallback_implementation();
    }
}

Best practices

Set appropriate minSdkVersion

// build.gradle
android {
    defaultConfig {
        // Choose based on required features and target market
        minSdkVersion 21  // Recommended minimum for 2024
        targetSdkVersion 34
    }
}
Recommended minSdkVersion: API 21 (Android 5.0)
  • Covers 99%+ of active devices (as of 2024)
  • 64-bit support
  • Better standards compliance
  • Modern security features

Check bionic status documentation

For definitive API availability, consult:

Use feature detection

// Better than hardcoded version checks
#include <unistd.h>

if (sysconf(_SC_NPROCESSORS_ONLN) > 0) {
    // Feature available
} else {
    // Not available or failed
}

Avoid private APIs

Never use symbols not in the public NDK API. They may:
  • Disappear in future Android versions
  • Behave differently across devices
  • Cause your app to be rejected or break
// BAD - private API usage
// void* handle = dlopen("libutils.so", RTLD_NOW);

// GOOD - use public NDK APIs only
#include <android/log.h>
__android_log_print(ANDROID_LOG_INFO, "TAG", "Message");

Testing across API levels

Ensure compatibility by testing on multiple Android versions:
# Create emulators for different API levels
avdmanager create avd -n api21 -k "system-images;android-21;default;x86_64"
avdmanager create avd -n api28 -k "system-images;android-28;default;x86_64"
avdmanager create avd -n api34 -k "system-images;android-34;default;x86_64"

# Run tests on each
adb -e shell am instrument -w com.example.app.test/androidx.test.runner.AndroidJUnitRunner

Resources

For more information on bionic:

Next steps

When in doubt about API availability, compile with the lowest minSdkVersion you support and test on actual devices running that Android version.

Build docs developers (and LLMs) love