NDK (Native Development Kit) — це інструментарій для написання частини застосунку на C та C++ під Android. На відміну від звичайного Android SDK, NDK компілює код у нативні бібліотеки (.so), які працюють безпосередньо з процесором без прошарку віртуальної машини. Згідно з Google NDK Guides, 2026, NDK використовують для високопродуктивних обчислень, ігор, обробки аудіо та повторного використання існуючих C/C++ проєктів. JNI (Java Native Interface) пов'язує нативний код з Kotlin та Java.
Головне
NDK (Native Development Kit) — це набір інструментів для кросс-компіляції C та C++ коду в виконувані бібліотеки для Android. До складу NDK входять компілятор Clang, стандартна бібліотека libc++, заголовні файли Android API та утиліти збірки. Розробник пише нативний код на C або C++, компілює його в .so-файли (shared objects) і підключає до застосунку через JNI.
NDK не замінює Android SDK, а доповнює його. Весь UI, життєвий цикл та системні сервіси залишаються на Kotlin або Java. Нативний код вирішує вузькі задачі: математичні обчислення, криптографія, кодеки, фізика в іграх. Google рекомендує використовувати NDK лише тоді, коли SDK не забезпечує потрібної продуктивності або доступу до апаратних можливостей.
Історія NDK починається з 2009 року (NDK r1). За цей час інструментарій пройшов шлях від експериментального набору скриптів до зрілої системи з підтримкою CMake, LLDB-налагодження та профілювання. На 2026 рік актуальна версія — NDK r27 з Clang 19, повною підтримкою C++20 та libc++ як єдиною стандартною бібліотекою.
NDK дає розробнику доступ до низькорівневих можливостей Android через нативні API. Основні сценарії використання включають: робота з OpenGL ES та Vulkan для 3D-графіки, обробка аудіо через Oboe, криптографічні операції через BoringSSL та NEON-оптимізації для ARM. Всі ці API доступні з C/C++ та потребують NDK для компіляції.
Крім того, NDK дозволяє повторно використовувати існуючі C/C++ бібліотеки без переписування на Kotlin. Це особливо актуально для проєктів з багаторічною історією на C++ — ігрових рушіїв (Unity, Unreal Engine), бібліотек комп'ютерного зору (OpenCV) або криптографічних пакетів (OpenSSL). У таких випадках NDK економить роки розробки.
| Компонент | Призначення |
|---|---|
| Clang | Компілятор C/C++ з підтримкою C++20 |
| libc++ | Стандартна бібліотека C++ (єдина в NDK r27) |
| CMake | Система збірки нативних бібліотек |
| LLDB | Налагоджувач нативного коду в Android Studio |
| ndk-build | Застаріла система збірки (замінена CMake) |
Вибір між NDK та чистим SDK залежить від задачі. Android SDK надає готові Java/Kotlin API для 90% сценаріїв — робота з мережею, файлами, сповіщеннями, камерою. NDK підключають, коли ці API не забезпечують потрібної продуктивності або функціональності. Наприклад, обробка аудіо в реальному часі через SDK можлива, але із затримками 50–200 мс, тоді як нативна бібліотека через Oboe знижує затримку до 5–15 мс.
Розмір APK — ще один фактор. Додавання NDK збільшує розмір застосунку, оскільки кожна ABI-архітектура потребує окремої .so-бібліотеки. Однак використання Android App Bundle (AAB) пом'якшує проблему: Google Play доставляє користувачу лише бібліотеку під його архітектуру. Типовий нативний проєкт додає 1–10 МБ до розміру встановлення на пристрій.
| Критерій | SDK (Kotlin/Java) | NDK (C/C++) |
|---|---|---|
| Продуктивність | Середня (JIT/AOT компіляція) | Висока (машинний код) |
| Затримка аудіо | 50–200 мс | 5–15 мс через Oboe |
| 3D-графіка | Через Canvas/OpenGL Kotlin API | Vulkan/OpenGL напряму |
| Складність розробки | Низька | Висока (керування пам'яттю, JNI) |
| Переносимість коду | Тільки Android | Linux, Windows, macOS, iOS |
| Розмір APK | Мінімальний | +1–10 МБ на бібліотеку |
NDK встановлюється окремо від Android SDK через SDK Manager. Всередині каталогу NDK знаходяться toolchain (компілятори), платформові бібліотеки, заголовні файли та утиліти. Toolchain NDK включає Clang для кросс-компіляції під всі цільові ABI: arm64-v8a, armeabi-v7a, x86_64 та x86. Компілятор автоматично вибирає потрібну архітектуру на основі прапорців CMake або ndk-build.
Android API для нативного коду представлені заголовними файлами в каталозі sysroot/usr/include. Там знаходяться оголошення для всіх API, доступних у нативному коді: native_activity.h для життєвого циклу Activity, input.h для подій введення, sensor.h для датчиків. Підключення цих заголовків дає доступ до апаратних можливостей без JNI-прошарку.
ndk/
toolchains/
llvm/prebuilt/windows-x86_64/
bin/ // Clang, ld, llvm-profdata
sysroot/ // Заголовні файли Android API
platforms/
android-26/ // libc, libm, libdl для API 26
android-34/
sources/
cxx-stl/ // Заголовки libc++
build/
cmake/ // Файл toolchain CMake
Через NDK доступні наступні групи нативних API: Native App Glue (керування життєвим циклом Activity з C), OpenGL ES 3.2 та Vulkan 1.3 (графіка), Oboe (низькозатримне аудіо), Neural Networks API (машинне навчання на пристрої). Кожне API має заголовний файл та статичну/динамічну бібліотеку у складі NDK.
Для роботи з нативними API не потрібен JNI — функції викликаються напряму з C/C++ коду. Однак для взаємодії з UI на Kotlin все одно потрібен JNI. Така гібридна архітектура зустрічається в ігрових рушіях: графіка та фізика на C++ (через Vulkan), меню та UI на Kotlin (через Jetpack Compose).
JNI (Java Native Interface) — стандартний механізм виклику нативного коду з віртуальної машини Java/Kotlin. Розробник оголошує в Kotlin зовнішню функцію з ключовим словом external та підключає .so-бібліотеку через System.loadLibrary. На стороні C++ функція оголошується з використанням угоди імен JNI, яка кодує пакет та ім'я класу.
class NativeBridge {
companion object {
init {
System.loadLibrary("native-lib")
}
}
external fun stringFromJNI(): String
external fun fibonacci(n: Int): Long
external fun processBuffer(data: ByteArray): ByteArray
}
// Використання в коді
val bridge = NativeBridge()
println(bridge.stringFromJNI()) // "Hello from C++"
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_app_NativeBridge_stringFromJNI(
JNIEnv* env, jobject /* this */) {
std::string hello = "Hello from C++ NDK";
return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT jlong JNICALL
Java_com_example_app_NativeBridge_fibonacci(
JNIEnv* env, jobject /* this */, jint n) {
if (n <= 1) return n;
jlong a = 0, b = 1;
for (int i = 2; i <= n; i++) {
jlong temp = a + b;
a = b;
b = temp;
}
return b;
}
CMake — основна система збірки для NDK, яка витіснила застарілий ndk-build. Файл CMakeLists.txt описує вихідні файли, бібліотеки та прапорці компіляції. Android Studio автоматично викликає CMake при збірці проєкту, якщо налаштовано externalNativeBuild в build.gradle. CMake генерує make-файли для кожної цільової ABI та компілює нативний код паралельно.
cmake_minimum_required(VERSION 3.22.1)
project("nativelib")
# Підключення заголовних файлів
include_directories(src/main/cpp/include)
# Створення нативної бібліотеки
add_library(
native-lib
SHARED
src/main/cpp/native-lib.cpp
src/main/cpp/math_utils.cpp
src/main/cpp/audio_processor.cpp
)
# Підключення системних бібліотек Android
target_link_libraries(
native-lib
android
log
OpenSLES
# libc++ підключається автоматично
)
# Прапорці оптимізації
target_compile_options(native-lib PRIVATE -O3 -Wall -Wextra)
android {
defaultConfig {
ndk {
// Цільові ABI для збірки
abiFilters "arm64-v8a", "armeabi-v7a", "x86_64"
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
version "3.22.1"
}
}
buildTypes {
release {
externalNativeBuild {
cmake {
arguments "-DCMAKE_BUILD_TYPE=Release"
}
}
}
}
}
ABI (Application Binary Interface) — це формат машинного коду, який визначає сумісність з процесором пристрою. Кожна архітектура ARM або x86 має свій ABI. NDK компілює нативний код окремо для кожного вказаного ABI. Найпоширеніші ABI на 2026 рік: arm64-v8a (99% сучасних пристроїв), armeabi-v7a (старі 32-бітні пристрої), x86_64 (емулятор та Chromebook).
Вказання abiFilters в build.gradle обмежує збірку тільки потрібними архітектурами, що скорочує час компіляції. Для Google Play рекомендується включати всі ABI, з якими сумісна бібліотека — це гарантує роботу на всіх пристроях. Google Play Console дозволяє налаштувати доставку ABI-специфічних APK через App Bundle.
#include <jni.h>
#include <android/api-level.h>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_app_NativeBridge_getABIInfo(
JNIEnv* env, jobject /* this */) {
#if defined(__arm__)
#if defined(__ARM_ARCH_7A__)
return env->NewStringUTF("armeabi-v7a");
#endif
#elif defined(__aarch64__)
return env->NewStringUTF("arm64-v8a");
#elif defined(__x86_64__)
return env->NewStringUTF("x86_64");
#elif defined(__i386__)
return env->NewStringUTF("x86");
#endif
return env->NewStringUTF("unknown");
}
| ABI | Архітектура | Розрядність | Пристрої |
|---|---|---|---|
| arm64-v8a | ARMv8-A | 64-біт | Майже всі сучасні телефони |
| armeabi-v7a | ARMv7-A | 32-біт | Старі пристрої (до 2020) |
| x86_64 | x86-64 | 64-біт | Емулятор, Chromebook |
| x86 | x86 IA-32 | 32-біт | Застарілі емулятори |
Розглянемо практичний приклад: математична бібліотека для роботи з числами з плаваючою точкою. Нативний код на C++ виконує обчислення ефективніше, ніж Kotlin, завдяки прямому доступу до NEON-інструкцій ARM та відсутності перевірок меж масивів у runtime. Цей приклад демонструє типовий патерн використання NDK — винос важких обчислень у нативний шар.
#include <jni.h>
#include <cmath>
#include <vector>
extern "C" JNIEXPORT jfloatArray JNICALL
Java_com_example_app_NativeBridge_normalizeArray(
JNIEnv* env, jobject, jfloatArray input) {
jsize len = env->GetArrayLength(input);
jfloat* elements = env->GetFloatArrayElements(input, nullptr);
// Обчислюємо середнє та стандартне відхилення
float sum = 0.0f, sumSq = 0.0f;
for (jsize i = 0; i < len; i++) {
sum += elements[i];
sumSq += elements[i] * elements[i];
}
float mean = sum / len;
float stddev = std::sqrt(sumSq / len - mean * mean);
// Нормалізація: (x - mean) / stddev
jfloat* result = new jfloat[len];
for (jsize i = 0; i < len; i++) {
result[i] = (elements[i] - mean) / stddev;
}
env->ReleaseFloatArrayElements(input, elements, JNI_ABORT);
jfloatArray output = env->NewFloatArray(len);
env->SetFloatArrayRegion(output, 0, len, result);
delete[] result;
return output;
}
Для налагодження нативного коду використовуйте макрос __android_log_print з бібліотеки android/log.h. Повідомлення з'являються в Logcat поряд з Java/Kotlin логами. Рівень логування (ANDROID_LOG_DEBUG, ANDROID_LOG_ERROR) допомагає фільтрувати повідомлення.
#include <android/log.h>
#define LOG_TAG "NativeLib"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
extern "C" JNIEXPORT void JNICALL
Java_com_example_app_NativeBridge_processData(
JNIEnv* env, jobject, jint count) {
LOGD("Processing %d items", count);
for (int i = 0; i < count; i++) {
// Важка обробка
LOGD("Item %d processed", i);
}
LOGD("Processing complete");
}
Часті запитання
Ні. Для більшості застосунків достатньо SDK на Kotlin. NDK потрібен для високопродуктивних задач: обробка аудіо/відео в реальному часі, 3D-графіка, криптографія або повторне використання існуючих C/C++ проєктів.
NDK використовує Clang з LLVM toolchain. Починаючи з NDK r23, GCC повністю видалено. Clang кросс-компілює код для всіх Android ABI: arm64-v8a, armeabi-v7a, x86_64, x86.
ABI (Application Binary Interface) — формат машинного коду, що визначає сумісність з процесором. NDK збирає .so-бібліотеки під кожен ABI окремо. arm64-v8a — основний ABI для сучасних Android-пристроїв.
Так. Android Studio підтримує LLDB — налагоджувач нативного коду. Можна ставити точки зупину в C++ файлах, дивитися змінні та стек викликів. Для роботи потрібні NDK та плагін LLDB.
Кожна нативна бібліотека додає 100 КБ — кілька МБ в APK. Для кожної ABI потрібна окрема .so-бібліотека. Android App Bundle доставляє користувачу тільки відповідну архітектуру, зменшуючи розмір встановлення.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також