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, though only marginally, by implementing the CPU-intensive portions of its business logic in C++.
- To use high-performance APIs: Implementations 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 πr². 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.
In the dialog that pops up, name the activity MainActivity. After making sure that the Launcher Activity option is checked, press the Finish button.
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:
Conclusion
In this tutorial, you learned how to create a native C++ library and use it in an Android application. It is worth noting that the native build process, by default, generates a separate .so file for every single CPU architecture the NDK supports. Therefore, you can be sure that your application will run on most Android devices without any issues.
To learn more about the Android NDK, I suggest you refer to the NDK Guide.
And check out some of our other tutorials and courses on Android development!