Understanding how NDK works
Whether you had a clean installation using TADP or followed the manual setup steps, at this stage you should have all the needed components to develop vision-aware Android applications.
Before we move forward to our first example, let's first elaborate on how NDK works. It's always a good idea to familiarize yourself with the basics of Android NDK and be comfortable using it as it will be a cornerstone to our development of Android applications using OpenCV.
An overview of NDK
If you decided to compile the native part of your Android application using the command prompt, you must have used the ndk-build
tool. The ndk-build
tool is actually a script that launches different build scripts that are responsible for the following:
- It automatically searches your project to decide on what to build
- Once the search is done, the scripts start generating binaries and managing dependencies
- It copies the generated binaries to your project path
Besides the ndk-build
tool, there are a few other main components that you should be familiar with, including the following:
- Java and native calls: Android applications are written in Java, and once the source code is compiled, it is transformed to bytecode so that the Android OS runs under the Dalvik or Android Runtime (ART) virtual machine.
Note
Note that the applications that execute the native code are tested only on a Dalvik virtual machine.
When you are using methods implemented in native code, you should use the
native
keyword.For example, you could declare a function that multiplies two numbers and instructs the compiler that it is a native library:
public native double mul(double x, double y);
- Native shared libraries: NDK builds these libraries with an extension,
.so
. As the name suggests, these libraries are shared and linked in runtime. - Native static libraries: NDK also builds these libraries with an extension,
.a
; these kind of libraries are actually linked at the compile time. - Java Native Interface (JNI): As you write your Android application in Java, you need a way to channel your calls to the native libraries written in C/C++ and that's where the JNI comes in handy.
- Application Binary Interface (ABI): It is the interface that defines how your application machine code should look as you can run your application on different machine architectures. By default, NDK builds your code for ARM EABI; however, you can also select it to be built for MIPS or x86.
- Android.mk: Think of this file as a Maven build script or better, a make file, which instructs the
ndk-build
script about the definitions of the module and its name, the source files that you need to compile, and also the libraries that you need to link. It is very important to understand how to use this file and we will come back to it later for more details. - Application.mk: It is optional to create this file and it is used to list the modules that your application requires. This information can include ABIs to generate machine code for a specific target architecture, toolchains, and standard libraries.
With these components in mind, you can summarize the general flow of developing native applications for Android as follows:
- Decide which parts will be written in Java and which parts will be written in native C/C++.
- Create an Android application in Eclipse.
- Create an
Android.mk
file to define your module, list the native source code files to be compiled, and enumerate the linked libraries. - Create
Application.mk
; this is optional. - Copy your
Anrdoid.mk
file under thejni
folder in your project path. - Build the project using Eclipse. As we linked Eclipse to the installed NDK, the
ndk-build
tool will compile the.so
,.a
libraries, your Java code will be compiled to the.dex
files, and everything will be packaged in one single APK file and ready to be installed.
A simple example of NDK
As you will be developing Android applications with native support, you will need to be familiar with the general structure of a typical Android application using NDK.
Usually, your Android application has the following folder structure. The project root
folder has the following subdirectories:
jni/
libs/
res/
src/
AndroidManifest.xml
project.properties
Here, the NDK-related folders are as follows:
- The
jni
folder will contain the native part of your application. In other words, this is the C/C++ source code with the NDK build scripts such asAndroid.mk
andApplication.mk
, which are needed to build the native libraries. - The
libs
folder will contain the native libraries after a successful build.Note
The NDK build system requires both the
AndroidManifest.xml
andproject.properties
files to compile the native part of your application. So, if any of these files are missing, you will need to compile your Java code first before compiling the C/C++ code.
Android.mk
In this section, I'll describe the syntax of the Android.mk
build file. As mentioned before, Android.mk
is actually a GNU makefile fragment that the build system parses to know what to build in your project. The syntax of the file allows you to define modules. A module is one of the following:
- A static library
- A shared library
- A standalone executable
You already used ndk-build
to build the hello-jni
project, so let's take a look at the contents of this project Android.mk
file:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.c include $(BUILD_SHARED_LIBRARY)
Now, let's go through these lines one by one:
LOCAL_PATH := $(call my-dir)
: Here, the script defines a variable calledLOCAL_PATH
and sets its value by calling themy-dir
function, which returns the current working directory.include $(CLEAR_VARS)
: In this line, the script includes another GNU makefile calledCLEAR_VARS
to clear all the local variables—variables starting withLocal_XXX
with the exception ofLOCAL_PATH
. This is needed because the build files are parsed in a single-make execution context where all the variables are declared as global.LOCAL_MODULE := hello-jni
: Here, the script defines a module calledhello-jni
. TheLOCAL_MODULE
variable must be defined and unique to identify each module inAndroid.mk
.Note
The build system will add the
lib
prefix and.so
suffix to your defined modules. In the example case, the generated library will be namedlibhello-jni.so
.LOCAL_SRC_FILES := hello-jni.c
: As the name suggests, you will list all the source files that you need be built and assembled in one module.Note
You only list the source files and not the header files; it is the responsibility of the build system to compute the dependency for you.
include $(BUILD_SHARED_LIBRARY)
: Here we are including another GNU makefile, which will collect all the information that you defined after the lastinclude
command and decide what to build and how to build your module.