NDK Android: що це, Native Development Kit та JNI для C++

Автор: IT Sectr Опубліковано: 2026-02-09 Час читання: 10 хв

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 — інструментарій для компіляції C/C++ коду в нативні бібліотеки під Android.
  • JNI — інтерфейс взаємодії між віртуальною машиною Java/Kotlin та нативним кодом.
  • CMake — основна система збірки для NDK проєктів, налаштовувана через CMakeLists.txt.
  • ABI — цільова архітектура процесора (arm64-v8a, armeabi-v7a, x86_64), під яку збирається бібліотека.
  • NDK не потрібен для більшості Android-застосунків і застосовується лише в задачах з високими вимогами до продуктивності.

Що таке NDK

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

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 vs SDK: коли потрібен нативний код

Вибір між 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 та NDK за критеріями

КритерійSDK (Kotlin/Java)NDK (C/C++)
ПродуктивністьСередня (JIT/AOT компіляція)Висока (машинний код)
Затримка аудіо50–200 мс5–15 мс через Oboe
3D-графікаЧерез Canvas/OpenGL Kotlin APIVulkan/OpenGL напряму
Складність розробкиНизькаВисока (керування пам'яттю, JNI)
Переносимість кодуТільки AndroidLinux, Windows, macOS, iOS
Розмір APKМінімальний+1–10 МБ на бібліотеку

Архітектура NDK: інструменти та бібліотеки

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

text
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

Нативні API Android

Через 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: як Kotlin викликає C++

JNI (Java Native Interface) — стандартний механізм виклику нативного коду з віртуальної машини Java/Kotlin. Розробник оголошує в Kotlin зовнішню функцію з ключовим словом external та підключає .so-бібліотеку через System.loadLibrary. На стороні C++ функція оголошується з використанням угоди імен JNI, яка кодує пакет та ім'я класу.

Оголошення нативної функції в Kotlin

kotlin
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++"

Реалізація JNI-функції на C++

cpp
#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;
}

Налаштування CMakeLists.txt для NDK

CMake — основна система збірки для NDK, яка витіснила застарілий ndk-build. Файл CMakeLists.txt описує вихідні файли, бібліотеки та прапорці компіляції. Android Studio автоматично викликає CMake при збірці проєкту, якщо налаштовано externalNativeBuild в build.gradle. CMake генерує make-файли для кожної цільової ABI та компілює нативний код паралельно.

Приклад CMakeLists.txt для нативної бібліотеки

cmake
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)

Налаштування build.gradle для NDK

groovy
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 та багатоплатформова збірка

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.

Визначення ABI пристрою в нативному коді

cpp
#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-v8aARMv8-A64-бітМайже всі сучасні телефони
armeabi-v7aARMv7-A32-бітСтарі пристрої (до 2020)
x86_64x86-6464-бітЕмулятор, Chromebook
x86x86 IA-3232-бітЗастарілі емулятори

Приклади нативного коду на C++

Розглянемо практичний приклад: математична бібліотека для роботи з числами з плаваючою точкою. Нативний код на C++ виконує обчислення ефективніше, ніж Kotlin, завдяки прямому доступу до NEON-інструкцій ARM та відсутності перевірок меж масивів у runtime. Цей приклад демонструє типовий патерн використання NDK — винос важких обчислень у нативний шар.

Математичні операції в нативному коді

cpp
#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) допомагає фільтрувати повідомлення.

cpp
#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");
}

Часті запитання

Чи обов'язково використовувати NDK для Android-розробки?

Ні. Для більшості застосунків достатньо SDK на Kotlin. NDK потрібен для високопродуктивних задач: обробка аудіо/відео в реальному часі, 3D-графіка, криптографія або повторне використання існуючих C/C++ проєктів.

Який компілятор використовує NDK?

NDK використовує Clang з LLVM toolchain. Починаючи з NDK r23, GCC повністю видалено. Clang кросс-компілює код для всіх Android ABI: arm64-v8a, armeabi-v7a, x86_64, x86.

Що таке ABI в контексті NDK?

ABI (Application Binary Interface) — формат машинного коду, що визначає сумісність з процесором. NDK збирає .so-бібліотеки під кожен ABI окремо. arm64-v8a — основний ABI для сучасних Android-пристроїв.

Чи можна налагоджувати C++ код через NDK?

Так. Android Studio підтримує LLDB — налагоджувач нативного коду. Можна ставити точки зупину в C++ файлах, дивитися змінні та стек викликів. Для роботи потрібні NDK та плагін LLDB.

Як NDK впливає на розмір APK?

Кожна нативна бібліотека додає 100 КБ — кілька МБ в APK. Для кожної ABI потрібна окрема .so-бібліотека. Android App Bundle доставляє користувачу тільки відповідну архітектуру, зменшуючи розмір встановлення.

Підсумки

  • NDK — набір інструментів для кросс-компіляції C/C++ коду в нативні бібліотеки під Android через компілятор Clang.
  • JNI — інтерфейс, що пов'язує Kotlin/Java з нативним кодом через ключове слово external та угоду імен функцій.
  • CMake — основна система збірки NDK, налаштовувана через файл CMakeLists.txt та externalNativeBuild в Gradle.
  • ABI визначає архітектуру процесора — arm64-v8a, armeabi-v7a, x86_64. Кожна ABI потребує окремої збірки бібліотеки.
  • Використовуйте NDK лише для задач, що потребують високої продуктивності: ігри, аудіо, графіка, криптографія, машинне навчання на пристрої.
  • Google Play підтримує ABI-розділення через App Bundle: користувач отримує лише бібліотеку під архітектуру свого пристрою.
  • Нативні бібліотеки суттєво збільшують APK, але забезпечують максимальну продуктивність та низькі затримки для критичних операцій.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також