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, lifecycle и системные сервисы остаются на 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 для lifecycle 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/       // CMake toolchain file

Нативные API Android

Через NDK доступны следующие группы нативных API: Native App Glue (управление lifecycle 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-bitПочти все современные телефоны
armeabi-v7aARMv7-A32-bitСтарые устройства (до 2020)
x86_64x86-6464-bitЭмулятор, Chromebook
x86x86 IA-3232-bitУстаревшие эмуляторы

Примеры нативного кода на 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 — отладчик нативного кода. Можно ставить breakpoints в 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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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