Android NDK — Native Library Build Setup
Practices for integrating C/C++ native code into Android projects using the Android NDK, targeting Android Automotive OS (AAOS) on ARM/AArch64 devices.
When to Use This Skill
- Adding a new native (C/C++) library to an Android Studio (Gradle) project.
- Adding a native shared library to an AOSP module via
Android.bp. - Choosing the right ABI filters, API level, and C++ STL.
- Debugging "library not found" or "unsatisfied link error" at runtime.
- Setting up CMake options inside Gradle
externalNativeBuild.
Key Concepts
| Concept | Description |
|---|---|
| ABI | CPU architecture variant: arm64-v8a, armeabi-v7a, x86_64, x86 |
| API level | Minimum Android API the native code targets (controls which NDK APIs are available) |
| STL | C++ standard library: c++_shared (most common), c++_static, none, system |
| ndk-build | Legacy build system (Android.mk). Use CMake for new projects. |
| CMake | Preferred build system for NDK native code since NDK r14 |
Gradle + CMake Integration (Android Studio)
build.gradle.kts (module level)
android {
defaultConfig {
externalNativeBuild {
cmake {
cppFlags += "-std=c++17"
arguments += listOf(
"-DANDROID_STL=c++_shared",
"-DANDROID_ARM_NEON=TRUE"
)
// ABI filters — restrict to what AAOS hardware supports
abiFilters += listOf("arm64-v8a", "armeabi-v7a")
}
}
}
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
}
CMakeLists.txt for Android NDK
cmake_minimum_required(VERSION 3.22)
project(MyNativeLib VERSION 1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Build a shared library that Gradle packages into the APK
add_library(my_native_lib SHARED
src/native_lib.cpp
src/audio_processor.cpp
)
# Find NDK-provided system libraries
find_library(log-lib log)
find_library(android-lib android)
target_link_libraries(my_native_lib
PRIVATE
${log-lib}
${android-lib}
)
target_include_directories(my_native_lib
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
)
Loading the library from Kotlin/Java
// In a companion object or the class that calls native methods
companion object {
init {
System.loadLibrary("my_native_lib")
}
}
// Declare native methods
external fun processAudio(samples: ShortArray): Int
AOSP Soong — Android.bp
cc_library_shared {
name: "libmy_native",
srcs: [
"src/native_lib.cpp",
"src/audio_processor.cpp",
],
export_include_dirs: ["include"],
shared_libs: [
"liblog",
"libutils",
"libbinder_ndk",
],
cflags: ["-Wall", "-Wextra", "-Werror"],
cppflags: ["-std=c++17"],
// Target only 64-bit on AAOS production hardware
compile_multilib: "64",
}
// Prebuilt .so for distribution
cc_prebuilt_library_shared {
name: "libvendor_sensor",
srcs: ["prebuilt/arm64-v8a/libvendor_sensor.so"],
export_include_dirs: ["include"],
compile_multilib: "64",
}
ABI Selection
| ABI | Architecture | Notes |
|---|---|---|
arm64-v8a |
64-bit ARMv8 | Required for all Android 11+ 64-bit-only targets (most AAOS HW) |
armeabi-v7a |
32-bit ARMv7 | Legacy; needed if app runs 32-bit processes on 64-bit device |
x86_64 |
64-bit x86 | Android emulator / development VM |
x86 |
32-bit x86 | Android emulator legacy; rarely needed |
For AAOS production targets: filter to arm64-v8a only unless you explicitly need 32-bit support.
C++ STL Selection
| Option | When to Use |
|---|---|
c++_shared |
Multiple shared libraries in the same process — all share one STL instance (avoids ODR violations) |
c++_static |
Single shared library with no other NDK .so in the process |
none |
Pure C code, or you provide your own STL |
system |
Minimal C++ runtime; very limited — avoid for new code |
Rule: If you ship more than one .so that uses the C++ STL, use c++_shared for all of them. Mixing c++_static across multiple .so files causes undefined behavior.
Available NDK APIs (Selected)
The NDK exposes a stable subset of Android APIs directly to C/C++:
| Header | Provides |
|---|---|
<android/log.h> |
__android_log_print — logcat from native code |
<android/asset_manager.h> |
Access to assets/ in APK |
<android/native_window.h> |
Native window for OpenGL ES / Vulkan surface |
<android/sensor.h> |
Sensor events via ASensorManager |
<android/hardware_buffer.h> |
AHardwareBuffer — zero-copy shared GPU buffers (API 26+) |
<android/binder_ibinder.h> |
Binder IPC from native code (NDK Binder, API 29+) |
Check the NDK API reference for the API level each symbol requires.
Logging from Native Code
#include <android/log.h>
#define LOG_TAG "MyNativeLib"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
void processAudio(int sampleRate) {
LOGI("Processing audio at %d Hz", sampleRate);
}
Prerequisites
- Android Studio (Flamingo or newer) or AOSP build environment set up.
- Android SDK Platform-Tools installed (
adbon PATH). - Target device or emulator running Android 11+ (API 30+).
- For AOSP modules:
repotool, AOSP source synced,lunchtarget configured.
Step-by-Step Workflows
Step 1: Configure CMakeLists.txt
Define add_library(mylib SHARED ...) with source files; set the minimum NDK API level.
Step 2: Link to the Gradle build
Set externalNativeBuild.cmake.path "CMakeLists.txt" in your module's build.gradle.kts.
Step 3: Set ABI filters
Use abiFilters "arm64-v8a" for production; add "x86_64" if emulator support is needed.
Step 4: Load the library from Kotlin/Java
Call System.loadLibrary("mylib") in a companion object or static initializer block.
Step 5: Debug native crashes
Use ndk-stack to symbolicate tombstone logs; attach LLDB from Android Studio for live debug.
Troubleshooting
CANNOT LINK EXECUTABLE ... not found— the shared library.sois not in the APK'slib/<abi>/directory; checkabiFiltersand CMakeinstallrules.- Native crash without symbols — add
-gtoCMAKE_C_FLAGSin debug builds; runndk-stack -sym <symbol-dir> -dump <tombstone>. - STL ABI mismatch — all libraries in the same process must use the same STL (
c++_shared); never mixc++_staticwithc++_shared. - **cmake:
NDK_ROOTnot set** — setANDROID_NDKenvironment variable orandroid.ndkPathinlocal.properties`.
Pre-Commit Checklist
-
abiFilterscontains only the ABIs the target hardware needs. -
ANDROID_STL=c++_sharedif multiple native.sofiles are loaded in the same process. -
cmake_minimum_requiredversion matches the version specified inbuild.gradle. - NDK API calls checked against the minimum
minSdk/APP_PLATFORMof the project. -
find_libraryused for system libs — no hardcoded lib paths. -
LOCAL_ARM_NEON := true(ndk-build) or-DANDROID_ARM_NEON=TRUE(CMake) only if code actually uses NEON intrinsics.