NDK (Native Development Kit) es un conjunto de herramientas para escribir partes de una aplicación en C y C++ para Android. A diferencia del SDK de Android habitual, NDK compila el código en bibliotecas nativas (.so) que funcionan directamente con el procesador sin una capa de máquina virtual. Según Google NDK Guides, 2026, NDK se utiliza para computación de alto rendimiento, juegos, procesamiento de audio y reutilización de proyectos C/C++ existentes. JNI (Java Native Interface) conecta el código nativo con Kotlin y Java.
Puntos clave
NDK (Native Development Kit) es un conjunto de herramientas para la compilación cruzada de código C y C++ en bibliotecas ejecutables para Android. NDK incluye el compilador Clang, la biblioteca estándar libc++, los archivos de cabecera de la API de Android y utilidades de compilación. El desarrollador escribe código nativo en C o C++, lo compila en archivos .so (objetos compartidos) y los conecta a la aplicación mediante JNI.
NDK no reemplaza al SDK de Android, sino que lo complementa. Toda la interfaz de usuario, el ciclo de vida y los servicios del sistema permanecen en Kotlin o Java. El código nativo resuelve tareas específicas: cálculos matemáticos, criptografía, códecs, física en juegos. Google recomienda usar NDK solo cuando el SDK no proporcione el rendimiento o acceso necesario a las capacidades del hardware.
La historia de NDK comienza en 2009 (NDK r1). Durante este tiempo, el conjunto de herramientas ha pasado de ser un conjunto experimental de scripts a un sistema maduro con soporte para CMake, depuración con LLDB y creación de perfiles. A partir de 2026, la versión actual es NDK r27 con Clang 19, soporte completo de C++20 y libc++ como única biblioteca estándar.
NDK brinda al desarrollador acceso a las capacidades de bajo nivel de Android a través de APIs nativas. Los principales casos de uso incluyen: trabajo con OpenGL ES y Vulkan para gráficos 3D, procesamiento de audio mediante Oboe, operaciones criptográficas a través de BoringSSL y optimizaciones NEON para ARM. Todas estas APIs están disponibles desde C/C++ y requieren NDK para su compilación.
Además, NDK permite reutilizar bibliotecas C/C++ existentes sin reescribirlas en Kotlin. Esto es especialmente relevante para proyectos con una larga trayectoria en C++: motores de juegos (Unity, Unreal Engine), bibliotecas de visión por computadora (OpenCV) o paquetes criptográficos (OpenSSL). En estos casos, NDK ahorra años de desarrollo.
| Componente | Función |
|---|---|
| Clang | Compilador C/C++ con soporte C++20 |
| libc++ | Biblioteca estándar de C++ (única en NDK r27) |
| CMake | Sistema de compilación de bibliotecas nativas |
| LLDB | Depurador de código nativo en Android Studio |
| ndk-build | Sistema de compilación heredado (reemplazado por CMake) |
La elección entre NDK y SDK puro depende de la tarea. El SDK de Android proporciona APIs Java/Kotlin listas para el 90% de los escenarios: redes, archivos, notificaciones, cámara. NDK se utiliza cuando estas APIs no ofrecen el rendimiento o la funcionalidad requeridos. Por ejemplo, el procesamiento de audio en tiempo real mediante SDK es posible pero con una latencia de 50–200 ms, mientras que una biblioteca nativa a través de Oboe reduce la latencia a 5–15 ms.
El tamaño del APK es otro factor. Añadir NDK aumenta el tamaño de la aplicación, ya que cada arquitectura ABI requiere una biblioteca .so independiente. Sin embargo, el uso de Android App Bundle (AAB) mitiga el problema: Google Play entrega al usuario solo la biblioteca para su arquitectura. Un proyecto nativo típico añade 1–10 MB al tamaño de instalación por dispositivo.
| Criterio | SDK (Kotlin/Java) | NDK (C/C++) |
|---|---|---|
| Rendimiento | Medio (compilación JIT/AOT) | Alto (código máquina) |
| Latencia de audio | 50–200 ms | 5–15 ms mediante Oboe |
| Gráficos 3D | Mediante Canvas/OpenGL Kotlin API | Vulkan/OpenGL directamente |
| Complejidad de desarrollo | Baja | Alta (gestión de memoria, JNI) |
| Portabilidad del código | Solo Android | Linux, Windows, macOS, iOS |
| Tamaño del APK | Mínimo | +1–10 MB por biblioteca |
NDK se instala por separado del SDK de Android a través del SDK Manager. Dentro del directorio de NDK se encuentran el toolchain (compiladores), las bibliotecas de plataforma, los archivos de cabecera y las utilidades. El Toolchain de NDK incluye Clang para la compilación cruzada a todas las ABI objetivo: arm64-v8a, armeabi-v7a, x86_64 y x86. El compilador selecciona automáticamente la arquitectura requerida según las banderas de CMake o ndk-build.
Las APIs de Android para código nativo se proporcionan mediante archivos de cabecera en el directorio sysroot/usr/include. Allí se encuentran las declaraciones de todas las APIs disponibles en código nativo: native_activity.h para el ciclo de vida de Activity, input.h para eventos de entrada, sensor.h para sensores. La inclusión de estos encabezados da acceso a las capacidades del hardware sin una capa JNI.
ndk/
toolchains/
llvm/prebuilt/windows-x86_64/
bin/ // Clang, ld, llvm-profdata
sysroot/ // Archivos de cabecera de la API de Android
platforms/
android-26/ // libc, libm, libdl para API 26
android-34/
sources/
cxx-stl/ // Encabezados de libc++
build/
cmake/ // Archivo toolchain de CMake
A través de NDK están disponibles los siguientes grupos de APIs nativas: Native App Glue (gestión del ciclo de vida de Activity desde C), OpenGL ES 3.2 y Vulkan 1.3 (gráficos), Oboe (audio de baja latencia), Neural Networks API (aprendizaje automático en el dispositivo). Cada API tiene un archivo de cabecera y una biblioteca estática/dinámica como parte de NDK.
Trabajar con APIs nativas no requiere JNI: las funciones se llaman directamente desde el código C/C++. Sin embargo, la interacción con la interfaz de usuario en Kotlin sigue requiriendo JNI. Esta arquitectura híbrida es común en los motores de juegos: gráficos y física en C++ (mediante Vulkan), menús e interfaz de usuario en Kotlin (mediante Jetpack Compose).
JNI (Java Native Interface) es un mecanismo estándar para llamar código nativo desde la máquina virtual Java/Kotlin. El desarrollador declara una función externa en Kotlin con la palabra clave external y carga la biblioteca .so mediante System.loadLibrary. En el lado de C++, la función se declara utilizando la convención de nomenclatura de JNI, que codifica el paquete y el nombre de la clase.
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
}
// Uso en código
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 es el sistema de compilación principal para NDK, que ha reemplazado al antiguo ndk-build. El archivo CMakeLists.txt describe los archivos fuente, las bibliotecas y las banderas de compilación. Android Studio invoca automáticamente CMake al compilar el proyecto si externalNativeBuild está configurado en build.gradle. CMake genera makefiles para cada ABI objetivo y compila el código nativo en paralelo.
cmake_minimum_required(VERSION 3.22.1)
project("nativelib")
# Incluir archivos de cabecera
include_directories(src/main/cpp/include)
# Crear biblioteca nativa
add_library(
native-lib
SHARED
src/main/cpp/native-lib.cpp
src/main/cpp/math_utils.cpp
src/main/cpp/audio_processor.cpp
)
# Enlazar bibliotecas del sistema Android
target_link_libraries(
native-lib
android
log
OpenSLES
# libc++ se enlaza automáticamente
)
# Banderas de optimización
target_compile_options(native-lib PRIVATE -O3 -Wall -Wextra)
android {
defaultConfig {
ndk {
// ABI objetivo para la compilación
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) es el formato de código máquina que determina la compatibilidad con el procesador del dispositivo. Cada arquitectura ARM o x86 tiene su propio ABI. NDK compila el código nativo por separado para cada ABI especificado. Las ABI más comunes en 2026: arm64-v8a (99% de los dispositivos modernos), armeabi-v7a (dispositivos antiguos de 32 bits), x86_64 (emulador y Chromebook).
Especificar abiFilters en build.gradle limita la compilación solo a las arquitecturas necesarias, reduciendo el tiempo de compilación. Para Google Play, se recomienda incluir todas las ABI con las que la biblioteca es compatible; esto garantiza su funcionamiento en todos los dispositivos. Google Play Console permite configurar la entrega de APK específicos por ABI mediante 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 | Arquitectura | Bits | Dispositivos |
|---|---|---|---|
| arm64-v8a | ARMv8-A | 64 bits | Casi todos los teléfonos modernos |
| armeabi-v7a | ARMv7-A | 32 bits | Dispositivos antiguos (anteriores a 2020) |
| x86_64 | x86-64 | 64 bits | Emulador, Chromebook |
| x86 | x86 IA-32 | 32 bits | Emuladores heredados |
Consideremos un ejemplo práctico: una biblioteca matemática para trabajar con números de punto flotante. El código nativo en C++ realiza cálculos de manera más eficiente que Kotlin, gracias al acceso directo a las instrucciones NEON de ARM y la ausencia de verificaciones de límites de arrays en tiempo de ejecución. Este ejemplo demuestra un patrón típico de uso de NDK: trasladar los cálculos pesados a la capa nativa.
#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);
// Calcular la media y la desviación estándar
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);
// Normalización: (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;
}
Para depurar código nativo, use la macro __android_log_print de la biblioteca android/log.h. Los mensajes aparecen en Logcat junto a los registros de Java/Kotlin. El nivel de registro (ANDROID_LOG_DEBUG, ANDROID_LOG_ERROR) ayuda a filtrar los mensajes.
#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++) {
// Procesamiento pesado
LOGD("Item %d processed", i);
}
LOGD("Processing complete");
}
Preguntas frecuentes
No. Para la mayoría de las aplicaciones, el SDK de Kotlin es suficiente. NDK es necesario para tareas de alto rendimiento: procesamiento de audio/vídeo en tiempo real, gráficos 3D, criptografía o reutilización de proyectos C/C++ existentes.
NDK usa Clang del LLVM toolchain. Desde NDK r23, GCC se ha eliminado por completo. Clang compila código de forma cruzada para todas las ABI de Android: arm64-v8a, armeabi-v7a, x86_64, x86.
ABI (Application Binary Interface) es el formato de código máquina que determina la compatibilidad con el procesador. NDK compila bibliotecas .so para cada ABI por separado. arm64-v8a es la ABI principal para dispositivos Android modernos.
Sí. Android Studio es compatible con LLDB, un depurador de código nativo. Se pueden establecer puntos de interrupción en archivos C++, ver variables y la pila de llamadas. Se requiere NDK y el complemento LLDB.
Cada biblioteca nativa añade desde 100 KB hasta varios MB al APK. Cada ABI requiere una biblioteca .so independiente. Android App Bundle entrega al usuario solo la arquitectura adecuada, reduciendo el tamaño de instalación.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también