Android Native development Kit (NDK)

            The Native Development Kit (NDK) is a set of tools that allows you to use C and C++ code with Android, and provides platform libraries you can use to manage native activities and access physical device components, such as sensors and touch input. The NDK may not be appropriate for most novice Android programmers who need to use only Java code and framework APIs to develop their apps.

           With the launch of Android Studio 2.2, developing Android applications that contain C++ code has become easier than ever.In this tutorial, I'll show you how to use the Android Native Development Kit, which is usually referred to as just NDK, to create a native C++ library whose functions are available to Java classes.

Prerequisites

To be able to follow this tutorial, you will need the following:

- the latest version of Android Studio
- a basic understanding of C++ syntax


1. Why write native code?

As a rule of thumb, you would develop an Android application using only Java. Adding C++ code increases its complexity dramatically and also reduces its portability. Nevertheless, here are some reasons why you would still want to do it:

  • To maximize performance : You can improve the performance of an Android application, through only marginally, by implementing the CPU-intensive portions of its business logic in C++.
  • To use high-performance APIs : Implementation of API specifications such as Vulkan Graphics and OpenSL ES are a part of the NDK. Therefore, Android game developers tend to use the NDK.
  • To use popular C/C++ libraries : There are numerous C and C++ libraries out there that have no Java equivalents. If you want to work with them in your Android app, using the NDK is the way to go.
  • To reuse code : As long as it doesn't contain any platform-specific dependencies, code written in C++ can be used in both Android and iOS applications, usually with minimal changes. If you are developing a large application and intend to support both the iOS and Android platforms, using C++ might improve your productivity.


2. Creating a New Project

In Android Studio 2.2 or higher, the project creation wizard allows you to quickly create new projects that support C++ code.

Start by launching Android Studio and pressing the Start a new Android Studio project button in the welcome screen. In the next screen, give your application a meaningful name and check the Include C++ Support field.


In the activity creation screen of the wizard, choose the Add No Activity option. In the final screen of the wizard, make sure that the value of the C++ Standard field is set to Toolchain Default and press the Finish button.


The Android NDK and the tools it depends on are not installed by default. Therefore, once the project has been generated, you'll see an error that looks like this:


To fix the error, go to Tools > Android > SDK Manager and switch to the SDK Tools tab.

In the list of available developer tools, select both CMake and NDK, and press the Apply button.


Once the installation completes, restart Android Studio.


3. Creating a Native Library

An Android Studio project that supports C++ has an additional source code directory called cpp. As you might have guessed, all C++ files and libraries must be placed inside it. By default, the directory has a file called native-lib.cpp. For now, we'll be writing all our C++ code inside it.

In this tutorial, we'll be creating a simple native library containing a function that calculates the area of a circle using the formula pr². The function will accept the radius of the circle as a jdouble and return the area as a jstring.

Start by adding the following include directives to the file:

#include<jni.h>
#include<string>
#include<math.h>

jni.h is a header file containing several macro definitions, types, structures, and functions, all of which are indispensable while working with NDK.( JNI stands for Java Native Interface, and this is the framework that allows the Java Runtime to communicate with native code.) The string header file is necessary because we will be using the jstring type in our library. The math.h header file contains the value of π.

By default, in order to support polymorphism, the C++ compiler modifies the names of all the functions you define in your code. This feature is often referred to as name mangling. Due to name mangling, calling your C++ functions from Java code will lead to errors. To avoid the errors, you can disable name mangling by defining your functions inside an extern "C" block.

extern "C"
{
        // Your functions must be defined
        //  here
}

The names of C++ functions that are accessible via JNI must have the following format:

  • They must have a Java_ prefix.
  • They must contain a mangled form of the package name where the dots are replaced with underscores.
  • They must contain the name of the Java class they belong to.
Additionally, you must specify the visibility of the function. You can do so using the JNIEXPORT macro. By convention, most developers also include the JNICALL macro in the function definition, although it currently doesn't serve any purpose in Android.

The following code defines a function called calculateArea, which can be accessed from a Java class called MainActivity:

JNIEXPORT jstring JNICALL
Java_com_tutsplus_mynativeapplication_MainActivity_calculateArea(
        JNIEnv *jenv,
        jobject self,
        jdouble radius
}

Note that in addition to the radius, the function also accepts a JNIEnv type, which has utility functions you can use to handle Java types, and a jobject instance, which is a reference to an instance of MainActivity. We will, of course, be creating MainActivity later in this tutorial.

Calculating the area is easy. All you need to do is multiply the M_PI macro by the square of the radius.

jdouble area = M_PI * radius * radius;

Just so you know how to handle strings while working with JNI, let us now create a new string containing a message saying what the area is. To do so, you can use the sprintf() function.

char output[40];
sprintf(output, "The area is %f sqm", area);

Because Java cannot directly handle a C++ character array, our function's return type is jstring. To convert the output array into a jstring object, you must use the NewStringUTF() function.

return jenv->NewStringUTF(output);
At this point, our C++ code is ready.


4. Using the Native Library

In the previous step, you saw that the calculateArea() function needs to belong to the MainActivity Java class. Start creating the class by right-clicking on your Java package name and selecting File > New > Empty Activity.


                                             
The native library must be loaded before it can be used. Therefore, add a static block to the class and load the library using the loadLibrary() method of the System class.

static 
{
    System.loadLibrary("native-lib");
}

To be able to use the calculateArea() C++ function inside the activity, you must declare it as a native method.

private native String calculateArea(double radius);

You can now use the calculateArea() method like any ordinary Java method. For example, you can add the following code to the onCreate() method to calculate and print the area of circle whose radius is 5.5:

Log.d(TAG, calculateArea(5.5f));

If you run the app, you should be able to see the following output in the logcat window: