Intégrer des fichiers .so et du NDK Android avec Gradle/Android Studio

Xavier Hallade

Avec les changements récents (release 0.7.3 il y a deux semaines), le nouveau système de build d'Android devient vraiment intéressant aussi si vous utilisez le NDK!

Il est maintenant très simple d'intégrer des bibliothèques natives à votre application et générer des APK différents pour chaque architecture, tout en gérant correctement leur numéro de version (pour plus d'informations sur l'importance de ceci, vous pouvez vous référer à mon précédent article).

N'hésitez pas à commenter cet article ! Commentez

- Titre : Intégrer des fichiers .so et du NDK Android avec Gradle/Android Studio

- Auteur : Xavier Hallade

- Parution : 21 janvier 2014

- Licence : Copyright ® 2014 Intel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Intégrer des fichiers .so dans votre APK

- II. Créer un APK par architecture, intelligemment

- III. Paramétrer un numéro de version différent pour de multiples APK en fonction de leur architecture cible

- IV. Appeler (ou non) ndk-build depuis Android Studio

- V. Mon fichier .gradle

- VI. Problèmes courants

- VI-A. Impossible de lancer ndk-build

- VI-B. Le NDK n'est pas configuré

- VI-C. Autres problèmes

- VII. Obtenir plus d'informations

- VIII. Remerciements

I - Intégrer des fichiers .so dans votre APK

Si vous utilisez déjà Android Studio et aviez besoin d'intégrer des bibliothèques natives à votre application, vous avez certainement utilisé des méthodes complexes pour cela (impliquant Maven et des paquets .aar/.jar…). La bonne nouvelle est que vous n'avez plus besoin de tout ceci !

Vous avez seulement besoin de placer vos fichiers .so sous le dossier jniLibs, dans des sous-dossiers nommés en fonction des ABI supportées (x86, mips, armeabi-v7a, armeabi), et c'est tout !

Une fois ceci fait, tous les fichiers .so seront intégrés dans votre APK lorsque vous lancerez sa création.

Si le nom de dossier par défaut jniLibs ne vous convient pas (peut-être générez-vous vos bibliothèques ailleurs), vous pouvez le modifier explicitement dans votre fichier build.gradle :

android {
...
sourceSets.main {
jniLibs.srcDir 'src/main/libs'
}
}

II - Créer un APK par architecture, intelligemment

Vous pouvez utiliser les « flavors » pour créer un APK par architecture de manière très simple, en passant par la propriété abiFilter.

Par défaut, les ABI ne sont pas filtrés. ndk.abiFilter(s) a un impact sur l'intégration des .so comme sur les appels à ndk-build (cette partie est traitée plus loin dans cet article).

Ajoutons donc quelques « flavors » correspondant aux architectures que l'on veut supporter, dans build.gradle :

android{
...
productFlavors {
x86 {
ndk {
abiFilter "x86"
}
}
mips {
ndk {
abiFilter "mips"
}
}
armv7 {
ndk {
abiFilter "armeabi-v7a"
}
}
arm {
ndk {
abiFilter "armeabi"
}
}
fat
}
}

Puis, synchronisons le projet pour prendre en compte ce changement :

Vous devriez maintenant pouvoir profiter de ces nouvelles « flavors » en sélectionnant la variante souhaitée :

Chacune de ces variantes vous permettra de produire un APK pour l'architecture désignée :

app-x86-release-unsigned-apk

La variante fat(Release|Debug) contiendra encore toutes les bibliothèques, de la même manière que l'APK classique construit au début de cet article.

Mais n'arrêtez pas votre lecture ici ! Ces APK dépendant de l'architecture sont utiles lorsque vous développez, mais si vous voulez les déployer aux utilisateurs, il vous faut spécifier un numéro de version (android:versionCode) différent pour chacun. Grâce au nouveau système de build, ceci est maintenant très simple.

III - Paramétrer un numéro de version différent pour de multiples APK en fonction de leur architecture cible

La propriété android.defaultConfig.versionCode contient le versionCode de votre application. Elle contient par défaut -1, et si vous ne la changez pas, le versionCode configuré dans votre AndroidManifest.xml sera finalement utilisé.

Ainsi, si vous voulez être capable de la modifier dynamiquement, vous devez d'abord la configurer dans votre fichier build.gradle :

android {
...
defaultConfig{
versionName "1.1.0"
versionCode 110
}
}

Il est toujours possible de conserver sa configuration dans le fichier AndroidManifest.xml si vous le souhaitez, il suffit de l'y récupérer « manuellement » :

import java.util.regex.Pattern

android {
...
defaultConfig{
versionCode getVersionCodeFromManifest()
}
...
}

def getVersionCodeFromManifest() {
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
def matcher = pattern.matcher(manifestFile.getText())
matcher.find()
return Integer.parseInt(matcher.group(1))
}

Une fois ceci fait, vous pouvez préfixer ce versionCode au sein de vos différentes « flavors » :

android {
...
productFlavors {
x86 {
versionCode Integer.parseInt("6" + defaultConfig.versionCode)
ndk {
abiFilter "x86"
}
}
mips {
versionCode Integer.parseInt("4" + defaultConfig.versionCode)
ndk {
abiFilter "mips"
}
}
armv7 {
versionCode Integer.parseInt("2" + defaultConfig.versionCode)
ndk {
abiFilter "armeabi-v7a"
}
}
arm {
versionCode Integer.parseInt("1" + defaultConfig.versionCode)
ndk {
abiFilter "armeabi"
}
}
fat
}
}

Ainsi dans cette configuration, j'ai ajouté un 6 pour x86, 4 pour mips, 2 pour ARMv7 et 1 pour ARMv5. Si vous vous demandez pourquoi !? Référez-vous à ce paragraphe que j'ai écrit avant sur la gestion de plusieurs APK dépendant de l'ABI.

IV - Appeler (ou non) ndk-build depuis Android Studio

Si vous avez un dossier jni/ dans les sources de votre projet, le système de build essaiera automatiquement d'appeler ndk-build.

Dans la version 0.7.3, cette intégration marche uniquement pour des systèmes compatibles Unix, cf. bug 63896. Sur Windows vous voudrez donc désactiver cette fonctionnalité pour pouvoir appeler ndk-build.cmd vous-même. Vous pouvez faire cela en le configurant dans build.gradle :

android{
...
sourceSets.main.jni.srcDirs = [] //disable automatic ndk-build call
}

Si vous utilisez un système compatible Unix, vous pouvez utiliser l'intégration de ndk-build et même aller plus loin en supprimant vos fichiers *.mk. Pour cela vous aurez besoin d'au moins configurer la propriété ndk.moduleName comme ceci :

android {
...
defaultConfig {
ndk {
moduleName "hello-jni"
}
}
}

Vous pouvez aussi configurer d'autres propriétés du NDK :

•cFlags ;

•ldLibs ;

•stl (ex. : gnustl_shared, stlport_static…) ;

•abiFilters (ex. : « x86 », « armeabi-v7a »).

Ainsi que mettre android.buildTypes.debug.jniDebugBuild à true pour passer NDK_DEBUG=1 à ndk-build lorsque vous générez un APK de débogage.

Si vous utilisez RenderScript depuis le NDK, vous devrez mettre la propriété spécifique defaultConfig.renderscriptNdkMode à true.

Ne configurez moduleName et autres uniquement que si vous ne souhaitez plus utiliser vos fichiers *.mk files anymore. Dans ce cas vous ne pourrez plus gérer différents cFlags selon l'architecture cible si vous créez des APK pour plusieurs architectures à la fois. Donc si vous voulez utiliser gradle uniquement, je vous recommande de générer un APK par architecture, avec les cFlags adéquats, en utilisant les « flavors » ainsi que décrit plus tôt dans cet article :

...
productFlavors {
x86 {
versionCode Integer.parseInt("6" + defaultConfig.versionCode)
ndk {
cFlags cFlags + " -mtune=atom -mssse3 -mfpmath=sse"
abiFilter "x86"
}
}
...

V - Mon fichier .gradle

En conclusion de ce que j'ai expliqué précédemment, voici le fichier build.gradle que j'utilise actuellement. Il spécifie une « flavor » par architecture supportée, n'utilise pas l'intégration à ndk-build, et ne requiert aucun changement d'emplacement des sources et bibliothèques (sources dans jni/, libs dans libs/) ni du contenu de mes fichiers *.mk:

import java.util.regex.Pattern

buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.7.3'
}
}

apply plugin: 'android'

android {
compileSdkVersion 19
buildToolsVersion "19.0.1"

defaultConfig{
versionCode getVersionCodeFromManifest()
}

sourceSets.main {
jniLibs.srcDir 'src/main/libs'
jni.srcDirs = [] //disable automatic ndk-build call
}

productFlavors {
x86 {
versionCode Integer.parseInt("6" + defaultConfig.versionCode)
ndk {
abiFilter "x86"
}
}
mips {
versionCode Integer.parseInt("4" + defaultConfig.versionCode)
ndk {
abiFilter "mips"
}
}
armv7 {
versionCode Integer.parseInt("2" + defaultConfig.versionCode)
ndk {
abiFilter "armeabi-v7a"
}
}
arm {
versionCode Integer.parseInt("1" + defaultConfig.versionCode)
ndk {
abiFilter "armeabi"
}
}
fat
}
}

def getVersionCodeFromManifest() {
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
def matcher = pattern.matcher(manifestFile.getText())
matcher.find()
return Integer.parseInt(matcher.group(1))
}

VI - Problèmes courants

VI-A - Impossible de lancer ndk-build

Si vous obtenez ce genre d'erreur :

java.io.IOException: Cannot run program « C:\Android
dk
dk-build »: CreateProcess error=193, %1 is not a valid Win32 application

Cela signifie que l'intégration du ndk appelle ndk-build mais ceci ne marche pas encore sous Windows (ndk-build est un script shell unix, non un exécutable, il devrait appeler ndk-build.cmd à la place).

Vous pouvez désactiver ce comportement en supprimant le contenu de jni.srcDirs :

sourceSets.main.jni.srcDirs = []

VI-B - Le NDK n'est pas configuré

Si vous obtenez ce genre d'erreur :

Execution failed for task ':app:compileX86ReleaseNdk'.
> NDK not configured

Cela signifie que les outils n'ont pas trouvé le répertoire du NDK. Vous avez deux manières de corriger cela : configurer la variable d'environnement ANDROID_NDK_HOME en y mettant l'emplacement de votre dossier NDK et supprimer local.properties, ou le configurer manuellement dans le fichier local.properties :

ndk.dir=C\:\\Android\\ndk

VI-C - Autres problèmes

Vous pourrez trouver de l'aide sur le forum officiel des outils développeurs pour Android : https://groups.google.com/forum/#!forum/adt-dev.

VII - Obtenir plus d'informations

Le meilleur endroit pour approfondir le sujet est la page officielle du projet : http://tools.android.com/tech-docs/new-build-system.

Vous pouvez y examiner le changelog et y trouver en bas de page un lien vers une archive remplie d'exemples de projets utilisant gradle et le NDK, nommée « gradle-samples-XXX.zip ».

VIII - Remerciements

Merci à Intel Developer Zone et à Xavier Hallade pour la rédaction de ce tutoriel.

Retrouvez le Blog de Xavier Hallade, Software Engineer chez Intel.

Pour toute question, merci de contacter Slim Soussi, EMEA Program Manager chez Intel : slim(pt)soussi(at)intel(pt)com