Upload
movilforum
View
414
Download
0
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
Usando el NDK (Native Development Kit) de Android* Xavier Hallade, Technical Marketing Engineer
@ph0b - ph0b.com
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
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.
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
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
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.
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
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:
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*
11
Agregar soporte NDK a tu proyecto Android* en Eclipse
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).
…
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.”
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
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);
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");
}
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*
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;
…
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;
}
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()
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");
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;
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);
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
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()
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
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
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
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, ...)
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
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.
36
Debugging con GDB y Eclipse*
Cuando NDK_DEBUG=1 es especificado, un archivo “gdbserver” se agrega a
tus librerías
37
Debugging con GDB y Eclipse*
Depura tu proyecto como una aplicación nativa Android*:
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.
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
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