Kotlin has already been here for a while. Over the past few years, this language has managed to win the trust and loyalty of many web and mobile developers around the globe.

has become especially popular in the community. It’s relatively easy to learn and, thanks to its concise and flexible nature, Kotlin makes it possible for us – developers – to build “healthy and performant” apps quickly. Finally (and perhaps most importantly), Kotlin can work together with Java and existing tools. This is exactly the topic we’re going to dive into today.

While there are a number of articles that can give you a sense for what the Android Native Development Kit (NDK) is and how to use it when writing an Android app in Java, it’s very hard to find information on how to use the Android NDK when building Android apps in Kotlin. So I ventured a little experiment…  

This post is for developers who want to get started with Android NDK and want to know all the niceties of using it for developing in Kotlin.

To see the whole picture, I suggest we start with the theoretical part. Then we’ll proceed to the practical part, with a detailed sample project written in Kotlin.

What’s the Android NDK?

The Android Native Development Kit (NDK) is a set of tools that lets developers write parts of their apps in native code (C/C++), squeezing more performance out of devices and achieving better app performance.

Why do we need the Android NDK?

The NDK may significantly improve application performance, especially for processor-bound applications. Many multimedia applications and games use native code for processor-intensive tasks. There are three reasons why C/C++ can offer performance improvements:

  • C/C++ code is compiled to binary that runs directly on the operating system, while Java code is compiled to Java bytecode and executed by the Java Virtual Machine.

  • Native code allows developers to make use of certain processor features that are not accessible via the Android SDK.

  • Critical code can be optimized at the assembly level.

Used in conjunction with the Android SDK, the NDK toolset provides to platform libraries that app developers can use to manage native activities and physical device components such as sensors and touch input. It’s also possible to use your own libraries or to use popular C/C++ libraries that have no equivalents in Java (such as the ffmpeg library written in C/C++ to process audio and video or the jnigraphics library to process bitmaps).

What exactly does the NDK include?

The NDK’s default tools are a debugger, CMake, and the Java Native Interface (or JNI), which does all the heavy lifting – handles the interaction between Java and native code.

The JNI defines how managed code (written in Java or Kotlin) communicates with native code (written in C/C++). How does this happen? Both managed code and native code have functions and variables. The JNI makes it possible to call functions written in C/C++ from Java or Kotlin, and vice versa. It also lets us read and change values stored in variables across languages.

“Why can we apply the JNI to both Java and Kotlin?” you may ask. The answer is pretty simple: Java and Kotlin are interoperable to such an extent that they are compiled to the same bytecode. In fact, the JNI’s task is not to manage the interaction between Java/Kotlin and C/C++, but to manage the interaction between this bytecode and the native language. Since we always get the same bytecode regardless of which high-level language we compile, we can apply JNI to both Java and Kotlin.

Now let’s look at the NDK in action.

Getting started

Let’s create a new Kotlin project (as of the time of writing, the current version of Android Studio is 2.3.2). To set up your project, do the following:

  • Download NDK, LLDB (a software debugger), and CMake using the SDK Manager.

  • Include C++ support using the checkbox on the New Project screen.

how to use android NDK: interaction between C/C++ and Kotlin

  • If you want to use some features like lambda or delegating constructors, you’ll have to use C++11. To do so, choose the appropriate item from the drop-down list on the Customize C++ support screen.

how to use android NDK: interaction between C/C++ and Kotlin 2

Otherwise, the process of creating a new project in Kotlin is the same as with Java. To adjust your project to use Kotlin, you have to install a special Kotlin plugin. After that, press Ctrl+Shift+a and input the “Configure Kotlin in Project” command. To convert any Java file to Kotlin, open it and use the “Convert Java file to Kotlin” command.

Exploring the project structure

If all previous steps have been done successfully, you’ll have a project with the following structure:

how to use android NDK: interaction between C/C++ and Kotlin project structure

You can put your C/C++ source code in the cpp folder. CMake will generate the .externalNativeBuild folder.

So what’s CMake? CMake is a tool that controls and manages the compilation process using a configuration file called CMakeList.txt.

You can set a minimum required version of CMake:

cmake_minimum_required(VERSION 3.4.1)

As we know, first we compile an Android program and then we package all its parts into a single APK file. The contents of our APK will look as follows:

how to use android NDK: interaction between C/C++ and Kotlin; project structure

As you can see, our APK has an additional lib folder that contains folders for different CPUs, which, in turn, support different instruction sets. The thing is, the part of your app written in native code is usually packaged into a separate library (the lib folder, in my case) or into several of them. To get all of your native code gathered into such a library, you need to specify how CMake should package your C/C++ code. This is usually done with the help of the add_library function.

The add_library function creates and names your library, declares it as either STATIC or SHARED, and provides relative paths to its source code. You can define multiple libraries and CMake will build them for you. Gradle will then automatically package all shared libraries into your APK.

add_library( # Sets the name of the library.
             StoreUtil

             # Sets the library as a shared library.
             SHARED

             # Provides a relative  to your source file(s).
             src/main/cpp/StoreUtil.cpp)

add_library( # Sets the name of the library.
             Store

             # Sets the library as a shared library.
             SHARED

             # Provides a relative  to your source file(s).
             src/main/cpp/Store.cpp
             src/main/cpp/StoreUtil.cpp)

You can use different third-party C/C++ libraries in your project. To integrate a library, use the find-library function, which retrieves the name and type of a library as well as a path. The find_library function searches for your specified prebuilt library and stores its path as a variable.

In my case, I used only system libraries. Because CMake includes system libraries in the search path by default, the only thing I had to do was specify the name of the public system NDK library I wanted to add. CMake then needed to check if this library existed before completing its build. The following code snippet demonstrates how to add a library for logs:

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log)

We can now use the target_link_libraries function to specify libraries that CMake will link to our target libraries created by the add_library function. You can link multiple libraries (such as libraries you’ve already defined in this build script), prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       Store

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib})

target_link_libraries( # Specifies the target library.
                       StoreUtil

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib})

In the build.gradle file, we can specify our extra cppFlags flags and define the path to CMakeLists.txt for CMake. There’s no need to manually create the CMakeLists.txt file, since it was automatically created during project setup.

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.0"

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"

+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++11"
+            }
+        }

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
+    externalNativeBuild {
+        cmake {
+           path "CMakeLists.txt"
+        }
+    }
}

In C/C++, header files contain definitions of functions and variables. There are two types of header files: system header files (which come with a compiler) and user header files (which are written by a developer). Why do we need header files?

You can use a header file to create certain new types:

#ifndef INMEMORYSTORAGE_STORE_H
#define INMEMORYSTORAGE_STORE_H

#include <cstdint>
#include "jni.h"

#define STORE_MAX_CAPACITY 16

typedef struct {
    StoreEntry mEntries[STORE_MAX_CAPACITY];
    int32_t mLength;
} Store;

#endif //INMEMORYSTORAGE_STORE_H

You can also declare functions in these files:

#ifndef INMEMORYSTORAGE_STOREUTIL_H
#define INMEMORYSTORAGE_STOREUTIL_H

#include <jni.h>
#include "Store.h"

bool isEntryValid(JNIEnv* pEnv, StoreEntry* pEntry, StoreType pType);

StoreEntry* allocateEntry(JNIEnv* pEnv, Store* pStore, jstring pKey);

StoreEntry* findEntry(JNIEnv* pEnv, Store* pStore, jstring pKey);

void releaseEntryValue(JNIEnv* pEnv, StoreEntry* pEntry);

void throwNoKeyException(JNIEnv* pEnv);

#endif //INMEMORYSTORAGE_STOREUTIL_H

You can then use these new data types and declared functions in your project.

To add a standard header file, use #include <file>; for your own header files, you need to use #include “file”. This way the content from a header file will be copied to your *.cpp file.

You may accidently duplicate one header file in one *.cpp file; as a result, you will receive an error message during compilation. To avoid this, you can wrap the header content in the #ifndef#endif block.


  • Primitive Types of the JNI

We have to invoke functions written in C/C++ to deal with our native code. There are special native types (defined by the JNI) to pass arguments to a native function or get a result in the form of a primitive type. In other words, each primitive in Java has a corresponding native type:

how to use android NDK: interaction between C/C++ and Kotlin. primitive types of JNI

If you want to create a function that adds two values and returns a result, you’ll  need to write something like this:

extern "C"
JNIEXPORT jint JNICALL
Java_your_package_name_Math_add(
        JNIEnv* pEnv,
        jobject pThis,
        jint a,
        jint b) {
    return a + b;
}

  • Reference Types in the JNI

The JNI also includes a number of reference types that correspond to different kinds of Java objects. These JNI reference types have the following hierarchy:

how to use android NDK: interaction between C/C++ and Kotlin. Reference types og JNI

The function that takes and returns an object may look like this:

extern "C"
JNIEXPORT void JNICALL
Java_your_package_name_SomeClass_passObject(
        JNIEnv* pEnv,
        jobject pThis,
        jobject pObject) {
   ///.......
}

  • The difference between Java strings and C/C++ strings

The JNI uses modified UTF-8 strings to represent various string types. Java strings are stored in memory as UTF-16 strings. When the content of Java strings is extracted into native code, the returned buffer is encoded in the Modified UTF-8 format. Modified UTF-8 encoding is compatible with standard C string functions, which usually work on string buffers composed of 8 bits per character. To convert a Java string to a C/C++ sting, you can use something like this:

extern "C"
JNIEXPORT void JNICALL
Java_com_ihorkucherenko_storage_Store_setString(
        JNIEnv* pEnv,
        jobject pThis,
        jstring pKey,
        jstring pString) {
    // Turns the Java string into a temporary C string.
    StoreEntry* entry = allocateEntry(pEnv, &gStore, pKey);
    if (entry != NULL) {
        entry->mType = StoreType_String;
        // Copy the temporary C string into its dynamically allocated
        // final location. Then releases the temporary string.
        jsize stringLength = pEnv->GetStringUTFLength(pString);
        entry->mValue.mString = new char[stringLength + 1];
        // Directly copies the Java String into our new C buffer.
        pEnv->GetStringUTFRegion(pString, 0, stringLength, entry->mValue.mString);
        // Append the null character for string termination.
        entry->mValue.mString[stringLength] = '

LEAVE A REPLY

Please enter your comment!
Please enter your name here