Android Native Development Kit
Contents
Introducción
La plataforma Android se sitúa por encima de Dalvik, la máquina virtual de Java, y toda la programación se hace normalmente desde Java.
Sin embargo en ocasiones podemos querer acceder a las librerías que están por debajo de Dalvik y que no se ofrecen como APIs Java.
Para ello utilizamos el lenguaje de programación C o C++ y utilizando NDK, nos ahorramos toda la complejidad que introduce JNDI. Si las librerías que queremos utilizar aún no están soportadas en NDK, no nos queda más remedio que utilizar directamente JNDI.
No se gana mucho rendimiento en general y la principal motivación de usar código en C/C++ es como hemos dicho, o acceder a APIs no disponibles desde Dalvik, ojo que sean estables, o la de reutilizar código que podíamos tener ya desarrollado.
Es importante utilizar sólo las librerías nativas que se anuncian ya como estables. Si usamos usas librerías cuya API cambie en el futuro harán que nuestra aplicación deje de funcionar con dichos cambios.
Instalación de NDK
acs@rayito:~/devel/android$ unzip android-ndk-r3-linux-x86.zip
acs@rayito:~/devel/android$ tree android-ndk-r3 -L 2
android-ndk-r3
|-- GNUmakefile
|-- README.TXT
|-- apps
| |-- hello-gl2
| |-- hello-jni
| |-- san-angeles
| |-- two-libs
| `-- unit-tests
|-- build
| |-- check-awk.awk
| |-- core
| |-- gmsl
| |-- host-setup.sh
| |-- platforms
| |-- prebuilt
| |-- toolchains
| `-- tools
`-- docs
|-- ANDROID-MK.TXT
|-- APPLICATION-MK.TXT
|-- CHANGES.TXT
|-- CPU-ARCH-ABIS.TXT
|-- HOWTO.TXT
|-- INSTALL.TXT
|-- LICENSES.TXT
|-- OVERVIEW.TXT
|-- STABLE-APIS.TXT
|-- SYSTEM-ISSUES.TXT
`-- system
acs@rayito:~/devel/android$ cd android-ndk-r3
acs@rayito:~/devel/android/android-ndk-r3$ build/host-setup.sh
Checking host development environment.
NDK Root : /home/acs/devel/android/android-ndk-r3
GNU Make : make (version 3.81)
Awk : awk
Platform : linux-x86
Generate : out/host/config.mk
Toolchain : Checking for arm-eabi-4.2.1 prebuilt binaries
Host setup complete. Please read docs/OVERVIEW.TXT if you don't know what to do.Cabe destacar que con el Nexus One es necesaria la versión 4.4.0 de arm-eabi, al menos para compilar Cupcake. Se puede cambiar dentro de "build/host-setup.sh".
Primer ejemplo
Una vez que tenemos instalado NDK vamos a realizar un primer ejemplo. Tendremos nuestro código C/C++ que gracias al NDK, se convertirá en librerías estáticas o dinámicas, que se podrán utilizar desde nuestro proyecto Android.
Comencemos con el ejemplo más básico, el de hello-jni, cuya definición se encuentra dentro del fichero:
acs@rayito:~/devel/android/android-ndk-r3$ cat apps/hello-jni/Application.mk APP_PROJECT_PATH := $(call my-dir)/project APP_MODULES := hello-jni
Para crear el JNI necesario para poder acceder desde Java a la API que se define en "hello-jni.c", ejecutamos:
acs@rayito:~/devel/android/android-ndk-r3$ make APP=hello-jni
Android NDK: Building for application 'hello-jni'
Compile thumb : hello-jni <= apps/hello-jni/project/jni/hello-jni.c
SharedLibrary : libhello-jni.so
Install : libhello-jni.so => apps/hello-jni/project/libs/armeabi
acs@rayito:~/devel/android/android-ndk-r3$ tree apps/hello-jni/
apps/hello-jni/
|-- Application.mk
`-- project
|-- AndroidManifest.xml
|-- default.properties
|-- jni
| |-- Android.mk
| `-- hello-jni.c
|-- libs
| `-- armeabi
| `-- libhello-jni.so
|-- res
| `-- values
| `-- strings.xml
|-- src
| `-- com
| `-- example
| `-- hellojni
| `-- HelloJni.java
`-- tests
|-- AndroidManifest.xml
|-- default.properties
`-- src
`-- com
`-- example
`-- HelloJni
`-- HelloJniTest.javaAhora lo que nos queda es crear la aplicación Android que hace uso de las librerías en C/C++ que hemos creado. Para ello seguimos el procedimiento normal de creación de aplicaciones en android utilizando el SDK.
acs@rayito:~/devel/android/android-ndk-r3/apps/hello-jni$ cat creaProyecto android update project \ --target 3 \ --name HolaJNI \ --path project \ --activity Hola \ --package es.acsblog.jni acs@rayito:~/devel/android/android-ndk-r3/apps/hello-jni$ . creaProyecto Error: Project folder 'project' is not empty. Please consider using 'android update' instead. Created directory /home/acs/devel/android/android-ndk-r3/apps/hello-jni/project/src/es/acsblog/jni Added file project/src/es/acsblog/jni/Hola.java ... Added file project/build.xml
Podemos ahora desde la clase "project/src/es/acsblog/jni/Hola.java" realizar llamadas a la librería
acs@rayito:~/devel/android/android-ndk-r3/apps/hello-jni/project$ ant debug
...
[apkbuilder] /home/acs/devel/android/android-ndk-r3/apps/hello-jni/project/bin/classes.dex => classes.dex
[apkbuilder] /home/acs/devel/android/android-ndk-r3/apps/hello-jni/project/libs/armeabi/libhello-jni.so => lib/armeabi/libhello-jni.so
debug:
[echo] Running zip align on final apk...
[echo] Debug Package: bin/HolaJNI-debug.apk
BUILD SUCCESSFUL
Total time: 4 secondsVemos como en los pasos finales se incluye la librería dinámica que implementa el código en C.
Si realizamos cualquier cambio en la parte de JNI deberemos de volver a ejecutar el comando "make APP=hello-jni" desde el directorio principal de la instalación de NDK.
Vemos que es transparente que para la compilación del código en C se utiliza el arm-eabi para generar binarios para ARM. Y como de forma automática genera las librerías compartidas necesarias.
Uso de la librería en C desde Java
En la clase que se utilice la librería nativa, Hola.java, tenemos que cargar la librería cuando se crea la clase. Para ello:
package es.acsblog.jni;
import android.app.Activity;
import android.os.Bundle;
public class Hola extends Activity
{
// Native method
public native String stringFromJNI();
...
static {
System.loadLibrary("hello-jni");
}
}La definición de las funciones que usaremos desde la librería nativa la hacemos como métodos "native" dentro de la clase.
La carga de la librería es en la parte estática de la clase con lo que se carga en el mismo momento que se inicializa la clase desde la máquina virtual. De esta forma siempre estará disponible dicha librería desde el código Java.
Un ejemplo de invocación de una función nativa desde Java sería:
...
TextView tv = new TextView(this);
tv.setText( stringFromJNI() );
setContentView(tv);
}
...Lo que hacemos es crear un vista de texto y dentro de ella pintamos el resultado de ejecutar la función C "stringFromJNI" cuya implementación es:
#include <string.h>
#include <jni.h>
jstring
Java_es_acsblog_jni_Hola_stringFromJNI( JNIEnv* env,
jobject thiz )
{
return (*env)->NewStringUTF(env, "Hello from JNI !");
}Atención al nombre de la función C, "Java_es_acsblog_jni_Hola_stringFromJNI", que incluye el paquete y la clase desde la que se hará visible la función, en este caso "es.acsblog.jni.Hola".
La compilación e instalación de la aplicación se realiza como siempre:
acs@rayito:~/devel/android/android-ndk-r3/apps/hello-jni/project$ ant debug
Buildfile: build.xml
[setup] Project Target: Android 1.6
[setup] API level: 4
...
[apkbuilder] /home/acs/devel/android/android-ndk-r3/apps/hello-jni/project/libs/armeabi/libhello-jni.so => lib/armeabi/libhello-jni.so
debug:
[echo] Running zip align on final apk...
[echo] Debug Package: bin/HolaJNI-debug.apk
BUILD SUCCESSFUL
Total time: 4 seconds
acs@rayito:~/devel/android/android-ndk-r3/apps/hello-jni/project$ adb install bin/HolaJNI-debug.apkSi hay cualquier tipo de problema en la ejecución, "adb logcat" nos dará información valiosa al respecto.
Primeras conclusiones
El NDK nos permite poder centrarnos en el desarrollo de la aplicación creando de forma automática él las librerías para ser accedidas desde JNI.
Tenemos que ser muy cuidadosos con el nombre de las funciones ya que indican desde que clase se engancha con JNI el código nativo.
De momento hemos mezclado dentro de NDK el proyecto Android, aunque no debería de ser complicado separarlos. Cada vez que hagamos cambios en el código nativo, tenemos que generar de nuevo desde el NDK la librería.
Desde el código nativo tenemos que utilizar sólo las librerías con API soportadas, que se definen dentro de "docs/STABLE-APIS.TXT" del NDK. Todo uso fuera de estas APIs hará que nuestra aplicación se rompa entre versiones de Android.