Setting up for Android NDK development

Android allows you to compile and execute native code written in C/C++ for increased computational performance. Getting the NDK up and running on the other hand can be a pain. This tutorial was put together using Android Studio 1.4.1 with the most recent API version 23. I'll also be using the older deprecated NDK plugin rather than the new experimental version.

Create an empty project

This is done by selecting Start a new Android Studio project in the Quick Start menu when you first launch android studio. Select an application name and company name (this will be used as the package name[1], so you company domain name should be used).

Next you have to select a minimum SDK or Android version which your application will support. As well as the Different form factors- Android Wear, TV and Auto.

Select a minimum api

Now just select the defaults for everything else, by hitting Next until the project is created.

Get the NDK

Now that you have a new project, you need to download the NDK using the SDK manager built into Android studio[1:1]. You can access this through

File > Settings > Appearance & Behavior > System Settings > Android SDK`

or from the tools menu

Tools > Android > SDK Manager

In the SDK tools tab, scroll down the bottom of the page and select Android NDK and then click apply to install.

Select android NDK and hit apply to install

Generate C/C++ header files

In order to invoke native C/C++ code in your application, you have to use the Java Native Interface (JNI)[1:2]. To begin, add a native method to your class file. This is an abstract method with the native keyword.

public static native String getStringFromNative();

Adding a native method

Save and build your project by selecting

Build > Make Project

This will compile and generate some necessary class files.

Android studio will mark this as an error because we don't have the corresponding JNI method defined yet. We fix this by first generating the appropriate C/C++ header files using the C Header and Stub File Generator - javah[1:3].

Using the terminal window at the bottom of the screen, move into the src/main/ directory using the cd command[1:4]

cd src/main

Next, invoke javah using the following command (Replace [path to sdk] and [package name] to match your own path). (Windows users replace forward slashes(/) with back slashes (\) and colon separators (:) with semi colons(;)).

javah -d jni -classpath [path to sdk]/platforms/android-23/android.jar:[path to sdk]/extras/android/support/v7/appcompat/libs/android-support-v7-appcompat.jar:[path to sdk]/extras/android/support/v7/appcompat/libs/android-support-v4.jar:../../build/intermediates/classes/debug [package name].MainActivity

Running this successfully with generate a folder called jni with a file [package name]_MainActivity.h. This is the generated header file.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_demoproject_MainActivity */

#ifndef _Included_com_example_demoproject_MainActivity
#define _Included_com_example_demoproject_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
#undef com_example_demoproject_MainActivity_BIND_ABOVE_CLIENT
#define com_example_demoproject_MainActivity_BIND_ABOVE_CLIENT 8L
#undef com_example_demoproject_MainActivity_BIND_ADJUST_WITH_ACTIVITY
#define com_example_demoproject_MainActivity_BIND_ADJUST_WITH_ACTIVITY 128L
#undef com_example_demoproject_MainActivity_BIND_ALLOW_OOM_MANAGEMENT
#define com_example_demoproject_MainActivity_BIND_ALLOW_OOM_MANAGEMENT 16L

/* ... lots of other generated statements here ... */

#define com_example_demoproject_MainActivity_MSG_REALLY_STOPPED 1L
#undef com_example_demoproject_MainActivity_MSG_RESUME_PENDING
#define com_example_demoproject_MainActivity_MSG_RESUME_PENDING 2L
/*
 * Class:     com_example_demoproject_MainActivity
 * Method:    getStringFromNative
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_demoproject_MainActivity_getStringFromNative
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

Ignoring the #define statements, this file contains a function prototype that matches the native method that we defined in our java code. We need to create a *.c file where we will write the function body for the method.

I created main.c in my jni folder with the following contents which simply returns a java string when the method is called.

#include "com_example_demoproject_MainActivity.h"

JNIEXPORT jstring JNICALL Java_com_example_demoproject_MainActivity_getStringFromNative
        (JNIEnv* env, jobject object){
   return (*env) -> NewStringUTF(env, "With great power, comes great responsibility.");
}

Configure Properties

At this point, trying to build the project will fail, because there is still a bit of configuration that needs to be done. First we need to configure Gradle to use the old currently deprecated NDK plugin.

Inside the grade.properties file, add the following property

android.useDeprecatedNdk=true

Configuring the gradle.properties

Next you need to configure the path to your NDK distribution in your local.properties file. If you used the SDK manager, this will probably be inside of your SDK in the ndk-bundle folder.

ndk.dir=[path to sdk]/ndk-bundle

Configuring the local.properties

At this point you should be able to build the project by selecting Build > Make project from the toolbar, however you're not quite ready to run it just yet.

Add the native lib to your app

Now we need to tell Gradle how we want our NDK project to be built.

Inside the application build.gradle file, we need to add an ndk configuration inside our default config. The moduleName property tells gradle what to name the compiled native library so we can reference it later.

ndk {
    moduleName "_demoproject"
}

Application build.gradle

Finally, we need to load the the native lib into our java code, this is done by adding a static initialization block with System.loadLibrary and a reference to your native lib.

Adding the library to java code

You can now invoke the native method in java code as though it were any other method in your program. For example, I'm calling my getStringFromNative() method in a Snackbar using the following code snippet.

Snackbar.make(view, getStringFromNative(), Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();

Running this code in the emulator gives me this as the result;

Native code running in the emulator

Footnotes and References

  1. Note that on windows the slash(/) is backwards (\) ↩︎ ↩︎ ↩︎ ↩︎ ↩︎