36
Usando el NDK (Native Development Kit) de Android* Xavier Hallade, Technical Marketing Engineer @ph0b - ph0b.com

Aprendiendo a usar NDK Android Day(castellano)

Embed Size (px)

DESCRIPTION

Aprendiendo a usar NDK Android Day presentado por Xavier Hallade en el Android Day del 06/06/2014 celebrado en Madrid

Citation preview

Page 1: Aprendiendo a usar NDK Android Day(castellano)

Usando el NDK (Native Development Kit) de Android* Xavier Hallade, Technical Marketing Engineer

@ph0b - ph0b.com

Page 2: Aprendiendo a usar NDK Android Day(castellano)

3

Agenda

• El NDK (Native Development Kit) de Android*

• Desarrollando una aplicación que utiliza el NDK

• Soportando diferentes arquitecturas de CPU

• Depuración (debug) y optimización

• Q&A

Page 3: Aprendiendo a usar NDK Android Day(castellano)

4

NDK (Native Development Kit) de Android* Qué es?

Un build de scripts/toolkit para incorporar código nativo en aplicaciones Android* a través de la Java Native Interface (JNI)

Por qué utilizarlo?

Performance

e.g., algoritmos complejos, aplicaciones multimedia, juegos

Diferenciación

Aplicaciones que toman ventaja de acceder directamente a la CPU/HW

e.g., utilizando SSSE3 para optimización

Animaciones fluidas sin cortes o interrupciones

Re-utilización de código

Por qué no utilizarlo?

La mejora en performance no siempre está garantizada y estamos agregando complejidad.

Page 4: Aprendiendo a usar NDK Android Day(castellano)

5

Código C/C++

Makefile ndk-build

Mix con Java*

GDB debug

Java Framework

SDK APIs

JNI

Librerías nativas

Aplicación Android*

NDK APIs

Librería Bionic C

Desarrollo de una aplicación NDK

Using JNI

Page 5: Aprendiendo a usar NDK Android Day(castellano)

6

Plataforma NDK Aplicación Android* NDK

Aplicación Dalvik*

Archivos .class

Fuente Java

Compilar con Javac

Librería nativa Java .class

Librería nativa Java*

Compilar con Javac

Crear header C con javah -jni

Archivo Header Código fuente C/C++

Compilar y linkear Código C

Librería dinámica

Archivos de la aplicación

Makefile

Opcional gracias a

JNI_Onload

Page 6: Aprendiendo a usar NDK Android Day(castellano)

7

Compatibilidad con C/C++ Standard

Librería Bionic C:

Más liviana que que la librería GNU C standard

No compatible con POSIX

Incluye soporte de pthread, pero limitado

Sin System-V IPCs

Acceso a las propiedades del sistema Android*

Bionic no es compatible en relación al binario con la librería C standard

Esto significa que generalmente deberás (re)compilar todo utilizando la toolchain del NDK Android.

Page 7: Aprendiendo a usar NDK Android Day(castellano)

8

Soporte Android* para C++ Por defecto se utiliza el sistema. Le falta:

Soporte de librería standard C++ (excepto algunos headers)

Soporte a excepciones de C++

Soporte RTTI

Afortunadamente, tienes otras librerías disponibles con el NDK:

Runtime Exceptions RTTI STL

system No No No

gabi++ Sí Sí No

stlport Sí Sí Sí

gnustl Sí Sí Sí

libc++ Sí Sí Sí

Elige contra qué librería quieres

compilar en tu Makefile

(Application.mk file):

APP_STL := gnustl_shared

Postfix el runtime con _static o

_shared

Para utilizar capacidades C++, también debes habilitarlas en tu Makefile: LOCAL_CPP_FEATURES += exceptions rtti

Page 8: Aprendiendo a usar NDK Android Day(castellano)

9

PSI

TS

PIDs

Instalando el NDK Android* NDK es un archive dependiente de la plataforma:

Provee:

Un ambiente de build

Headers y librerías Android*

Documentación y muestras de código

(muy útiles)

Puedes integrar con Eclipse ADT:

Page 9: Aprendiendo a usar NDK Android Day(castellano)

10

Estructura Standard de un proyecto

Android* Fuentes nativas – carpeta JNI

1. Creación de la carpeta

JNI para las Fuentes

nativas

3. Creación del Makefile

Android.mk

4. Construir librerías nativas

usando el script NDK-BUILD

2. Reutiliza o crea Fuentes

nativas c/c++

NDK-BUILD creará

automáticamente las carpetas

de librerías ABI.

Agregar manualmente código nativo a un proyecto Android*

Page 10: Aprendiendo a usar NDK Android Day(castellano)

11

Agregar soporte NDK a tu proyecto Android* en Eclipse

Page 11: Aprendiendo a usar NDK Android Day(castellano)

12

Android* NDK Samples

App de muestra Tipo

hello-jni Llama a una función nativa escrita en C desde

Java*.

bitmap-plasma Accede a un objeto Android* Bitmap desde C.

san-angeles Códiog EGL y OpenGL* ES en C.

hello-gl2 Setup EGL en Java y código OpenGL ES en C.

native-activity Muestra OpenGL solo en C

(sin Java, usa la clase NativeActivity).

native-plasma Muestra OpenGL solo en C

(también usa la clase NativeActivity).

Page 12: Aprendiendo a usar NDK Android Day(castellano)

13

El foco en Actividades Nativas

Sólo código nativo en el proyecto

android_main(): es el punto de entrada en su propio hilo de ejecución

Event loop to get input data and frame drawing messages

/**

* This is the main entry point of a native application that is using

* android_native_app_glue. It runs in its own thread, with its own

* event loop for receiving input events and doing other things.

*/

void android_main(struct android_app* state);

“Éste es el punto de entrada principal a una app nativa que utiliza android_native_app_glue,. corre en su propio hilo

de ejecucion, con su propio bucle de eventos para recibir eventos de entrada y ejecutar otras tareas.”

Page 13: Aprendiendo a usar NDK Android Day(castellano)

14

PSI

TS

PIDs

Integrando funciones nativas con Java*

Declara métodos nativos en tu app Android* (Java*) usando la palabra clave “nativa":

public native String stringFromJNI();

Proporciona una librería native compartida construida con el NDK que contiene los métodos usados por tu aplicación:

libMyLib.so

Tu aplicación deber cargar la librería compartida (antes de usarla… durante la carga de la clase, por ejemplo):

static {

System.loadLibrary("MyLib");

}

Hay dos formas de asociar tu código nativo a los métodos Java: javah y JNI_OnLoad

Page 14: Aprendiendo a usar NDK Android Day(castellano)

15

Método Javah

“javah” ayuda a generar automáticamente los headers JNI apropiados basados en los archivos fuente Java de los archivos compilados de los archivos de clase Java.

Ejemplo:

> javah –d jni –classpath bin/classes \

com.example.hellojni.HelloJni

Genera el archivo com_example_hellojni_HelloJni.h con esta definción:

JNIEXPORT jstring JNICALL

Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv *, jobject);

Page 15: Aprendiendo a usar NDK Android Day(castellano)

16

Javah Method

La función C que será mapeada automáticamente:

jstring

Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env,

jobject thiz )

{

return (*env)->NewStringUTF(env, "Hello from JNI !");

}

...

{

...

tv.setText( stringFromJNI() );

...

}

public native String stringFromJNI();

static {

System.loadLibrary("hello-jni");

}

Page 16: Aprendiendo a usar NDK Android Day(castellano)

17

Método JNI_OnLoad – Por qué?

Método probado

No más sorpresas luego de registrar métodos

Menos propenso a errores al refactorizar

Agregar/eliminar funciones nativas fácilmente

Sin problemas de tabla de símbolos al mezclar código C/C++

La mejor forma para cachear referencias a clases y objetos Java*

Page 17: Aprendiendo a usar NDK Android Day(castellano)

18

Método JNI_OnLoad En tu librería nombra las funciones a tu gusto y declara el mapeo con métodos JVM:

jstring stringFromJNI(JNIEnv* env, jobject thiz)

{ return env->NewStringUTF("Hello from JNI !");}

static JNINativeMethod exposedMethods[] = {

{"stringFromJNI","()Ljava/lang/String;",(void*)stringFromJNI},

}

()Ljava/lang/String; es la firma JNI del método Java*, que puedes recuperar usando la utilidad javap:

> javap -s -classpath bin\classes -p com.example.hellojni.HelloJni

Compiled from "HelloJni.java“

public native java.lang.String stringFromJNI();

Signature: ()Ljava/lang/String;

Page 18: Aprendiendo a usar NDK Android Day(castellano)

19

Método JNI_OnLoad

JNI_OnLoad es el punto de entrada de la librería llamado durante la carga.

Aquí aplica el mapeo definido previamente.

extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)

{

JNIEnv* env;

if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) !=

JNI_OK)

return JNI_ERR;

jclass clazz = env->FindClass("com/example/hellojni/HelloJni");

if(clazz==NULL)

return JNI_ERR;

env->RegisterNatives(clazz, exposedMethods,

sizeof(exposedMethods)/sizeof(JNINativeMethod));

env->DeleteLocalRef(clazz);

return JNI_VERSION_1_6;

}

Page 19: Aprendiendo a usar NDK Android Day(castellano)

20

Manejo de memoria en objetos Java* El manejo de memoria de objetos Java* es realizado por la JVM:

• Tú solo te encargas de las referencias a esos objetos

• Cada vez que obtienes una referencia, debes recordar

eliminarla luego de su uso

• Las referencias locales son eliminadas automáticamente

cuando la llamada nativa vuelve a Java

• Las referencias son locales por defecto

• Las referencias globales solo son creadas por NewGlobalRef()

Page 20: Aprendiendo a usar NDK Android Day(castellano)

21

Creando una cadena Java*

La memoria es manejada por la JVM, jstring siempre es una referencia.

Puedes hacer un llamado a DeleteLocalRef() una vez que has terminado con ella.

La diferencia principal de compilar código JNI en C o en C++ es la naturaleza de “env” como se ve aquí.

Recuerda que más allá de eso, la API es la misma.

C:

jstring string =

(*env)->NewStringUTF(env, "new Java String");

C++:

jstring string = env->NewStringUTF("new Java String");

Page 21: Aprendiendo a usar NDK Android Day(castellano)

22

Obteniendo una cadena C/C++ desde una cadena Java*

const char *nativeString = (*env)-

>GetStringUTFChars(javaString, null);

(*env)->ReleaseStringUTFChars(env, javaString, nativeString);

//más seguro

int tmpjstrlen = env->GetStringUTFLength(tmpjstr);

char* fname = new char[tmpjstrlen + 1];

env->GetStringUTFRegion(tmpjstr, 0, tmpjstrlen, fname);

fname[tmpjstrlen] = 0;

delete fname;

Page 22: Aprendiendo a usar NDK Android Day(castellano)

23

Manejando excepciones Java*

// llamar a métodos java puede arrojar excepciones Java

jthrowable ex = (*env)->ExceptionOccurred(env);

if (ex!=NULL) {

(*env)->ExceptionClear(env);

// manejar la excepción

}

(*env)->DeleteLocalRef(env, ex);

Page 23: Aprendiendo a usar NDK Android Day(castellano)

24

Tipos primitivos JNI

Tipos Java* Tipos Nativos Descripción

boolean jboolean unsigned 8 bits

byte jbyte signed 8 bits

char jchar unsigned 16 bits

short jshort signed 16 bits

int jint signed 32 bits

long jlong signed 64 bits

float jfloat 32 bits

double jdouble 64 bits

void void N/A

Page 24: Aprendiendo a usar NDK Android Day(castellano)

25

Tipos de Referencias JNI

jobject

jclass

jstring

jarray

jobjectArray

jbooleanArray

jbyteArray

jcharArray

jshortArray

jintArray

jlongArray

jfloatArray

jdoubleArray

jthrowable

Los elementos Arrays se manipulan usando

Get<type>ArrayElements() and Get/Set<type>ArrayRegion()

No olvides llamar ReleaseXXX() para cada llamado GetXXX()

Page 25: Aprendiendo a usar NDK Android Day(castellano)

26

Llamando métodos Java*

En una instancia de objeto: jclass clazz = (*env)->GetObjectClass(env, obj);

jmethodID mid = (*env)->GetMethodID(env, clazz, "methodName",

"(…)…");

if (mid != NULL)

(*env)->Call<Type>Method(env, obj, mid, parameters…);

Llamado estático: jclass clazz = (*env)->FindClass(env, "java/lang/String");

jmethodID mid = (*env)->GetStaticMethodID(env, clazz, "methodName",

"(…)…");

if (mid != NULL)

(*env)->CallStatic<Type>Method(env, clazz, mid, parameters…);

• (…)…: method signature

• Parámetros: lista de parámetros esperados por el método Java* • <Type>: Java method return type

Page 26: Aprendiendo a usar NDK Android Day(castellano)

27

Lanzando excepciones Java*

jclass clazz =

(*env->FindClass(env, "java/lang/Exception");

if (clazz!=NULL)

(*env)->ThrowNew(env, clazz, "Message");

La excepción será lanzada solo cuando la llamada JNI vuelve a

Java*, no romperá la ejecución en curso de código nativo

Page 27: Aprendiendo a usar NDK Android Day(castellano)

28

Incluye todos los ABIs seteando APP_ABI a all en jni/Application.mk:

APP_ABI=all

El NDK generará código optimizado para todos los ABIs objetivo

También puedes pasar la variable APP_ABI a ndk-build, y especificar cada ABI:

ndk-build APP_ABI=x86

NDK: configurando los ABIs objetivos

Build ARM v7a libs

Build ARM v5 libs

Build x86 libs

Build mips libs

Page 28: Aprendiendo a usar NDK Android Day(castellano)

33

Debugging con logcat El NDK provee una API de API en <android/log.h>:

Normalmente utilizado a través de este tipo de macro:

#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "APPTAG", __VA_ARGS__))

Ejemplo de uso:

LOGI("accelerometer: x=%f y=%f z=%f", x, y, z);

int __android_log_print(int prio, const char *tag,

const char *fmt, ...)

Page 29: Aprendiendo a usar NDK Android Day(castellano)

34

Debugging con logcat

Para obtener más información en ejecución de código nativo:

adb shell setprop debug.checkjni 1

(habilitado por defecto en el emulador)

Y para obtener más información sobre depuración de memoria (solo root):

adb shell setprop libc.debug.malloc 1

-> detección de pérdidas

adb shell setprop libc.debug.malloc 10

-> Detección de overrun

adb shell start/stop -> ambiente de reload

Page 30: Aprendiendo a usar NDK Android Day(castellano)

35

Debugging con GDB y Eclipse

Soporte nativo debe ser agregado a tu proyecto

Pasa NDK_DEBUG=1 APP_OPTIM=debug al commando ndk-build, desde las

propiedades del proyecto:

La bandera (flag) NDK_DEBUG debería ser automáticamente

seteada para un build de debug pero no ocurre en este caso.

Page 31: Aprendiendo a usar NDK Android Day(castellano)

36

Debugging con GDB y Eclipse*

Cuando NDK_DEBUG=1 es especificado, un archivo “gdbserver” se agrega a

tus librerías

Page 32: Aprendiendo a usar NDK Android Day(castellano)

37

Debugging con GDB y Eclipse*

Depura tu proyecto como una aplicación nativa Android*:

Page 33: Aprendiendo a usar NDK Android Day(castellano)

38

Debugging con GDB y Eclipse

Desde la perspectiva de depuración de Eclipse tu puedes manipular breakpoints

y depurar tu proyecto

Tu aplicación correrá antes de que se adjunte el debugger, los breakpoints que

establezcas cerca del lanzamiento de la app serán ignorados.

Page 34: Aprendiendo a usar NDK Android Day(castellano)

40

Flags GCC

ffast-math influence round-off of fp arithmetic and so breaks strict IEEE

compliance

Las otras optimizaciones son completamente seguras

Agrega -ftree-vectorizer-verbose para obtener un reporte de vectorización

ifeq ($(TARGET_ARCH_ABI),x86)

LOCAL_CFLAGS += -ffast-math -mtune=atom -msse3 -mfpmath=sse

else

LOCAL_CFLAGS += ...

endif

LOCAL_CFLAGS += -O3 -ffast-math -mtune=slm -msse4.2 -mfpmath=sse

Para optimizar para la microarquitectura Intel Silvermont (disponible a partir de NDK r9

gcc-4.8 toolchain):

Optimization Notice

Intel's compilers may or may not optimize to the same degree for non-Intel microprocessors for optimizations that are not unique to Intel microprocessors. These

optimizations include SSE2, SSE3, and SSSE3 instruction sets and other optimizations. Intel does not guarantee the availability, functionality, or effectiveness of any

optimization on microprocessors not manufactured by Intel. Microprocessor-dependent optimizations in this product are intended for use with Intel microprocessors.

Certain optimizations not specific to Intel microarchitecture are reserved for Intel microprocessors. Please refer to the applicable product User and Reference Guides

for more information regarding the specific instruction sets covered by this notice.

Notice revision #20110804

Page 35: Aprendiendo a usar NDK Android Day(castellano)

41

Vectorización Las instrucciones SIMD hasta SSSE3 está disponible en arquitecturas basadas en el procesador Intel® Atom™, Intel® SSE4.2 en la microarquitectura Intel Silvermont

En ARM*, puedes obtener vectorización a través de las instrucciones de ARM NEON*

Dos formas clásicas de utilizer estas instrucciones:

• Compilador de auto-vectorization

• Intrínseco del compilador Optimization Notice

Intel's compilers may or may not optimize to the same degree for non-Intel microprocessors for optimizations that are not unique to Intel microprocessors. These

optimizations include SSE2, SSE3, and SSSE3 instruction sets and other optimizations. Intel does not guarantee the availability, functionality, or effectiveness of any

optimization on microprocessors not manufactured by Intel. Microprocessor-dependent optimizations in this product are intended for use with Intel microprocessors.

Certain optimizations not specific to Intel microarchitecture are reserved for Intel microprocessors. Please refer to the applicable product User and Reference Guides

for more information regarding the specific instruction sets covered by this notice.

Notice revision #20110804

SSSE3, SSE4.2 Vector size: 128 bit

Data types:

• 8, 16, 32, 64 bit integer

• 32 and 64 bit float

VL: 2, 4, 8, 16

X2

Y2

X2◦Y2

X1

Y1

X1◦Y1

X4

Y4

X4◦Y4

X3

Y3

X3◦Y3

127 0

Page 36: Aprendiendo a usar NDK Android Day(castellano)

Q&A

[email protected]

@ph0b – ph0b.com