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 !

Image non disponible

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

Image non disponible

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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

Image non disponible

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

Image non disponible

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

Image non disponible
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 :

 
Sélectionnez
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 » :

 
Sélectionnez
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 » :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
...
  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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
sourceSets.main.jni.srcDirs = []

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

Si vous obtenez ce genre d'erreur :

 
Sélectionnez
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 :

 
Sélectionnez
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