Ce document est extrait de ma formation “Android - programmation avancée”.
La formation aborde les thèmes suivants : les services, les fournisseurs de contenu, les capteurs, la localisation et évidement le NDK.
Les workshops sont effectués sous Android Studio.
2. antislashn.org Android avancé - NDK 2 / 97
NDK
● Native Development Kit
● ensemble d'outils permettant de coder en C/C++ pour la
plateforme Android
● Google met en avant que ce type dé développement
devrait-être exceptionnel
source : Google
4. antislashn.org Android avancé - NDK 4 / 97
NDK - support C++
● Le support de C++ par Android est minimal
● ne prend pas en compte
– la librairie standard C++
● sauf les .h les plus communs
● pas de STL
– les exceptions
– RTTI (Run-Time Type Information)
● un ensemble de bibliothèques supplémentaires est
utilisable
– "helper runtimes"
– cf. http://developer.android.com/ndk/guides/cpp-support.html
6. antislashn.org Android avancé - NDK 6 / 97
Structure du NDK
● Le répertoire du NDK est soit :
● le répertoire où le NDK a été
décompressé
● le répertoire <répertoire-sdk>/ndk-bundle
si le NDK a été téléchargé par le SDK
Manager
7. antislashn.org Android avancé - NDK 7 / 97
Structure du NDK
● Composants principaux
● ndk-build : script principal du système de build du NDK
● ndk-gdb : script pour le débogage du code natif, utilise le
débogueur gdb
● ndk-stack : exécutable d'analyse des traces de pile créées
lors du plantage du code natif
8. antislashn.org Android avancé - NDK 8 / 97
Structure du NDK
● Répertoires
● build : contient les modules nécessaires pour la chaîne de
build
● platforms : contient le fichiers .h pour chaque version
Android
● samples : exemples d'application native
● sources : bibliothèques tiers pouvant être importées dans
les projets NDK
● toolchains : utilisé par le cross-compiler
9. antislashn.org Android avancé - NDK 9 / 97
ABI
● Application Binary Interface
● Définit l'interaction du code avec le système
● jeu d'instruction du CPU
● type de mémoire (endian)
● format du binaire
● alignements mémoire, ...
● Un ABI doit être défini pour chaque CPU cible
● actuellement : ARM, x86, MIPS
● cf. : http://developer.android.com/ndk/guides/abis.html
11. antislashn.org Android avancé - NDK 11 / 97
JNI
● Java Native Interface
● effectue le lien entre le code natif C/C++ et le code Java
● Pour écrire un code compatible JNI, il faut :
● créer des classes java dont certaines méthodes seront
implémentées en C/C++
– ces méthodes ont la signature
● public native return-type method-name (params-list)
– chaque méthode correspondra à une fonction C
● qui sera exposée dans une fichier .h
13. antislashn.org Android avancé - NDK 13 / 97
Android Studio
● Créer la méthode native
public class MainActivity extends AppCompatActivity {
static{
System.loadLibrary("hello-jni");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView view = (TextView) findViewById(R.id.jni);
String msg = stringFromJNI();
view.setText(msg);
}
public native String stringFromJNI();
}
14. antislashn.org Android avancé - NDK 14 / 97
Android Studio
● Créer le fichier .h
● en mode console - dans le terminal d'Android Studio
– se positionner dans le répertoire java
– lancer la commande javah suivante
– le fichier .h a été généré dans le répertoire jni
javah -d ..jni org.antislashn.jni.hello.MainActivity
15. antislashn.org Android avancé - NDK 15 / 97
Android Studio
● Fichier .h généré
● org_antislashn_jni_hello_MainActivity.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_antislashn_jni_hello_MainActivity */
#ifndef _Included_org_antislashn_jni_hello_MainActivity
#define _Included_org_antislashn_jni_hello_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: org_antislashn_jni_hello_MainActivity
* Method: stringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_antislashn_jni_hello_MainActivity_stringFromJNI(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
16. antislashn.org Android avancé - NDK 16 / 97
Android Studio
● Codage en langage C
● le source est mis dans le répertoire jni
– nota : dans l'exemple suivant les fichiers .h et .c ont été renommés hello-jni
#include "hello-jni.h"
#include "string.h"
JNIEXPORT jstring JNICALL Java_org_antislashn_jni_hello_MainActivity_stringFromJNI
(JNIEnv* env, jobject thiz)
{
return (*env)->NewStringUTF(env,"Hello from JNI");
}
17. antislashn.org Android avancé - NDK 17 / 97
Android Studio
● Invocation de la méthode native
● la librairie doit être chargée
public class MainActivity extends AppCompatActivity {
static{
System.loadLibrary("hello-jni");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView view = (TextView) findViewById(R.id.jni);
String msg = stringFromJNI();
view.setText(msg);
}
public native String stringFromJNI();
}
chargement de la librairie
seul le nom du module est
précisé
invocation de la méthode native
18. antislashn.org Android avancé - NDK 18 / 97
Intégration des librairies C/C++
● Deux types d'intégrations
● les librairies C/C++ ont déjà été compilées pour les
différentes cibles
– répertoire libs du module
● contient les sous-répertoires correspondants aux ABIs
● le code source C/C++ doit être compilé
– les sources sont dans le répertoire jni du module
19. antislashn.org Android avancé - NDK 19 / 97
Étapes de création
● Créer un projet Android
● ou modifier un projet existant
● Écriture des méthodes natives Java
● Invocation de l'utilitaire javah pour créer le fichier .h
● Écriture des fonctions C
● Compilation vers les différentes plateformes (ABI)
● avec l'utilitaire ndk-build
– script qui utilise la commande make
– nécessite des fichiers .mk
20. antislashn.org Android avancé - NDK 20 / 97
Android Studio
● Structure de répertoire
● ajouter un répertoire jni au projet
– passer en vue "Project" pour
ajouter le répertoire
– répertoire par défaut des sources
pour gradle
● les fichiers .h et .c seront placés
dans ce répertoire
21. antislashn.org Android avancé - NDK 21 / 97
Android Studio
● Compilation : plusieurs possibilités
● compilation directe par ndk-build
– nécessite les fichiers *.mk
– dans le terminal Android Studio
● compilation automatique via Gradle
– pas besoin de fichier *.mk
– attention aux versions d'Android Studio
● compilation par un l'exécution d'un script Gradle
– nécessite les fichiers *.mk
– à partir de Android Studio 1.5.1
22. antislashn.org Android avancé - NDK 22 / 97
Logs
● Utiliser le système de log Android en natif
● inclure android/log.h
● ajouter la librairie au linker ld
– Android.mk
– build.gradle
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c calcul.c
LOCAL_LDLIBS += -llog
include $(BUILD_SHARED_LIBRARY)
ndk {
moduleName "hello-jni"
ldLibs "log"
}
23. antislashn.org Android avancé - NDK 23 / 97
Logs
● Niveaux de log
● définis dans un enum
– extrait du log.h
typedef enum android_LogPriority {
ANDROID_LOG_UNKNOWN = 0,
ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
ANDROID_LOG_VERBOSE,
ANDROID_LOG_DEBUG,
ANDROID_LOG_INFO,
ANDROID_LOG_WARN,
ANDROID_LOG_ERROR,
ANDROID_LOG_FATAL,
ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
} android_LogPriority;
24. antislashn.org Android avancé - NDK 24 / 97
Logs
● L'utilisation du framwork de log nécessite de préciser
● le niveau de log
● le tag du log
– identificateur de l'émetteur du log
● le message de log
25. antislashn.org Android avancé - NDK 25 / 97
Logs
● Fonctions
● __android_log_write : pour un simple message
● __android_log_print : pour un message formaté
comme avec le printf du langage C
● __android_log_vprint : pour un message formaté
comme avec le printf du langage C
– les paramètres sont récupérés par va_list
● __android_log_assert : pour logger des échecs
d'assertion
26. antislashn.org Android avancé - NDK 26 / 97
Logs
● Exemple
● les logs sont affichés dans la vue "logcat"
__android_log_write(ANDROID_LOG_INFO,"JNI",">>> simple message");
__android_log_print(ANDROID_LOG_INFO,"JNI",">>> titre : %s",str);
__android_log_print(ANDROID_LOG_INFO,"JNI",">>> NB_MEMOS : %i",nbMemos);
logInfo("JNI",">>> titre : %s et NB_MEMOS : %d",str,nbMemos);
if(!ok)
__android_log_assert("!ok","JNI","ok vaut %d",ok);
void logInfo(const char* tag, const char* fmt, ...){
va_list args;
va_start(args,fmt);
__android_log_vprint(ANDROID_LOG_INFO,tag,fmt,args);
va_end(args);
}
27. antislashn.org Android avancé - NDK 27 / 97
Déboguer
● Android NDK permet de déboguer une application
● fonctionne à partir de la version Android 2.2 (Froyo)
● Étapes sous Android Studio (version 1.5.1)
● ajouter dans build.gradle le support du debug
● si ce n'est pas fait, créer une configuration Android Native
– vérifier que la configuration ne soit pas en erreur
● mettre les points d'arrêt dans le source C
● lancer la configuration native en mode debug
28. antislashn.org Android avancé - NDK 28 / 97
Déboguer
● Ajouter le support du débogage dans build.gralde
...
buldTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug{
debuggable=true
jniDebuggable=true
}
}
...
29. antislashn.org Android avancé - NDK 29 / 97
Déboguer
● Créer un configuration Android Native
● Run → Edit configuration ou
● si nécessaire ajouter une configuration
30. antislashn.org Android avancé - NDK 30 / 97
Déboguer
● Mettre les points d'arrêt nécessaires dans les sources
● Lancer l'application native en mode debug
● le démarrage de l'application peut être assez long
● l'onglet Debug s'affiche
31. antislashn.org Android avancé - NDK 31 / 97
JNI - Implémentation de la méthode native
● La fonction native prend au moins deux paramètres
● même si la méthode Java n'a aucun paramètre
public class Calcul {
public native void foo();
public native static void bar();
}
JNIEXPORT void JNICALL Java_org_antislashn_jni_hello_Calcul_foo (JNIEnv *, jobject);
JNIEXPORT void JNICALL Java_org_antislashn_jni_hello_Calcul_bar (JNIEnv *, jclass);
javah
32. antislashn.org Android avancé - NDK 32 / 97
JNI - Implémentation de la méthode native
● JNIEnv est un pointeur vers une structure
correspondant à l'interface de la JVM
● C++ : le déférencement est effectué par env->
● C : le déférencement est effectué par (*env)->
33. antislashn.org Android avancé - NDK 33 / 97
JNI - Implémentation de la méthode native
● Le second paramètre correspond à l'instance de la
classe ou la classe elle-même
● méthode statique : paramètre de type jclass
● méthode d'instance : paramètre de type jobject
● Les paramètres supplémentaires correspondent aux
paramètres de la méthode native Java
● deux types de paramètres
– les types primitifs
– les références
34. antislashn.org Android avancé - NDK 34 / 97
JNI - Types primitifs
● Les types primitifs ont une correspondance directe
avec les types C/C++
Java JNI C/C++ Taille
boolean jboolean unsigned char unsigned 8 bits
byte jbyte char signed 8 bits
char jchar unsigned short unsigned 16 bits
short jshort short signed 16 bits
int jint int signed 32 bits
long jlong long long signed 64 bits
float jfloat float 32 bits
double jdouble double 64 bits
36. antislashn.org Android avancé - NDK 36 / 97
JNI - les références
● Les références sont passées à la méthodes natives
● elles ne peuvent pas être directement modifiées
● le garbage-collector utilise les références pour libérer la
mémoire
● JNI propose trois types de références
– référence local (local reference)
– référence globale (global reference)
– référence faible (weak reference)
37. antislashn.org Android avancé - NDK 37 / 97
JNI - référence locale
● Les références locales sont libérées au retour de la
fonction native
● la référence, pas l'objet qui est référencé
– les références passées au fonctions natives sont locales
– la plupart des références retournées par JNI sont locales
● une référence locale ne doit pas être sauvegardée d'un
appel de méthode à l'autre
– le code ci-dessous n'est donc pas valide
static jobject myReference;
JNIEXPORT void JNICALL Java_org_antislashn_jni_hello_Calcul_foo
(JNIEnv *env, jobject thiz, jobject lRef){
myReference = lRef;
}
38. antislashn.org Android avancé - NDK 38 / 97
JNI - référence locale
● Les références locales peuvent être supprimées
● La spécification impose à la JVM de garder au moins
16 références en même temps
● la JVM peut refuser d'en créer d'autres
● il peut être nécessaire de préciser explicitement le nombre
de références
(*env)->DeleteLocalRef(myReference);
(*env)->EnsureLocalCapacity(40);
39. antislashn.org Android avancé - NDK 39 / 97
JNI - référence globale
● Une référence globale garde une référence sur un
objet
● le garbage-collector tient compte de cette référence
● Une références globale peut être créée sur une
référence locale
● Une référence globale peut-être supprimée
jclass localClazz;
jclass globalClazz;
localClazz = (*env)->FindClass(env, "java/lang/String");
globalClazz = (*env)->NewGlobalRef(env,localClazz);
(*env)->DeleteGlobalRef(env,globalClazz);
40. antislashn.org Android avancé - NDK 40 / 97
JNI - référence faible
● Une référence faible peut-être utilisée d'une méthode
native à une autre méthode native
● comme pour une référence globale
● Une référence faible ne permet pas de garantir que
l'objet référencé ne supprimé par le GC
● à l'inverse d'une référence globale
41. antislashn.org Android avancé - NDK 41 / 97
JNI - référence faible
● Création d'une référence faible
● Vérification de la validité
● Suppression
jclass localClazz;
jclass weakClazz;
localClazz = (*env)->FindClass(env, "java/lang/String");
weakClazz = (*env)->NewWeakGlobalRef(env,localClazz);
if((*env)->IsSameObject(env,weakClazz,NULL) == JNI_TRUE){
// le GC a supprimé l'objet, l'objet ne peut pas être utilisé
}
else{
// l'objet est toujours valide, il peut-être utilisé
}
(*env)->DeleteWeakGlobalRef(env,weakClazz);
42. antislashn.org Android avancé - NDK 42 / 97
JNI
● Les pages suivantes présentent quelques opérations
de base
● création d'objets, conversion vers C, destruction, …
● Toutes ces opérations sont accessibles via le
pointeur vers JNIEnv
● voir la documentation
– http://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html
43. antislashn.org Android avancé - NDK 43 / 97
JNI - opérations sur String
● Profonde différence entre les chaînes de caractères
Java et C
● JNI support Unicode et UTF-8
– les méthodes diffèrent par l'ajout de "UTF" dans leur nom
● Création
● retourne NULL si la construction a échouée
jstring javaString;
javaString = (*env)->NewStringUTF(env,"hello, world");
44. antislashn.org Android avancé - NDK 44 / 97
JNI - opérations sur String
● Conversion vers C
● isCopy indique si une copie a été effectuée
– peut-être NULL
● Destruction d'une chaîne C retournée par une
fonction JNI
jstring javaString;
const jbyte * str;
jboolean isCopy;
javaString = (*env)->NewStringUTF(env,"hello, world");
str = (*env)->GetStringUTFChars(env,javaString,$isCopy);
(*env)->ReleaseStringUTFChars(env,javaString,str);
45. antislashn.org Android avancé - NDK 45 / 97
JNI - opérations sur les tableaux
● Création
● Les différentes signatures sont du type NewXXXArray
– où XXX est Int, Char, Byte,...
– renvoie NULL si la création n'a pas été possible
● Conversion vers C
jintArray ints;
ints = (*env)->NewIntArray(env,10);
jintArray javaArray;
javaArray = (*env)->NewIntArray(env,10);
jint nativeArray[10];
(*env)->GetIntArrayRegion(env, javaArray,0,10, nativeArray);
46. antislashn.org Android avancé - NDK 46 / 97
JNI - opérations sur les tableaux
● Utilisation d'un pointeur
● fonctions GetXXXArrayElements
● le pointeur retourné doit être libérer
– fonctions ReleaseXXXArrayElements
jint* pJint = (*env)->GetIntArrayElements(env,javaArray);
(*env)->ReleaseIntArrayElements(env,javaArray,pJint,0);
release mode
Release Mode Action
0 Recopie contenu et libère le tableau natif
JNI_COMMIT Recopie le contenu mais ne libère pas le tableau natif
Le tableau Java peut-être mis à jour
JNI_ABORT Libère le tableau natif sans recopier son contenu
47. antislashn.org Android avancé - NDK 47 / 97
JNI - NIO
● Fonctions JNI pour utiliser les buffers NIO
● un "direct buffer" est un tableau d'octets
● Création
● Récupération
unsigned char* buffer = (unsigned char*) malloc(1024);
jobject directBuffer = (*env)->NewDirectByteBuffer(env,buffer,1024);
unsigned char * buf = (unsigned char*) (*env)->GetDirectBufferAddress(env,directBuffer);
48. antislashn.org Android avancé - NDK 48 / 97
JNI - membres d'une classe Java
● Deux types de membre
● méthode ou propriété d'instance
● méthode ou propriété de classe - propriété statique
● L'accès aux membres d'une instance (ou d'une
classe) passe par le type jclass
jclass clazz = (*env)->GetObjectClass(env,instance);
49. antislashn.org Android avancé - NDK 49 / 97
JNI - membres d'une classe Java
● Les extraits de code des pages suivantes utilisent la
classe Memo
public class Memo {
private static int NB_MEMOS = 0;
private int urgence = 0;
private String titre;
private String descriptif;
public Memo() {
NB_MEMOS++;
}
public Memo(String titre, String descriptif) {
this.titre = titre;
this.descriptif = descriptif;
NB_MEMOS++;
}
// Liste des getteurs / setteurs
...
}
50. antislashn.org Android avancé - NDK 50 / 97
JNI - signature des types
● La manipulation des propriétés et des méthodes passe par
l'utilisation des signatures de type de la JVM
Type Java Signature
boolean Z
byte B
char C
short S
int I
long J
float F
double D
classe ex: java.lang.String Ljava/lang/String;
type[] [type
méthode (arg-type) ret-type
retour void V
51. antislashn.org Android avancé - NDK 51 / 97
JNI - signature des types
● Exemple
● une méthode int foo(int n, String s, int[] ints);
– est associée au descripteur (ILjava/lang/String;[I)J
● une méthode int bar();
– est associée au descripteur ()I
● une méthode void foo(String s);
– est associée au descripteur (Ljava/lang/String;)V
52. antislashn.org Android avancé - NDK 52 / 97
JNI - signature des types
● L'outil javap aide à trouver les signatures
javap -classpath .debug -p -s org.antislashn.jni.hello.Memo
...
public int getUrgence();
descriptor: ()I
public void setUrgence(int);
descriptor: (I)V
public java.lang.String getTitre();
descriptor: ()Ljava/lang/String;
public void setTitre(java.lang.String);
descriptor: (Ljava/lang/String;)V
...
53. antislashn.org Android avancé - NDK 53 / 97
JNI - propriétés d'une classe Java
● Accès aux propriétés
● d'abord récupérer le jfieldID de la propriété
– méthodes de JNIEnv : GetFieldId ou GetStaticFieldId
● lire la valeur du champ
– méthodes GetXxxField pour les propriétés d'instance
– méthodes GetStaticXxxField pour les propriétés de classe
– où Xxx est Int, Boolean, Short, Object, …
● mettre à jour la valeur du champ
– méthodes SetXxxField pour les propriétés d'instance
– méthodes SetStaticXxxField pour les propriétés de classe
– où Xxx est Int, Boolean, Short, Object, …
54. antislashn.org Android avancé - NDK 54 / 97
JNI - propriétés d'une classe Java
● Accès aux propriétés : extrait de code
...
jclass clazz = (*env)->GetObjectClass(env,instance);
jfieldID instanceFieldID = (*env)->GetFieldID(env,clazz,"titre","Ljava/lang/String;");
jfieldID staticFieldId = (*env)->GetStaticFieldID(env,clazz,"NB_MEMOS","I");
jstring titre = (*env)->GetObjectField(env,instance,instanceFieldID);
jint nb = (*env)->GetStaticIntField(env,clazz,staticFieldId);
(*env)->SetStaticIntField(env,clazz,staticFieldId,35);
...
statique donc de
type jclass
instance de Memo reçue comme
paramètre de la méthode
nouvelle valeur pour la propriété
statique de Memo
type de la propriété
55. antislashn.org Android avancé - NDK 55 / 97
JNI - méthodes d'une classe Java
● Comme pour les propriétés
● deux types de méthodes
– statique
– ou d'instance
● Mêmes principes que pour accéder aux propriétés
● récupération du l'identifiant de type jmethodID
– méthodes de JNIEnv : GetMethodID, GetStaticMethodID
● puis appel de la méthode
– méthodes CallStaticXxxMethod, CallXxxMethod,..
● cf. la documentation pour l'ensemble des méthodes d'invocation
56. antislashn.org Android avancé - NDK 56 / 97
JNI - méthodes d'une classe Java
● Invocation d'une méthode Java, extrait de code
...
jmethodID setTitreID = (*env)->GetMethodID(env,clazz,"setTitre","(Ljava/lang/String;)V");
jstring newTitle = (*env)->NewStringUTF(env,"Nouveau titre");
(*env)->CallVoidMethod(env,instance,setTitreID,newTitle);
jmethodID getTitreID = (*env)->GetMethodID(env,clazz,"getTitre","()Ljava/lang/String;");
jstring t = (*env)->CallObjectMethod(env,instance,getTitreID);
...
paramètre de type jstring
57. antislashn.org Android avancé - NDK 57 / 97
Multithreading
● Un thread Java peut lancer du code natif
● aisé à coder, le code Java interagit avec les instances des
classes java.lang.Thread
● pas d'impact sur le code natif
● le code natif peut communiquer avec le code Java via
JNIEnv
58. antislashn.org Android avancé - NDK 58 / 97
Multithreading
● L'utilisation des threads Java doit prendre en
considération les points suivants
● le code natif est-il thread-safe ?
● le code natif ne peut pas bénéficier de la programmation
concurrente Java
● le code natif exécuté dans des threads Java différents
– ne peuvent pas communiquer
– ne peuvent pas partager des ressources
59. antislashn.org Android avancé - NDK 59 / 97
Multithreading
● Exemple de code
● le code Java crée des threads
● le code natif est exécuté par le thread Java
...
private void javaThreads(int threads, final int iterarions){
// Création d'un thread Java pour chaque worker natif
for(int i=0 ; i<threads ; i++){
final int id = i;
Thread thread = new Thread(){
@Override
public void run() {
nativeWorker(id,iterarions);
}
};
thread.start();
}
}
...
60. antislashn.org Android avancé - NDK 60 / 97
POSIX Thread
● POSIX : norme technique de l'IEEE
● IEEE : Institute of Electrical an Electronics Engineers
● POSIX : Portable Operating System Interface X
● POSIX Thread est aussi nommé Pthreads
● Utilisation de Pthread
● inclure la librairie pthread.h
– l'implémentation fait partie de Bionic
– pas de lien supplémentaire pour le linker
● Les pages suivantes présentent les principes de base de
PThread
● voir le man pour une documentation complète
61. antislashn.org Android avancé - NDK 61 / 97
POSIX Thread
● Création d'un thread : fonction pthread_create
● arguments
– pthread_t* thread : pointeur mis à jour par la fonction pour
retourner le nouveau thread
– pthread_attr_t const* attr : attributs nécessaires à la
création du thread (base de la pile, taille de la pile, etc...)
– void* (*start_routine)(void*) : pointeur vers fonction
de la routine devant être exécutée par le thread
– void* arg : arguments passés à la fonction, peut être NULL
● retour
– int : 0 si le thread est créé, ou code d'erreur
62. antislashn.org Android avancé - NDK 62 / 97
POSIX Thread
● Les pthreads ne font pas partie de la plateforme Java
● ils doivent être attachés à la JVM pour interagir avec la
partie Java
● une fois attaché le pthread doit invoquer une méthode
callback pour interagir avec l'IHM
– le pthread doit donc avoir une référence vers l'activité
● utilisation de références globales comme cache
● utilisation de la fonction JNI_OnLoad
– fonction appelée par la JVM lorsque la librairie est chargée
– l'appel de la fonction fournit comme premier paramètre un pointeur
vers la JVM
– le pthread est détaché lorsque la tâche est finie
63. antislashn.org Android avancé - NDK 63 / 97
POSIX Thread
● Exemple de code
● la référence vers la JVM est mise en cache
– les références globales devront être supprimées
...
static jmethodID gOnNativeMessage = NULL;
static JavaVM* gVm = NULL;
static jobject gObj = NULL;
jint JNI_OnLoad(JavaVM* vm, void* resserved){
gVm = vm;
return JNI_VERSION_1_2;
}
...
64. antislashn.org Android avancé - NDK 64 / 97
POSIX Thread
● Le pthread doit être créé, puis attaché à la JVM
● création par pthread_create
● la routine exécutée par pthread devra
– attacher le pthread à la JVM
– exécuter la tâche
– détacher le pthread à la JVM
65. antislashn.org Android avancé - NDK 65 / 97
POSIX Thread
● Extraits de code
...
pthread_t thread;
// Create a new thread
int result = pthread_create(&thread,NULL,nativeWorkerThread,static_cast<void*>(nativeWorkerArgs));
if (0 != result) {
...
création du pthread
fonction exécutée dans le pthread
pointeur sur les paramètres
passés à la fonction qui sera
exécutée dans le pthread
66. antislashn.org Android avancé - NDK 66 / 97
POSIX Thread
● Code de la fonction exécutée dans le pthread
static void* nativeWorkerThread(void* args) {
JNIEnv *env = NULL;
// On attache le thread courant à la JVM
// et on récupère un pointeur vers JNIEv
if (gVm->AttachCurrentThread(&env, NULL) == 0) {
NativeWorkerArgs* nArgs = static_cast<NativeWorkerArgs*>(args);
// Excéution du worker natif dans le context du thread
Java_org_antislashn_threads_MainActivity_nativeWorker(env, gObj, nArgs->id, nArgs->iterations);
delete nArgs;
// on détache le thread courant de la JVM
gVm->DetachCurrentThread();
}
return (void*)1;
}
tâche réellement exécutée dans le pthread
la tâche est attachée avant son exécution
puis détachée à la fin de son exécution
67. antislashn.org Android avancé - NDK 67 / 97
POSIX Thread
● Contrairement aux threads Java, les threads POSIX
peuvent renvoyer un résultat
● une fonction de type join est utilisée pour attendre la fin
du Pthread et récupérer le résultat
– int pthread_join(pthread_t thread, void** ret_val)
● cette fonction est bloquante jusqu'à la fin du pthread
● paramètres
– pthread_t thread : thread retourné par pthread_create
– void** ret_val : pointeur vers le résultat (de type void*)
retourné par le tâche du pthread
68. antislashn.org Android avancé - NDK 68 / 97
POSIX Thread
● Extrait de code
...
pthread_t* handles = new pthread_t[nbThreads];
...
// Attente de la fin des tâches exécutées par les pthreads
for(jint i=0 ; i< nbThreads ; i++){
void* result = NULL;
int r=0;
if((r=pthread_join(handles[i],&result)) != 0){
...
}
else{
char message[50];
sprintf(message,"Worker %d returned %d", i, result);
jstring messageString = env->NewStringUTF(message);
env->CallVoidMethod(thizz, gOnNativeMessage, messageString);
if (NULL != env->ExceptionOccurred())
{
return;
}
}
}
...
69. antislashn.org Android avancé - NDK 69 / 97
POSIX Thread
● Synchronisation des pthreads par mutexes
● type pthread_mutex_t
● Fonctions de base
● initialisation d'un mutex
– fonction pthread_mutex_init
– macro PTHREAD_MUTEX_INITIALIZER
● blocage d'un mutex
– fonction pthread_mutext_lock
● déblocage d'un mutex
– fonction pthread_mutex_unlock
70. antislashn.org Android avancé - NDK 70 / 97
POSIX Thread
● Initialisation d'un mutex
● il est nécessaire de créer un variable de type
pthread_mutext_t
– ici en variable statique
● puis appel de la fonction d'initialisation
– le second paramètre étant NULL, création d'un mutex par défaut
...
static pthread_mutex_t mutex;
...
void Java_org_antislashn_threads_MainActivity_nativeInit(JNIEnv *env, jobject thizz) {
if(pthread_mutex_init(&mutex,NULL)!=0){
jclass exceptionClazz = env->FindClass("java/lang/RuntimeException");
env->ThrowNew(exceptionClazz, "Unable to initialize mutex");
return;
}
...
71. antislashn.org Android avancé - NDK 71 / 97
POSIX Thread
● Le worker du pthread peut acquérir le mutex en début de tâche et
le libérer lorsque la tâche est terminée
● la fonction pthread_mutext_lock est bloquante tant que le
mutex n'est pas disponible
...
void Java_org_antislashn_threads_MainActivity_nativeWorker(JNIEnv *env, jobject thizz, jint id,
jint iterations) {
if(pthread_mutex_lock(&mutex) !=0){
jclass exceptionClazz = env->FindClass("java/lang/RuntimeException");
env->ThrowNew(exceptionClazz, "Unable to lock mutex");
return;
}
...
if (pthread_mutex_unlock(&mutex) != 0)
{
jclass exceptionClazz = env->FindClass("java/lang/RuntimeException");
env->ThrowNew(exceptionClazz, "Unable to unlock mutex");
return;
}
}
...
72. antislashn.org Android avancé - NDK 72 / 97
POSIX Thread
● Lorsque le mutex n'est plus utilisé il doit être détruit
● ici dans la fonction native, invoquée par onDestroy()
de l'activité
...
void Java_org_antislashn_threads_MainActivity_nativeFree(JNIEnv *env, jobject thizz) {
...
if(pthread_mutex_destroy(&mutex) != 0){
jclass exceptionClazz = env->FindClass("java/lang/RuntimeException");
env->ThrowNew(exceptionClazz, "Unable to destroy mutex");
}
}
...
73. antislashn.org Android avancé - NDK 73 / 97
Fichier Android.mk
● Fichier de type makefile utilisé par la chaîne de build
● Permet de regrouper les sources en modules de type
librairie statique ou dynamique
● le fichier Android.mk peut définir plusieurs modules
● seules les librairies dynamiques peuvent être incluent
dans l'apk final
● Cf. la documentation de référence
● http://android.mk/
74. antislashn.org Android avancé - NDK 74 / 97
Fichier Android.mk
● Fichier minimal type
● LOCAL_PATH directive de début de fichier
– indique l'endroit où se trouve les sources
– my-dir renvoie le répertoire courant (celui où est Android.mk)
● CLEAR-VARS nettoie les varaiables LOCAL_XXX
– LOCAL_MODULE, LOCAL_SRC_FILE, …
– sauf LOCAL_PATH
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)
75. antislashn.org Android avancé - NDK 75 / 97
Fichier Android.mk
● LOCAL_MODULE définit le nom d'un module
● ce nom doit être unique, et ne doit pas contenir d'espace
– si le module est nommé foo, la librairie générée sera libfoo.so
● LOCAL_SRC_FILES liste des fichiers C/C++ qui seront
assemblés dans le module
● juste les fichier .c et .cpp
– séparés par un espace
● si les extensions sont différentes utiliser la variable
LOCAL_CPP_EXTENSION
76. antislashn.org Android avancé - NDK 76 / 97
Fichier Android.mk
● include $(BUILD_SHARED_LIBRARY)
● permet la génération de la librairie dynamique
● BUID_STATIC_LIBRARY génère une librairie statique
● LOCAL_STATIC_LIBRARIES ajoute des librairies
statiques au modules
77. antislashn.org Android avancé - NDK 77 / 97
Fichier Android.mk
● Génération de plusieurs librairies dynamiques
LOCAL_PATH := $(call my-dir)
#
# Module 1
#
include $(CLEAR_VARS)
LOCAL_MODULE := module1
LOCAL_SRC_FILES := module1.c
include $(BUILD_SHARED_LIBRARY)
#
# Module 2
#
include $(CLEAR_VARS)
LOCAL_MODULE := module2
LOCAL_SRC_FILES := module2.c
include $(BUILD_SHARED_LIBRARY)
78. antislashn.org Android avancé - NDK 78 / 97
Fichier Android.mk
● Génération de librairies statiques
LOCAL_PATH := $(call my-dir)
#
# 3rd party AVI library
#
include $(CLEAR_VARS)
LOCAL_MODULE := avilib
LOCAL_SRC_FILES := avilib.c platform_posix.c
include $(BUILD_STATIC_LIBRARY)
#
# Native module
#
include $(CLEAR_VARS)
LOCAL_MODULE := module
LOCAL_SRC_FILES := module.c
LOCAL_STATIC_LIBRARIES := avilib
include $(BUILD_SHARED_LIBRARY)
79. antislashn.org Android avancé - NDK 79 / 97
Fichier Android.mk
● Partager une librairie C/C++ entre plusieurs projets NDK
● les librairies à partager dont mises dans un répertoire
spécifique
– hors de tout projet NDK
● le nom du répertoire de la librairie correspond au nom du module
– avec leurs propres fichier .mk
● ils seront invoqués lors de la construction du projet
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := calculs
LOCAL_SRC_FILES := calculs.c
include $(BUILD_SHARED_LIBRARY)
contient
80. antislashn.org Android avancé - NDK 80 / 97
Fichier Android.mk
● Partager une librairie C/C++ entre plusieurs projets NDK
● le projet NDK qui utilise la librairie partagée déclare celle-
ci dans son fichier Android.mk
● il faut positionner la variable d'environnement
NDK_MODULE_PATH avant d'invoquer ndk-build
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := module
LOCAL_SRC_FILES := module.c
LOCAL_SHARED_LIBRARIES := calculs
include $(BUILD_SHARED_LIBRARY)
$(call import-module,calculs)
81. antislashn.org Android avancé - NDK 81 / 97
Android Studio - compilation automatique
● Par défaut Gradle construit les librairies .so si un
répertoires jni est présent
● la compilation est effectuée à la construction de l'apk
● les fichiers .mk ne sont pas nécessaires
– ils ne seront pas pris en compte
● les librairies sont construites dans le répertoire libs du
projet
– le nom par défaut de la librairie est app
● les fichiers générés pour les différentes cibles seront nommés : libapp.so
–
82. antislashn.org Android avancé - NDK 82 / 97
Android Studio - compilation automatique
● Il est aussi possible de paramétrer gradle pour
compiler et générer les fichiers .so au moment de la
création de l'apk
● dans le fichier build.gradle du module
– dans la section defaultConfig
● il faut que le répertoire du NDK soit positionné dans le
fichier local.properties
...
ndk {
moduleName "hello-jni"
}
...
ndk.dir=C:DEVELOPPEMENTSAndroidsdkndk-bundle
sdk.dir=C:DEVELOPPEMENTSAndroidsdk
83. antislashn.org Android avancé - NDK 83 / 97
Android Studio - compilation automatique
● Selon la version d'Android Studio il peut être
nécessaire d'ajouter dans le fichier gradle.properties
la ligne
● une erreur apparaît lors de la construction de l'apk
● L'ensemble des macros et variables du fichier make
sont configurables par gradle
android.useDeprecatedNdk=true
ndk {
moduleName "mymodule"
ldLibs "log"
stl "gnustl_static"
cFlags "-std=c++11 -fexceptions"
}
84. antislashn.org Android avancé - NDK 84 / 97
● Il faut désélectionner l'appel automatique de
ndk build‑ par gradle
● dans le fichier build.gradle
– annulation de la déclaration des répertoires JNI
Android Studio - par ndk-build
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
sourceSets{
main{
jniLibs.srcDirs "src/main/libs"
jni.srcDirs = []
}
}
...
85. antislashn.org Android avancé - NDK 85 / 97
Android Studio - par ndk-build
● Compilation dans le terminal
● ndk-build utilise make, il est nécessaire d'ajouter dans le
répertoire jni
– un fichier Android.mk pour l'unité de compilation
● cf. http://developer.android.com/ndk/guides/android_mk.html
– un fichier Application.mk pour les cibles
● cf. http://developer.android.com/ndk/guides/application_mk.html
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)
Android.mk
APP_ABI := all
Application.mk
86. antislashn.org Android avancé - NDK 86 / 97
Android Studio - par ndk-build
● Compilation dans le terminal
● dans le terminal
– se positionner dans le répertoire jni
– invoquer la commande ndk-build
● Les librairies des différentes cibles
sont générées dans le répertoire
main/libs
87. antislashn.org Android avancé - NDK 87 / 97
Android Studio - par script gradle
● Il est aussi possible d'automatiser l'appel de ndk build‑ par un
script gradle, dans le fichier build.gradle du module
● il faut éviter l'invocation automatique de ndk build‑
– dans android.sourceSets.main
● et ajouter en fin de fichier, en fait en dehors du bloc
android{...}
jni.srcDirs = []
task ndkBuild(type:Exec) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine 'ndk-build.cmd', '-C', file('src/main').absolutePath
} else {
commandLine 'ndk-build', '-C', file('src/main').absolutePath
}
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn ndkBuild
}
88. antislashn.org Android avancé - NDK 88 / 97
SWIG
● SWIG est un outil permettant de connecter du code
C/C++ avec d'autres langages
● ne fait pas partie d'Android
● n'est pas dédié à Java
● SWIG nécessite l'écriture d'une interface afin de
produire le code Java
● Site officiel : http://www.swig.org/
● installation sous windows, linux, Mac
89. antislashn.org Android avancé - NDK 89 / 97
SWIG - exemple de base
● Comme introduction à Swig nous allons utiliser la
fonction getuid
● cf. http://linux.die.net/man/2/getuid
● Etapes
● écrire l"interface SWIG
● généré le proxy Java
● ajouter les sources générées par SWIG dans Android.mk
● utiliser le proxy généré par SWIG
90. antislashn.org Android avancé - NDK 90 / 97
SWIG - interface
● Ce fichier déclare
● les prototypes de fonctions
● les déclarations de classes
● les déclarations de variables
● Syntaxe similaire à un header C/C++
● Le fichier interface s’appellera unix.i
● mis dans le répertoire jni
91. antislashn.org Android avancé - NDK 91 / 97
SWIG - interface
● Interface Unix.i
/* Module name is unix. */
%module unix
%{
/* Include the POSIX operating system APIs. */
#include < unistd.h>
%}
/* Tell SWIG about uid_t. */
typedef unsigned int uid_t;
/* Ask SWIG to wrap getuid function. */
extern uid_t getuid(void);
92. antislashn.org Android avancé - NDK 92 / 97
SWIG - génération du proxy Java
● Auparavant il faut créer le package java de la classe
proxy
● ici : org.antislashn.swig.proxy
● Puis lancer la commande swig
● Génération :
● un fichier Unix_wrap.c
● deux classes Java
– Unix.java
– UnixJNI.java
swig -java -package org.antislashn.swig.proxy -outdir java/org/antislashn/swig/proxy jni/unix.i
93. antislashn.org Android avancé - NDK 93 / 97
SWIG - utilisation
● Deux classes Java ont été générées
● L'utilisation est triviale
public class Unix {
public static long getuid() {
return UnixJNI.getuid();
}
}
public class UnixJNI {
public final static native long getuid();
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView) findViewById(R.id.uid);
tv.setText(Long.toString(Unix.getuid()));
}
}
95. antislashn.org Android avancé - NDK 95 / 97
Bibliographie
● Apress
● Pro Android C++ with th NDK
● Beginning Android C++ Game Developement
● Packt Publishing
● Android Native Development Kit Cookbook
● Android NDK
● Gradle for Android
96. antislashn.org Titre support 96 / 97
copyleft
Support de formation créé par
Franck SIMON
http://www.franck-simon.com
97. antislashn.org Titre support 97 / 97
copyleft
Cette œuvre est mise à disposition sous licence
Attribution
Pas d'Utilisation Commerciale
Partage dans les Mêmes Conditions 3.0 France.
Pour voir une copie de cette licence, visitez
http://creativecommons.org/licenses/by-nc-sa/3.0/fr/
ou écrivez à
Creative Commons, 444 Castro Street, Suite 900,
Mountain View, California, 94041, USA.