NDK Android: qué es, Native Development Kit y JNI para C++

Autor: IT Sectr Publicado: 2026-02-09 Tiempo de lectura: 10 min

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 — un conjunto de herramientas para compilar código C/C++ en bibliotecas nativas para Android.
  • JNI — una interfaz de interacción entre la máquina virtual Java/Kotlin y el código nativo.
  • CMake — el sistema de compilación principal para proyectos NDK, configurable mediante CMakeLists.txt.
  • ABI — la arquitectura objetivo del procesador (arm64-v8a, armeabi-v7a, x86_64) para la que se compila la biblioteca.
  • NDK no es necesario para la mayoría de las aplicaciones Android y solo se usa en tareas con altos requisitos de rendimiento.

Qué es NDK

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.

Capacidades clave de NDK

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.

ComponenteFunción
ClangCompilador C/C++ con soporte C++20
libc++Biblioteca estándar de C++ (única en NDK r27)
CMakeSistema de compilación de bibliotecas nativas
LLDBDepurador de código nativo en Android Studio
ndk-buildSistema de compilación heredado (reemplazado por CMake)

NDK vs SDK: cuándo se necesita código nativo

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.

Comparación SDK vs NDK por criterios

CriterioSDK (Kotlin/Java)NDK (C/C++)
RendimientoMedio (compilación JIT/AOT)Alto (código máquina)
Latencia de audio50–200 ms5–15 ms mediante Oboe
Gráficos 3DMediante Canvas/OpenGL Kotlin APIVulkan/OpenGL directamente
Complejidad de desarrolloBajaAlta (gestión de memoria, JNI)
Portabilidad del códigoSolo AndroidLinux, Windows, macOS, iOS
Tamaño del APKMínimo+1–10 MB por biblioteca

Arquitectura de NDK: herramientas y bibliotecas

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.

Estructura de directorios de NDK

text
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

APIs nativas de Android

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: cómo Kotlin llama a C++

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.

Declaración de una función nativa en 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
}

// Uso en código
val bridge = NativeBridge()
println(bridge.stringFromJNI())  // "Hello from C++"

Implementación de la función JNI en 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;
}

Configuración de CMakeLists.txt para NDK

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.

Ejemplo de CMakeLists.txt para una biblioteca nativa

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

Configuración de build.gradle para NDK

groovy
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 y compilación multiplataforma

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.

Determinación de la ABI del dispositivo en código 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");
}
ABIArquitecturaBitsDispositivos
arm64-v8aARMv8-A64 bitsCasi todos los teléfonos modernos
armeabi-v7aARMv7-A32 bitsDispositivos antiguos (anteriores a 2020)
x86_64x86-6464 bitsEmulador, Chromebook
x86x86 IA-3232 bitsEmuladores heredados

Ejemplos de código nativo en C++

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.

Operaciones matemáticas en código 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);

    // 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;
}

Registro de mensajes desde código nativo

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.

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

    LOGD("Processing complete");
}

Preguntas frecuentes

¿Es obligatorio usar NDK para el desarrollo en Android?

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.

¿Qué compilador usa NDK?

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.

¿Qué es ABI en el contexto de NDK?

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.

¿Se puede depurar código C++ mediante NDK?

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.

¿Cómo afecta NDK al tamaño del APK?

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

  • NDK — un conjunto de herramientas para la compilación cruzada de código C/C++ en bibliotecas nativas para Android mediante el compilador Clang.
  • JNI — una interfaz que conecta Kotlin/Java con el código nativo mediante la palabra clave external y las convenciones de nomenclatura de funciones.
  • CMake — el sistema de compilación principal de NDK, configurable mediante CMakeLists.txt y externalNativeBuild en Gradle.
  • ABI define la arquitectura del procesador: arm64-v8a, armeabi-v7a, x86_64. Cada ABI requiere una compilación separada de la biblioteca.
  • Use NDK solo para tareas que requieran alto rendimiento: juegos, audio, gráficos, criptografía, aprendizaje automático en el dispositivo.
  • Google Play admite la división por ABI mediante App Bundle: el usuario recibe solo la biblioteca para la arquitectura de su dispositivo.
  • Las bibliotecas nativas aumentan significativamente el tamaño del APK, pero proporcionan el máximo rendimiento y baja latencia para operaciones críticas.

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.

Discutir el proyecto

Lea también