top of page
  • admin

Using the Android NDK with Android Studio- Part 2

Our Android Studio / NDK series continues with details on how to create a Gradle-oriented NDK project that references statically compiled native libraries, a feature currently unsupported by the current Gradle plugin.

Using an NDK compilation process with pre-built native libraries

In part 1 of this series, I laid out the basic steps to implement a basic NDK module in Android Studio using the built-in Gradle Plugin NDK support. At the outset, however, I mentioned that my client needed to utilize PRE-BUILT (*.a) libraries containing proprietary code. Normally, this would be as simple as just adding a directive to the file, but since Gradle automatically generates the file deep in the .idea folder, we have less control over how that file is written and it’s relative location. While we are awaiting on Google for a more elegant solution to this issue, I had to move Android Studio back to an older NDK compilation model using the makefile.

The first thing I did was put some of my local directories in a properties file so that I could add them individually to source control and let other developers on my team install the ndk and keystores where they wanted:

properties.gradle (main)

Next, I updated the build.gradle to ‘hide’ the jni files from the automatic NDK build so that I could run my custom ndk-build command (and utilize the makefile to get finer control over how the build will run).

NOTE: You won’t be able to see your jni folder in the ‘Android’ project view in Android Studio, but the tradeoff (successful compilation and linking) is worth it… I tried experimenting with moving around the jni.srcDirs variable during compilation time but Android Studio didn’t fall for my cheap tricks...

build.gradle (complex module)
apply from: '../properties.gradle'
apply plugin: ''

android {
    compileSdkVersion 19
    buildToolsVersion "20.0.0"

    defaultConfig {
        applicationId ""
        minSdkVersion 16
        targetSdkVersion 19
        versionCode 1
        versionName "1.0"

        // We need to set the libs dir for the output and the srcdirs to null to 
        // prevent the gradle ndk hooks from firing.  We need to depend on
        // NOTE: this appears to break the android studio 'android' project view 
 // (at least, it hides the jni directory…) 
        sourceSets.main {
	     //Tell Gradle where to put the compiled library
            jniLibs.srcDir 'src/main/libs'

	     //hide the ‘jni’ folder so that the automatic gradle build doesn’t try to run 
     //it’s own ndk-build process
            jni.srcDirs = [];

    //Signing your application consistently with a keystore that you control 
    //is a great habit to form
    signingConfigs {
        mySigningConfig {
            keyAlias '<keyalias>'
            keyPassword '<keypassword>'
	     //pull the keystore path from properties.gradle
            storeFile file(keystore_path)
            storePassword '<storepassword>'

    buildTypes {
        release {
            runProguard false
            signingConfig signingConfigs.mySigningConfig
            proguardFiles getDefaultProguardFile('proguard-android.txt'), ''

        debug {
            signingConfig signingConfigs.mySigningConfig

    // Tell Gradle the run the ndkBuild task when compiling
    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn ndkBuild

    // This task utilizes the file defined in src/main/jni so that you 
    // have more control over the build parameters (like library inclusion)
    task ndkBuild(type: Exec) {
        commandLine ndk_build_path, '-C', file('src/main/jni').absolutePath

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile project(':usbSerialForAndroid') // <- this is a very handy library… Contact us 
//     if you need to talk to a USB device 
//     in Android

Now that I’m depending on an file to build my NDK project, I have to make one:

(If you’re pulling in a legacy project, you probably already have this and is why you’re reading this article…) (main/src/jni)
LOCAL_PATH := $(call my-dir)

### Pull in a pre-built library
include $(CLEAR_VARS)
LOCAL_MODULE    := libPrecompiledLib

# this libs path is relative to my jni files, so, src/main/jni/libs/libPrecompiledLib.a
LOCAL_SRC_FILES := libs/$(TARGET_ARCH_ABI)/libPrecompiledLib.a

include $(CLEAR_VARS)

### Pull in our local code

# Recognize these from our ndk block in the simple build.gradle?
LOCAL_CFLAGS += -std=c99

## You can (unadvisedly) mix in cpp with your c code…  I’m doing it here because 
## my client’s pre-built headers and structs were in cpp but I want to keep the JNI
## pieces in c...
SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)

# link our new library to the static library we referred to a few lines back

## Pull in some android helper libraries
LOCAL_LDLIBS := -llog -landroid


There you go, sync up your gradle files and build your library to get a fully linked native library available to your application.

In part 3 of this series, I will discuss Using Binder and Messenger to create an asynchronous native process and consuming it with a standard Android activity.

Ben Friedberg, Lead Software Engineer

87 views0 comments

Recent Posts

See All


bottom of page