NDK Android: co to jest, Native Development Kit i JNI dla C++

Autor: IT Sectr Opublikowano: 2026-02-09 Czas czytania: 10 min

NDK (Native Development Kit) — to zestaw narzędzi do pisania części aplikacji w C i C++ na Androida. W przeciwieństwie do zwykłego Android SDK, NDK kompiluje kod do natywnych bibliotek (.so), które działają bezpośrednio na procesorze bez pośrednictwa maszyny wirtualnej. Według Google NDK Guides, 2026, NDK jest używany do obliczeń wysokowydajnych, gier, przetwarzania dźwięku i ponownego wykorzystania istniejących projektów C/C++. JNI (Java Native Interface) łączy kod natywny z Kotlin i Java.

Najważniejsze

  • NDK — zestaw narzędzi do kompilacji kodu C/C++ do natywnych bibliotek na Androida.
  • JNI — interfejs komunikacji między maszyną wirtualną Java/Kotlin a kodem natywnym.
  • CMake — główny system budowania dla projektów NDK, konfigurowany przez CMakeLists.txt.
  • ABI — docelowa architektura procesora (arm64-v8a, armeabi-v7a, x86_64), dla której budowana jest biblioteka.
  • NDK nie jest potrzebny dla większości aplikacji Android i jest stosowany tylko w zadaniach o wysokich wymaganiach wydajnościowych.

Czym jest NDK

NDK (Native Development Kit) — to zestaw narzędzi do krzyżowej kompilacji kodu C i C++ do wykonywalnych bibliotek dla Androida. W skład NDK wchodzą kompilator Clang, standardowa biblioteka libc++, pliki nagłówkowe Android API i narzędzia budowania. Deweloper pisze kod natywny w C lub C++, kompiluje go do plików .so (shared objects) i podłącza do aplikacji przez JNI.

NDK nie zastępuje Android SDK, ale go uzupełnia. Cały interfejs użytkownika, lifecycle i usługi systemowe pozostają w Kotlin lub Java. Kod natywny rozwiązuje wąskie zadania: obliczenia matematyczne, kryptografia, kodeki, fizyka w grach. Google zaleca używanie NDK tylko wtedy, gdy SDK nie zapewnia wymaganej wydajności lub dostępu do możliwości sprzętowych.

Historia NDK zaczyna się w 2009 roku (NDK r1). W tym czasie narzędzia przeszły drogę od eksperymentalnego zestawu skryptów do dojrzałego systemu z obsługą CMake, debugowania LLDB i profilowania. Na 2026 rok aktualna wersja to NDK r27 z Clang 19, pełną obsługą C++20 i libc++ jako jedyną standardową biblioteką.

Kluczowe możliwości NDK

NDK daje deweloperowi dostęp do niskopoziomowych możliwości Androida przez natywne API. Główne scenariusze użycia obejmują: pracę z OpenGL ES i Vulkan dla grafiki 3D, przetwarzanie dźwięku przez Oboe, operacje kryptograficzne przez BoringSSL i optymalizacje NEON dla ARM. Wszystkie te API są dostępne z C/C++ i wymagają NDK do kompilacji.

Ponadto NDK pozwala ponownie wykorzystać istniejące biblioteki C/C++ bez przepisywania na Kotlin. Jest to szczególnie istotne w projektach z wieloletnią historią w C++ — silnikach gier (Unity, Unreal Engine), bibliotekach widzenia komputerowego (OpenCV) lub pakietach kryptograficznych (OpenSSL). W takich przypadkach NDK oszczędza lata rozwoju.

KomponentPrzeznaczenie
ClangKompilator C/C++ z obsługą C++20
libc++Standardowa biblioteka C++ (jedyna w NDK r27)
CMakeSystem budowania bibliotek natywnych
LLDBDebuger kodu natywnego w Android Studio
ndk-buildPrzestarzały system budowania (zastąpiony przez CMake)

NDK vs SDK: kiedy potrzebny jest kod natywny

Wybór między NDK a czystym SDK zależy od zadania. Android SDK zapewnia gotowe API Java/Kotlin dla 90% scenariuszy — praca z siecią, plikami, powiadomieniami, kamerą. NDK jest podłączany, gdy te API nie zapewniają wymaganej wydajności lub funkcjonalności. Na przykład przetwarzanie dźwięku w czasie rzeczywistym przez SDK jest możliwe, ale z opóźnieniami 50–200 ms, podczas gdy natywna biblioteka przez Oboe redukuje opóźnienie do 5–15 ms.

Rozmiar APK — kolejny czynnik. Dodanie NDK zwiększa rozmiar aplikacji, ponieważ każda architektura ABI wymaga osobnej biblioteki .so. Jednak użycie Android App Bundle (AAB) łagodzi problem: Google Play dostarcza użytkownikowi tylko bibliotekę dla jego architektury. Typowy projekt natywny dodaje 1–10 MB do rozmiaru instalacji na urządzeniu.

Porównanie SDK i NDK według kryteriów

KryteriumSDK (Kotlin/Java)NDK (C/C++)
WydajnośćŚrednia (kompilacja JIT/AOT)Wysoka (kod maszynowy)
Opóźnienie dźwięku50–200 ms5–15 ms przez Oboe
Grafika 3DPrzez Canvas/OpenGL Kotlin APIVulkan/OpenGL bezpośrednio
Złożoność rozwojuNiskaWysoka (zarządzanie pamięcią, JNI)
Przenośność koduTylko AndroidLinux, Windows, macOS, iOS
Rozmiar APKMinimalny+1–10 MB na bibliotekę

Architektura NDK: narzędzia i biblioteki

NDK instaluje się oddzielnie od Android SDK przez SDK Manager. Wewnątrz katalogu NDK znajdują się toolchain (kompilatory), biblioteki platformowe, pliki nagłówkowe i narzędzia. Toolchain NDK zawiera Clang do krzyżowej kompilacji dla wszystkich docelowych ABI: arm64-v8a, armeabi-v7a, x86_64 i x86. Kompilator automatycznie wybiera odpowiednią architekturę na podstawie flag CMake lub ndk-build.

Android API dla kodu natywnego są reprezentowane przez pliki nagłówkowe w katalogu sysroot/usr/include. Znajdują się tam deklaracje dla wszystkich API dostępnych w kodzie natywnym: native_activity.h dla lifecycle Activity, input.h dla zdarzeń wejścia, sensor.h dla czujników. Podłączenie tych nagłówków daje dostęp do możliwości sprzętowych bez pośrednictwa JNI.

Struktura katalogów NDK

text
ndk/
  toolchains/
    llvm/prebuilt/windows-x86_64/
      bin/        // Clang, ld, llvm-profdata
      sysroot/    // Pliki nagłówkowe Android API
  platforms/
    android-26/  // libc, libm, libdl dla API 26
    android-34/
  sources/
    cxx-stl/     // Nagłówki libc++
  build/
    cmake/       // Plik toolchain CMake

Natywne API Androida

Przez NDK dostępne są następujące grupy natywnych API: Native App Glue (zarządzanie lifecycle Activity z C), OpenGL ES 3.2 i Vulkan 1.3 (grafika), Oboe (dźwięk o niskim opóźnieniu), Neural Networks API (uczenie maszynowe na urządzeniu). Każde API ma plik nagłówkowy i bibliotekę statyczną/dynamiczną w składzie NDK.

Do pracy z natywnymi API nie jest potrzebny JNI — funkcje są wywoływane bezpośrednio z kodu C/C++. Jednak do interakcji z UI w Kotlin nadal wymagany jest JNI. Taka hybrydowa architektura występuje w silnikach gier: grafika i fizyka w C++ (przez Vulkan), menu i UI w Kotlin (przez Jetpack Compose).

JNI: jak Kotlin wywołuje C++

JNI (Java Native Interface) — standardowy mechanizm wywoływania kodu natywnego z maszyny wirtualnej Java/Kotlin. Deweloper deklaruje w Kotlin zewnętrzną funkcję ze słowem kluczowym external i podłącza bibliotekę .so przez System.loadLibrary. Po stronie C++ funkcja jest deklarowana z użyciem konwencji nazewnictwa JNI, która koduje pakiet i nazwę klasy.

Deklaracja funkcji natywnej w 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
}

// Użycie w kodzie
val bridge = NativeBridge()
println(bridge.stringFromJNI())  // "Hello from C++"

Implementacja funkcji JNI w 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;
}

Konfiguracja CMakeLists.txt dla NDK

CMake — główny system budowania dla NDK, który zastąpił przestarzały ndk-build. Plik CMakeLists.txt opisuje pliki źródłowe, biblioteki i flagi kompilacji. Android Studio automatycznie wywołuje CMake podczas budowania projektu, jeśli skonfigurowano externalNativeBuild w build.gradle. CMake generuje pliki make dla każdego docelowego ABI i kompiluje kod natywny równolegle.

Przykład CMakeLists.txt dla biblioteki natywnej

cmake
cmake_minimum_required(VERSION 3.22.1)
project("nativelib")

# Dołączanie plików nagłówkowych
include_directories(src/main/cpp/include)

# Tworzenie biblioteki natywnej
add_library(
    native-lib
    SHARED
    src/main/cpp/native-lib.cpp
    src/main/cpp/math_utils.cpp
    src/main/cpp/audio_processor.cpp
)

# Dołączanie bibliotek systemowych Androida
target_link_libraries(
    native-lib
    android
    log
    OpenSLES
    # libc++ dołącza się automatycznie
)

# Flagi optymalizacji
target_compile_options(native-lib PRIVATE -O3 -Wall -Wextra)

Konfiguracja build.gradle dla NDK

groovy
android {
    defaultConfig {
        ndk {
            // Docelowe ABI do budowania
            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 i budowa wieloplatformowa

ABI (Application Binary Interface) — to format kodu maszynowego, który określa zgodność z procesorem urządzenia. Każda architektura ARM lub x86 ma własne ABI. NDK kompiluje kod natywny osobno dla każdego określonego ABI. Najbardziej rozpowszechnione ABI na 2026 rok: arm64-v8a (99% nowoczesnych urządzeń), armeabi-v7a (stare urządzenia 32-bitowe), x86_64 (emulator i Chromebook).

Określenie abiFilters w build.gradle ogranicza budowanie tylko do potrzebnych architektur, co skraca czas kompilacji. Dla Google Play zaleca się dołączanie wszystkich ABI, z którymi biblioteka jest zgodna — gwarantuje to działanie na wszystkich urządzeniach. Google Play Console pozwala skonfigurować dostarczanie ABI-specyficznych APK przez App Bundle.

Określenie ABI urządzenia w kodzie natywnym

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");
}
ABIArchitekturaRozmiar słowaUrządzenia
arm64-v8aARMv8-A64-bitPrawie wszystkie nowoczesne telefony
armeabi-v7aARMv7-A32-bitStare urządzenia (przed 2020)
x86_64x86-6464-bitEmulator, Chromebook
x86x86 IA-3232-bitPrzestarzałe emulatory

Przykłady kodu natywnego w C++

Rozważmy praktyczny przykład: biblioteka matematyczna do pracy z liczbami zmiennoprzecinkowymi. Kod natywny w C++ wykonuje obliczenia wydajniej niż Kotlin, dzięki bezpośredniemu dostępowi do instrukcji NEON ARM i braku sprawdzania granic tablic w runtime. Ten przykład demonstruje typowy wzorzec użycia NDK — przeniesienie ciężkich obliczeń do warstwy natywnej.

Operacje matematyczne w kodzie natywnym

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

    // Obliczamy średnią i odchylenie standardowe
    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);

    // Normalizacja: (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;
}

Logowanie z kodu natywnego

Do debugowania kodu natywnego użyj makra __android_log_print z biblioteki android/log.h. Komunikaty pojawiają się w Logcat obok logów Java/Kotlin. Poziom logowania (ANDROID_LOG_DEBUG, ANDROID_LOG_ERROR) pomaga filtrować komunikaty.

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++) {
        // Ciężkie przetwarzanie
        LOGD("Item %d processed", i);
    }

    LOGD("Processing complete");
}

Często zadawane pytania

Czy używanie NDK jest obowiązkowe do tworzenia aplikacji na Androida?

Nie. Dla większości aplikacji wystarczy SDK w Kotlin. NDK jest potrzebny do zadań wymagających wysokiej wydajności: przetwarzanie dźwięku/wideo w czasie rzeczywistym, grafika 3D, kryptografia lub ponowne wykorzystanie istniejących projektów C/C++.

Jakiego kompilatora używa NDK?

NDK używa Clang z LLVM toolchain. Od NDK r23 GCC zostało całkowicie usunięte. Clang krzyżowo kompiluje kod dla wszystkich Android ABI: arm64-v8a, armeabi-v7a, x86_64, x86.

Czym jest ABI w kontekście NDK?

ABI (Application Binary Interface) — format kodu maszynowego określający zgodność z procesorem. NDK buduje biblioteki .so dla każdego ABI osobno. arm64-v8a — główne ABI dla nowoczesnych urządzeń Android.

Czy można debugować kod C++ przez NDK?

Tak. Android Studio obsługuje LLDB — debuger kodu natywnego. Można ustawiać breakpointy w plikach C++, przeglądać zmienne i stos wywołań. Do pracy wymagany jest NDK i plugin LLDB.

Jak NDK wpływa na rozmiar APK?

Każda natywna biblioteka dodaje 100 KB — kilka MB do APK. Dla każdego ABI potrzebna jest osobna biblioteka .so. Android App Bundle dostarcza użytkownikowi tylko odpowiednią architekturę, zmniejszając rozmiar instalacji.

Podsumowanie

  • NDK — zestaw narzędzi do krzyżowej kompilacji kodu C/C++ do natywnych bibliotek na Androida przez kompilator Clang.
  • JNI — interfejs łączący Kotlin/Java z kodem natywnym przez słowo kluczowe external i konwencję nazewnictwa funkcji.
  • CMake — główny system budowania NDK, konfigurowany przez plik CMakeLists.txt i externalNativeBuild w Gradle.
  • ABI określa architekturę procesora — arm64-v8a, armeabi-v7a, x86_64. Każde ABI wymaga osobnego budowania biblioteki.
  • Używaj NDK tylko do zadań wymagających wysokiej wydajności: gry, dźwięk, grafika, kryptografia, uczenie maszynowe na urządzeniu.
  • Google Play obsługuje podział ABI przez App Bundle: użytkownik otrzymuje tylko bibliotekę dla architektury swojego urządzenia.
  • Biblioteki natywne znacznie zwiększają APK, ale zapewniają maksymalną wydajność i niskie opóźnienia dla krytycznych operacji.

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również