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 (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.
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.
| Componente | Scopo |
|---|---|
| Clang | Compilatore C/C++ con supporto C++20 |
| libc++ | Libreria standard C++ (unica in NDK r27) |
| CMake | Sistema di compilazione per librerie native |
| LLDB | Debugger di codice nativo in Android Studio |
| ndk-build | Sistema di compilazione legacy (sostituito da CMake) |
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.
| Criterio | SDK (Kotlin/Java) | NDK (C/C++) |
|---|---|---|
| Prestazioni | Medie (compilazione JIT/AOT) | Alte (codice macchina) |
| Latenza audio | 50–200 ms | 5–15 ms tramite Oboe |
| Grafica 3D | Tramite API Kotlin Canvas/OpenGL | Vulkan/OpenGL direttamente |
| Complessità di sviluppo | Bassa | Alta (gestione memoria, JNI) |
| Portabilità del codice | Solo Android | Linux, Windows, macOS, iOS |
| Dimensione APK | Minima | +1–10 MB per libreria |
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.
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
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 (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.
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++"
#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 è 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.
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)
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 (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.
#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 | Architettura | Bit | Dispositivi |
|---|---|---|---|
| arm64-v8a | ARMv8-A | 64 bit | Quasi tutti i telefoni moderni |
| armeabi-v7a | ARMv7-A | 32 bit | Dispositivi più vecchi (pre-2020) |
| x86_64 | x86-64 | 64 bit | Emulatore, Chromebook |
| x86 | x86 IA-32 | 32 bit | Emulatori legacy |
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.
#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;
}
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.
#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
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.
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.
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.
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.
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
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.
Leggi anche