NDK support requires the use of Android NDK (r10e or above) and the android gradle-experimental plugin (0.7.0-alpha1). NDK support for the gradle(-stable) plugin, check this article on the NDK and Android Studio. This user guides provides general details on how to use it and highlights the difference between the experimental plugin and the original plugin. (This guide is based on the gradle-experimental 0.7.0 and Android Studio 2.1)
A typical Android Studio project may have a directory structure as follows. Files that we need to change are marked with a "*":
.
├── app/
│ ├── app.iml
│ ├── build.gradle*
│ └── src/
├── build.gradle*
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties*
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
├── MyApplication.iml
└── settings.gradle
The gradle-experimental 0.7.0-alpha1 pluging requires the gradle-2.10 use. Start by setting the project gradle/wrapper/gradle-wrapper.properties:
#Wed Apr 10 15:27:10 PDT 2013
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
We also need to change the reference to the android gradle plugin to the gradle-experimental plugin, in the ./build.gradle file:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle-experimental:0.7.0'
}
}
The new experimental plugin is based on Gradle’s new component model mechanism(DSL), while allows significant reduction in configuration time. There are some significant changes in the DSL between the new plugin and the traditional one. But, it also includes NDK integration for building JNI applications. The android plugins com.android.model.application and com.android.model.library are replacing the former com.android.application and com.android.library plugins.
We should migrate apps and libs build.gradle files to use these new plugins. Here is an example of the same configuration, with the original DSL and the experimental:
apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "com.ph0b.example"
minSdkVersion 15
targetSdkVersion 23
versionCode 4
versionName "1.0.1"
ndk {
moduleName "yourmodule"
ldLibs "log"
stl "gnustl_static"
cFlags "-std=c++11 -fexceptions"
}
}
signingConfigs {
release {
storeFile file(STORE_FILE)
storePassword STORE_PASSWORD
keyAlias KEY_ALIAS
keyPassword KEY_PASSWORD
}
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.txt'
signingConfig signingConfigs.release
}
debug {
jniDebuggable true
}
}
}
apply plugin: 'com.android.model.application'
model {
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "com.ph0b.example"
minSdkVersion.apiLevel 15
targetSdkVersion.apiLevel 23
versionCode 4
versionName "1.0.1"
}
}
android.ndk {
moduleName "mymodule"
ldLibs.addAll(['log'])
cppFlags.add("-std=c++11")
cppFlags.add("-fexceptions")
platformVersion 15
stl 'gnustl_shared'
}
android.signingConfigs {
create("release") {
keyAlias KEY_ALIAS
keyPassword STORE_PASSWORD
storeFile file(STORE_FILE)
storePassword KEY_PASSWORD
}
}
android.buildTypes {
release {
shrinkResources true
useProguard true
proguardFiles.add(file('proguard-rules.txt'))
signingConfig = $("android.signingConfigs.release")
}
}
}
To summarize the changes required:
- all the android declarations are now going under model {}
- assignments now have to use explicitly ‘=‘
- collections must not be overwritten instead we should use .removeAll(), .add(), .addAll()
- variants and other new configurations have to be declared using ‘create()‘
- properties like xxxSdkVersion have changed to xxxSdkVersion.apiLevel
We notice that in both DSLs, there is a configuration block for the NDK. This is where we should set all the NDK configuration when using gradle, because by default Android.mk and Application.mk files will be simply ignored.
To activate the NDK support inside Android Studio, you only need to have a NDK module declared inside your application or library build.gradle:
model {
android {
//...
ndk {
moduleName "mymodule"
}
}
}
Once it is done, in the Java sources, we can create a method prefixed with the native keyword then press ALT+Enter to generate its C or C++ implementation.
The implementation will be added under "jni" directory, inside an existing cpp file if there is one, or inside a new one.
In order to get started with NDK modules, ported samples to use the new gradle-experimental plugin can be found at: https://github.com/googlesamples/android-ndk
Here is everything you can configure for a ndk module:
android.ndk {
moduleName = "mymodule"
ldLibs.addAll(['log'])
ldFlags.add("")
toolchain = "clang"
toolchainVersion = "3.9"
abiFilters.add("x86")
CFlags.add("")
cppFlags.add("")
debuggable = false
renderscriptNdkMode = false
stl = "system"
platformVersion = 15
}
Since 0.7.0, you can also add ABI-specific configurations:
android.abis {
create("x86") {
cppFlags.add('-DENABLE_SSSE3')
ldLibs.add('')
ldFlags('')
}
create("armeabi-v7a") {
cppFlags.addAll(["-mhard-float", "-D_NDK_MATH_NO_SOFTFP=1", "-mfloat-abi=hard"])
ldLibs.add("m_hard")
ldFlags.add("-Wl,--no-warn-mismatch")
}
}
We have to create and use a new Run/Debug configuration from the "Android Native" default to get the Android Studio debug capabilities. This debug variant will have the ndk.debuggable flag set to true by default.
Many advanced features, such as the ability to have dependencies between native libraries, reuse prebuilts, tune specific toolchain options, and having dynamic version codes while still having our project in a good shape. These features are a bit complex to achieve, as the gradle-experimental plugin is still undergoing a lot of improvements across versions.
When you’re building a NDK module, the android platform you’re compiling it against is a quite important setting, as it basically determines the minimum platform your module will be guaranteed to run on.
With earlier versions than gradle-experimental:0.3.0-alpha4, the chosen platform was the one set as compileSdkVersion. Fortunately with subsequent releases, you can now set android.ndk.platformVersion independently, and you should make it the same as your minSdkVersion.
-- with sources If you have access to your 3rd party libraries source code, you can embed it into your project and make it statically compile with your code.
There is an example of this with the native_app_glue library from the NDK, inside the native-activity sample. For example, you can copy the library sources inside a subfolder inside your jni folder and add a reference to its directory so the includes are properly resolved:
android.ndk {
//...
cppFlags.add('-I' + file("src/main/jni/native_app_glue"))
}
-- with sources in different modules From 0.6.0-alpha7 version, you can finally have clean dependencies between native libraries, by setting the dependency on another module from your model: build.gradle
android.sources {
main {
jni {
dependencies {
project ":yourlib" buildType "release" productFlavor "flavor1" linkage "shared"
}
}
}
}
In order to keep debugging working, you may have to edit your app-native run configuration, to add /build/intermediates/binaries/release/obj/[abi] to the symbol directories.
--with native prebuilts This technique works with static and shared prebuilts too! Inside your model, you’ll have to add a “lib repository”:
repositories {
libs(PrebuiltLibraries) {
yourlib {
headers.srcDir "src/main/jni/prebuilts/include"
binaries.withType(SharedLibraryBinary) {
sharedLibraryFile = file("src/main/jni/prebuilts/${targetPlatform.getName()}/libyourlib.so")
}
}
}
}
And declare the dependency on this library:
android.sources {
main {
jni {
dependencies {
library "yourlib" linkage "shared"
}
}
}
}
Shared linkage is the default, but of course you can use static prebuilts by using a static linkage, and declaring StaticLibraryBinary/staticLibraryFile variables.
When having dependencies on shared libraries, you need to make sure to integrate these libs inside your APK. It will be the case if they’re under jniLibs, otherwise you can add them manually:
android.sources {
main {
jniLibs {
source {
srcDir "src/main/jni/prebuilts"
}
}
}
}
When we publish multiple APKs (for instance, per architecture), we have to give them different version Version Codes. Even using splits or abiFilters and flavors is not possible, yet. The good news is we can mix the use of the stable gradle plugin, and the new experimental one, even while keeping debug features working! Please follow this gist.
If the built-in gradle support isn’t suitable to your needs, you can get rid of it, while keeping the goodness of Android Studio C++ editing.
Declare a module that correctly represents your configuration, as this will help AS to correctly resolve all the symbols you’re using and keep the editing capabilities:
android.ndk { // keeping it to make AS correctly support C++ code editing
moduleName "mymodule"
ldLibs.add('log')
cppFlags.add('-std=c++11')
cppFlags.add('-fexceptions')
cppFlags.add('-I' + file("src/main/jni/prebuilts/include"))
stl = 'gnustl_shared'
}
Then, set the jniLibs location to libs, the default directory in which ndk-build will put the generated libs, and deactivate built-in compilation tasks:
model {
//...
android.sources {
main {
jniLibs {
source {
srcDir 'src/main/libs'
}
}
}
}
tasks.all {
task ->
if (task.name.startsWith('compile') && task.name.contains('MainC')) {
task.enabled = false
}
if (task.name.startsWith('link')) {
task.enabled = false
}
}
This way, we call ndk-build(.cmd) from the root of the src/main directory. ndk-build will use usual Android.mk/Application.mk files under the jni folder, the libs will be generated inside libs/ as usual and get included inside your APK.
We can also add the call to ndk-build in gradle configuration so it’s done automatically:
import org.apache.tools.ant.taskdefs.condition.Os
model {
...
android.sources {
main {
jniLibs {
source {
srcDir 'src/main/libs'
}
}
}
}
// call regular ndk-build(.cmd) script from app directory
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.all {
task ->
if (task.name.startsWith('compile') && task.name.contains('MainC')) {
task.enabled = false
}
if (task.name.startsWith('link')) {
task.enabled = false
}
if (task.name.endsWith('SharedLibrary') ) {
task.dependsOn ndkBuild
}
}