Conceptos

Antes de comenzar

En esta guía, se asume que ya conoces los conceptos inherentes a la programación nativa y al desarrollo de Android.

Introducción

En esta sección, se proporciona una explicación detallada del funcionamiento del NDK. Android NDK es un conjunto de herramientas que te permiten incorporar código C o C++ ("código nativo") a tus apps de Android. La capacidad para usar código nativo en apps de Android puede ser particularmente útil para los desarrolladores que deseen realizar una o varias de las siguientes acciones:

  • Conectar sus apps entre plataformas.
  • Reutilizar bibliotecas existentes o proporcionar sus propias bibliotecas para reutilizarlas
  • Aumentar el rendimiento en ciertos casos, en especial los de mayor exigencia computacional, como el de los juegos.

Cómo funciona

En esta sección, se presentan los componentes principales que se usan en la compilación de una app nativa para Android, y se describen los procesos de compilación y empaquetado.

Componentes principales

Debes comprender los siguientes componentes a medida que compilas tu app:

  • Bibliotecas compartidas nativas: El NDK compila estas bibliotecas, o archivos .so, a partir de tu código fuente de C/C++.
  • Bibliotecas estáticas nativas: El NDK también puede compilar bibliotecas estáticas, o archivos .a, que puedes vincular con otras bibliotecas.
  • Interfaz nativa de Java (JNI): Es la interfaz mediante la cual los componentes Java y C++ se comunican entre sí. En esta guía, se asume que ya conoces la JNI. Para obtener información sobre ella, consulta la Especificación de la interfaz nativa Java.
  • Interfaz binaria de la aplicación (ABI): La ABI define exactamente cómo se espera que el código máquina de tu app interactúe con el sistema en el tiempo de ejecución. El NDK compila archivos .so según estas definiciones. Las diferentes ABI corresponden a las distintas arquitecturas: El NDK incluye compatibilidad con ABI para ARM de 32 bits, AArch64, x86 y x86-64. Para obtener más información, consulta Administración de ABI.
  • Manifiesto: Si escribes una app sin componentes Java, debes declarar la clase NativeActivity en el manifiesto. En la sección Actividades y aplicaciones nativas encontrarás más detalles sobre cómo hacerlo, en el apartado "Cómo usar la interfaz native_activity.h".

Flujo

El flujo general para desarrollar una app nativa para Android es el siguiente:

  1. Diseña tu app y decide las partes que implementarás en Java, así como las que implementarás como código nativo.

    Nota: Si bien es posible evitar el uso de Java completamente, quizá te resulte útil el marco de trabajo de Java para Android para tareas que incluyen el control de la pantalla y la interfaz de usuario.

  2. Crea un proyecto de aplicación para Android como lo harías en cualquier otro proyecto de Android.
  3. Si escribes una app nativa, declara la clase NativeActivity en AndroidManifest.xml. Para obtener más información, consulta Aplicaciones y actividades nativas.
  4. Crea un archivo Android.mk que describa la biblioteca nativa y que también incluya el nombre, las marcas, las bibliotecas vinculadas y los archivos de origen que se van a compilar en el directorio "JNI".
  5. De manera opcional, puedes crear un archivo Application.mk si configuras las ABI de destino, la cadena de herramientas, el modo de lanzamiento y depuración, y la STL. Si no se especifica alguno de esos elementos, se usan los siguientes valores predeterminados, respectivamente:
    • ABI: Todas las ABI que no están obsoletas
    • Cadena de herramientas: Clang
    • Modo: Lanzamiento
    • STL: system
  6. Coloca tu archivo de origen nativo en el directorio jni del proyecto.
  7. Usa ndk-build para compilar las bibliotecas nativas (.so, .a).
  8. Compila el componente Java y crea el archivo .dex ejecutable.
  9. Empaqueta todo en un archivo APK que contenga los archivos .so, .dex y otros necesarios para que se ejecute tu app.

Aplicaciones y actividades nativas

El SDK de Android proporciona una clase de ayuda, NativeActivity, que te permite escribir una actividad completamente nativa. NativeActivity controla la comunicación entre el marco de trabajo de Android y tu código nativo, de manera que no tengas que crear una subclase ni llamar a sus métodos. Lo único que debes hacer es declarar que tu aplicación sea nativa en el archivo AndroidManifest.xml y comenzar a crearla.

Una aplicación para Android que usa NativeActivity aún se ejecuta en su propia máquina virtual, en una zona de pruebas separada de las demás aplicaciones. Por lo tanto, aún puedes acceder a las API del marco de trabajo de Android mediante la JNI. Sin embargo, en algunos casos (p. ej., con sensores, eventos de entrada y elementos), el NDK proporciona interfaces nativas que puedes usar en lugar de tener que realizar llamadas en toda la JNI. Para obtener más información, consulta API nativas del NDK de Android.

Independientemente de que desarrolles una actividad nativa o no, te recomendamos crear tus proyectos con las herramientas de compilación de Android tradicionales. De esta manera, podrás garantizar que las apps de Android se compilen y empaqueten con la estructura correcta.

El NDK de Android te ofrece dos opciones para implementar tu actividad nativa:

  • El encabezado native_activity.h define la versión nativa de la clase NativeActivity. Contiene la interfaz de devolución de llamada y las estructuras de datos que necesitas para crear tu actividad nativa. Debido a que el subproceso principal de tu app controla las devoluciones de llamada, las implementaciones de estas no deben generar bloqueos. Si lo hacen, podrías recibir errores ANR (la aplicación no responde), ya que el subproceso principal no responderá hasta que se muestre la devolución de llamada.
  • El archivo android_native_app_glue.h define una biblioteca auxiliar estática compilada por encima de la interfaz native_activity.h. Genera otro subproceso que controla, por ejemplo, las devoluciones de llamadas o los eventos de entrada en un bucle de eventos. Si mueves estos eventos a un subproceso independiente evitarás que las devoluciones de llamadas bloqueen tu subproceso principal.

También está disponible la fuente <ndk_root>/sources/android/native_app_glue/android_native_app_glue.c, lo que te permite modificar la implementación.

Para obtener más información sobre cómo usar esta biblioteca estática, explora la app de muestra native-activity y su documentación. Puedes leer más acerca de este tema en los comentarios del archivo <ndk_root>/sources/android/native_app_glue/android_native_app_glue.h.

Uso de la interfaz native_activity.h

Para implementar una actividad nativa con la interfaz native_activity.h, sigue estos pasos:

  1. Crea un directorio jni/ en el directorio raíz de tu proyecto. Ese directorio almacenará todo tu código nativo.
  2. Declara tu actividad nativa en el archivo AndroidManifest.xml.
  3. Como tu aplicación no tiene código Java, configura android:hasCode como false.

        <application android:label="@string/app_name" android:hasCode="false">
        

    Debes configurar el atributo android:name de la etiqueta de actividad como NativeActivity.

        <activity android:name="android.app.NativeActivity"
                    android:label="@string/app_name">
        

    Nota: Puedes separar a NativeActivity en subclases. Si lo haces, usa el nombre de la subclase en lugar de NativeActivity.

    El atributo android:value de la etiqueta meta-data especifica el nombre de la biblioteca compartida que contiene el punto de entrada a la aplicación (p. ej., main en C/C++), y omite el prefijo lib y el sufijo .so del nombre de la biblioteca.

                  <meta-data android:name="android.app.lib_name"
                    android:value="native-activity" />
                    <intent-filter>
                      <action android:name="android.intent.action.MAIN" />
                      <category android:name="android.intent.category.LAUNCHER" />
                    </intent-filter>
                  </activity>
                </application>
              </manifest>
        
  4. Crea un archivo para tu actividad nativa e implementa la función nombrada en la variable ANativeActivity_onCreate. La aplicación llama a esta función cuando se inicia la actividad nativa. Esta función, que es análoga a main en C/C++, recibe un puntero a una estructura ANativeActivity, que contiene punteros de función a distintas implementaciones de devoluciones de llamada que debes escribir. Configura los punteros de la función de devolución de llamada correspondientes en ANativeActivity->callbacks para las implementaciones de las devoluciones de llamada.
  5. Configura el campo ANativeActivity->instance con la dirección de cualquier instancia de datos específicos que quieras usar.
  6. Implementa todas las demás acciones que desees que tu actividad realice al iniciarse.
  7. Implementa el resto de las devoluciones de llamada que configuraste en ANativeActivity->callbacks. Para obtener más información sobre cuándo se deben invocar las devoluciones de llamada, consulta Cómo administrar el ciclo de vida de la actividad.
  8. Desarrolla el resto de tu aplicación.
  9. Crea un Android.mk file en el directorio jni/ de tu proyecto para describir el módulo nativo en el sistema de compilación. Para obtener más información, consulta Android.mk.
  10. Una vez que tengas un archivo Android.mk, compila tu código nativo con el comando ndk-build.
  11.     $ cd <path>/<to>/<project>
        $ <ndk>/ndk-build
        
  12. Compila e instala tu proyecto para Android como lo haces habitualmente. Si tu código nativo se encuentra en el directorio jni/, la secuencia de comandos de compilación empaquetará automáticamente los archivos .so compilados a partir de ella en el APK.

Código de ejemplo adicional

Para descargar muestras del NDK, consulta Muestras de NDK.