NDK Android: cos'è, Native Development Kit e JNI per C++

Autore: IT Sectr Pubblicato: 2026-02-09 Tempo di lettura: 10 min

NDK (Native Development Kit) è un toolkit per scrivere parti di un'applicazione in C e C++ per Android. A differenza del normale Android SDK, NDK compila il codice in librerie native (.so) che funzionano direttamente con il processore senza un livello di macchina virtuale. Secondo Google NDK Guides, 2026, NDK viene utilizzato per il calcolo ad alte prestazioni, giochi, elaborazione audio e riutilizzo di progetti C/C++ esistenti. JNI (Java Native Interface) collega il codice nativo con Kotlin e Java.

Punti chiave

  • NDK — un toolkit per compilare codice C/C++ in librerie native per Android.
  • JNI — un'interfaccia di interazione tra la macchina virtuale Java/Kotlin e il codice nativo.
  • CMake — il sistema di compilazione principale per i progetti NDK, configurabile tramite CMakeLists.txt.
  • ABI — l'architettura target del processore (arm64-v8a, armeabi-v7a, x86_64) per cui viene compilata la libreria.
  • NDK non è necessario per la maggior parte delle applicazioni Android e viene utilizzato solo in attività con requisiti di prestazioni elevati.

Cos'è NDK

NDK (Native Development Kit) è un insieme di strumenti per la compilazione incrociata di codice C e C++ in librerie eseguibili per Android. NDK include il compilatore Clang, la libreria standard libc++, i file di intestazione dell'API Android e utilità di compilazione. Lo sviluppatore scrive codice nativo in C o C++, lo compila in file .so (oggetti condivisi) e li collega all'applicazione tramite JNI.

NDK non sostituisce Android SDK, ma lo integra. Tutta l'interfaccia utente, il ciclo di vita e i servizi di sistema rimangono in Kotlin o Java. Il codice nativo risolve attività specifiche: calcoli matematici, crittografia, codec, fisica nei giochi. Google raccomanda di utilizzare NDK solo quando SDK non fornisce le prestazioni o l'accesso necessario alle capacità hardware.

La storia di NDK inizia nel 2009 (NDK r1). Durante questo periodo, il toolkit si è evoluto da un insieme sperimentale di script a un sistema maturo con supporto per CMake, debugging LLDB e profilazione. Nel 2026, la versione corrente è NDK r27 con Clang 19, supporto completo di C++20 e libc++ come unica libreria standard.

Capacità chiave di NDK

NDK dà allo sviluppatore accesso alle capacità di basso livello di Android tramite API native. I principali casi d'uso includono: lavoro con OpenGL ES e Vulkan per grafica 3D, elaborazione audio tramite Oboe, operazioni crittografiche attraverso BoringSSL e ottimizzazioni NEON per ARM. Tutte queste API sono disponibili da C/C++ e richiedono NDK per la compilazione.

Inoltre, NDK consente di riutilizzare librerie C/C++ esistenti senza riscriverle in Kotlin. Questo è particolarmente rilevante per progetti con una lunga storia in C++ — motori di gioco (Unity, Unreal Engine), librerie di visione artificiale (OpenCV) o pacchetti crittografici (OpenSSL). In questi casi, NDK risparmia anni di sviluppo.

ComponenteScopo
ClangCompilatore C/C++ con supporto C++20
libc++Libreria standard C++ (unica in NDK r27)
CMakeSistema di compilazione per librerie native
LLDBDebugger di codice nativo in Android Studio
ndk-buildSistema di compilazione legacy (sostituito da CMake)

NDK vs SDK: quando serve il codice nativo

La scelta tra NDK e SDK puro dipende dall'attività. Android SDK fornisce API Java/Kotlin pronte per il 90% degli scenari — rete, file, notifiche, fotocamera. NDK viene utilizzato quando queste API non forniscono le prestazioni o la funzionalità richieste. Ad esempio, l'elaborazione audio in tempo reale tramite SDK è possibile ma con una latenza di 50–200 ms, mentre una libreria nativa tramite Oboe riduce la latenza a 5–15 ms.

La dimensione dell'APK è un altro fattore. Aggiungere NDK aumenta la dimensione dell'applicazione poiché ogni architettura ABI richiede una libreria .so separata. Tuttavia, l'uso di Android App Bundle (AAB) attenua il problema: Google Play consegna all'utente solo la libreria per la sua architettura. Un progetto nativo tipico aggiunge 1–10 MB alla dimensione di installazione per dispositivo.

Confronto SDK e NDK per criteri

CriterioSDK (Kotlin/Java)NDK (C/C++)
PrestazioniMedie (compilazione JIT/AOT)Alte (codice macchina)
Latenza audio50–200 ms5–15 ms tramite Oboe
Grafica 3DTramite API Kotlin Canvas/OpenGLVulkan/OpenGL direttamente
Complessità di sviluppoBassaAlta (gestione memoria, JNI)
Portabilità del codiceSolo AndroidLinux, Windows, macOS, iOS
Dimensione APKMinima+1–10 MB per libreria

Architettura di NDK: strumenti e librerie

NDK viene installato separatamente da Android SDK tramite SDK Manager. All'interno della directory NDK si trovano il toolchain (compilatori), le librerie di piattaforma, i file di intestazione e le utilità. Il Toolchain NDK include Clang per la compilazione incrociata per tutte le ABI target: arm64-v8a, armeabi-v7a, x86_64 e x86. Il compilatore seleziona automaticamente l'architettura richiesta in base ai flag di CMake o ndk-build.

Le API Android per codice nativo sono fornite da file di intestazione nella directory sysroot/usr/include. Lì si trovano le dichiarazioni per tutte le API disponibili in codice nativo: native_activity.h per il ciclo di vita dell'Activity, input.h per gli eventi di input, sensor.h per i sensori. Includere queste intestazioni dà accesso alle capacità hardware senza un livello JNI.

Struttura delle directory NDK

text
ndk/
  toolchains/
    llvm/prebuilt/windows-x86_64/
      bin/        // Clang, ld, llvm-profdata
      sysroot/    // File di intestazione dell'API Android
  platforms/
    android-26/  // libc, libm, libdl per API 26
    android-34/
  sources/
    cxx-stl/     // Intestazioni libc++
  build/
    cmake/       // File toolchain CMake

API native Android

Attraverso NDK sono disponibili i seguenti gruppi di API native: Native App Glue (gestione del ciclo di vita dell'Activity da C), OpenGL ES 3.2 e Vulkan 1.3 (grafica), Oboe (audio a bassa latenza), Neural Networks API (apprendimento automatico sul dispositivo). Ogni API ha un file di intestazione e una libreria statica/dinamica come parte di NDK.

Lavorare con API native non richiede JNI — le funzioni vengono chiamate direttamente dal codice C/C++. Tuttavia, l'interazione con l'interfaccia utente in Kotlin richiede ancora JNI. Questa architettura ibrida è comune nei motori di gioco: grafica e fisica in C++ (tramite Vulkan), menu e interfaccia utente in Kotlin (tramite Jetpack Compose).

JNI: come Kotlin chiama C++

JNI (Java Native Interface) è un meccanismo standard per chiamare codice nativo dalla macchina virtuale Java/Kotlin. Lo sviluppatore dichiara una funzione esterna in Kotlin con la parola chiave external e carica la libreria .so tramite System.loadLibrary. Sul lato C++, la funzione viene dichiarata utilizzando la convenzione di denominazione JNI, che codifica il pacchetto e il nome della classe.

Dichiarazione di una funzione nativa in 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
}

// Utilizzo nel codice
val bridge = NativeBridge()
println(bridge.stringFromJNI())  // "Hello from C++"

Implementazione della funzione JNI in 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;
}

Configurazione di CMakeLists.txt per NDK

CMake è il sistema di compilazione principale per NDK, che ha sostituito il vecchio ndk-build. Il file CMakeLists.txt descrive i file sorgente, le librerie e i flag di compilazione. Android Studio invoca automaticamente CMake durante la compilazione del progetto se externalNativeBuild è configurato in build.gradle. CMake genera makefile per ogni ABI target e compila il codice nativo in parallelo.

Esempio di CMakeLists.txt per una libreria nativa

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

# Includere file di intestazione
include_directories(src/main/cpp/include)

# Creare libreria nativa
add_library(
    native-lib
    SHARED
    src/main/cpp/native-lib.cpp
    src/main/cpp/math_utils.cpp
    src/main/cpp/audio_processor.cpp
)

# Collegare librerie di sistema Android
target_link_libraries(
    native-lib
    android
    log
    OpenSLES
    # libc++ collegata automaticamente
)

# Flag di ottimizzazione
target_compile_options(native-lib PRIVATE -O3 -Wall -Wextra)

Configurazione di build.gradle per NDK

groovy
android {
    defaultConfig {
        ndk {
            // ABI target per la compilazione
            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 e compilazione multipiattaforma

ABI (Application Binary Interface) è il formato del codice macchina che determina la compatibilità con il processore del dispositivo. Ogni architettura ARM o x86 ha la propria ABI. NDK compila il codice nativo separatamente per ogni ABI specificata. Le ABI più comuni nel 2026: arm64-v8a (99% dei dispositivi moderni), armeabi-v7a (dispositivi più vecchi a 32 bit), x86_64 (emulatore e Chromebook).

Specificare abiFilters in build.gradle limita la compilazione solo alle architetture necessarie, riducendo i tempi di compilazione. Per Google Play, si consiglia di includere tutte le ABI supportate dalla libreria — questo garantisce il funzionamento su tutti i dispositivi. Google Play Console consente di configurare la consegna di APK specifici per ABI tramite App Bundle.

Determinazione dell'ABI del dispositivo in codice nativo

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");
}
ABIArchitetturaBitDispositivi
arm64-v8aARMv8-A64 bitQuasi tutti i telefoni moderni
armeabi-v7aARMv7-A32 bitDispositivi più vecchi (pre-2020)
x86_64x86-6464 bitEmulatore, Chromebook
x86x86 IA-3232 bitEmulatori legacy

Esempi di codice nativo in C++

Consideriamo un esempio pratico: una libreria matematica per lavorare con numeri a virgola mobile. Il codice nativo in C++ esegue calcoli in modo più efficiente di Kotlin, grazie all'accesso diretto alle istruzioni NEON di ARM e all'assenza di controlli dei limiti degli array in fase di esecuzione. Questo esempio dimostra un modello tipico di utilizzo di NDK — lo spostamento dei calcoli pesanti nel livello nativo.

Operazioni matematiche in codice nativo

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

    // Calcolare media e deviazione standard
    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);

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

Registrazione da codice nativo

Per eseguire il debug del codice nativo, utilizzare la macro __android_log_print della libreria android/log.h. I messaggi appaiono in Logcat insieme ai log Java/Kotlin. Il livello di registrazione (ANDROID_LOG_DEBUG, ANDROID_LOG_ERROR) aiuta a filtrare i messaggi.

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++) {
        // Elaborazione pesante
        LOGD("Item %d processed", i);
    }

    LOGD("Processing complete");
}

Domande frequenti

È obbligatorio usare NDK per lo sviluppo Android?

No. Per la maggior parte delle applicazioni, l'SDK Kotlin è sufficiente. NDK è necessario per attività ad alte prestazioni: elaborazione audio/video in tempo reale, grafica 3D, crittografia o riutilizzo di progetti C/C++ esistenti.

Quale compilatore usa NDK?

NDK usa Clang dal toolchain LLVM. Da NDK r23, GCC è stato completamente rimosso. Clang compila in modo incrociato per tutte le ABI Android: arm64-v8a, armeabi-v7a, x86_64, x86.

Cos'è ABI nel contesto di NDK?

ABI (Application Binary Interface) è il formato del codice macchina che determina la compatibilità con il processore. NDK compila librerie .so per ogni ABI separatamente. arm64-v8a è l'ABI principale per i dispositivi Android moderni.

Si può eseguire il debug di codice C++ tramite NDK?

Sì. Android Studio supporta LLDB — un debugger di codice nativo. È possibile impostare punti di interruzione nei file C++, visualizzare variabili e lo stack di chiamate. Sono richiesti NDK e il plugin LLDB.

Come influisce NDK sulla dimensione dell'APK?

Ogni libreria nativa aggiunge da 100 KB a diversi MB all'APK. Ogni ABI richiede una libreria .so separata. Android App Bundle consegna all'utente solo l'architettura appropriata, riducendo la dimensione di installazione.

Riepilogo

  • NDK — un insieme di strumenti per la compilazione incrociata di codice C/C++ in librerie native per Android con il compilatore Clang.
  • JNI — un'interfaccia che collega Kotlin/Java al codice nativo tramite la parola chiave external e le convenzioni di denominazione delle funzioni.
  • CMake — il sistema di compilazione principale di NDK, configurato tramite CMakeLists.txt e externalNativeBuild in Gradle.
  • ABI definisce l'architettura del processore — arm64-v8a, armeabi-v7a, x86_64. Ogni ABI richiede una compilazione separata della libreria.
  • Usa NDK solo per attività che richiedono prestazioni elevate: giochi, audio, grafica, crittografia, apprendimento automatico sul dispositivo.
  • Google Play supporta la suddivisione per ABI tramite App Bundle: l'utente riceve solo la libreria per l'architettura del proprio dispositivo.
  • Le librerie native aumentano significativamente la dimensione dell'APK, ma forniscono prestazioni massime e bassa latenza per le operazioni critiche.

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche