[!NOTE]
This article is not a thorough guide. It exists only to document and share what I learned in two-ish work days.
Sample Project
All the code in this article is available as a sample project on GitHub.
NDK
NDK (Native Development Kit) is the tool that lets Native (C/C++) code and Kotlin/Java code interoperate. So, first of all, we need to install the NDK in Android Studio.
- Open
Tools
->SDK Manager
- Switch to the
SDK Tools
tab - Tick
NDK (side by side)
- Click
OK
CMake
CMake is the preferred way of using external/native libraries in Android apps. Gradle also supports ndk-build. But we are going to use CMake for this article as that is the preferred choice.
Installation
You can install CMake in two ways;
Through Android Studio
- Open
Tools
->SDK Manager
- Switch to the
SDK Tools
tab - Tick
CMake
- Click
OK
Externally
You can install via any method you like.
-
If
cmake
command is available on thePATH
, it will automatically be detected by Android Studio.For example, on Arch Linux, I installed it via the following command, and it was auto-detected by Android Studio without any extra setup needed.
1
sudo pacman -S cmake
-
If
cmake
is not in yourPATH
, you can either add its location toPATH
, or you can directly tell Android Studio where it is located by adding the following property inlocal.properties
file.1 2
# local.properties cmake.dir = "/path/to/cmake"
Configuration
[!NOTE]
You should create a separate Android module for the native library.
Top-Level CMakeLists.txt
We cannot link our Android project with multiple CMake projects directly. So, we need a single top-level CMakeLists.txt
file in which we can import other CMake libraries.
We will create this file at {project}/{module}/src/main/cpp/CMakeLists.txt
with the following contents.
|
|
Linking With Our Android Project
We have two methods to add/link this CMake file with our Android project.
Through GUI
- In “Project” pane, make sure “Android” is selected as the current view
- Right click on the “app” module, and select “Link C++ Project with Gradle”
- Select “CMake” as the build system.
- Select the “CMakeLists.txt” file we just created as Project path.
- Click “OK”
By Editing build.gradle.kts
Add this inside the android
block of your module-level build.gradle.kts
file.
|
|
Adding Native (C/C++) Code
Adding Native Library Dependency
Depending on your situation, the method to add external native dependencies will differ.
Here, we will discuss the simplest case. Our dependency is a single function/open source C library with a permissive license, and no transitive dependencies other than the C standard library.
[!NOTE]
From now on, we will assume the library we are using is namedconcat
. For everything below this, you can replace the wordconcat
, wherever it is used, with the name of the library you want to use in your project.
We just copy all the source code of concat
(our dependency) into src/main/cpp/concat
. So, the directory structure of our cpp directory looks like below.
{project}/concat/src/main/cpp
├── CMakeLists.txt
└── concat
├── CMakeLists.txt
├── concat.c
└── concat.h
Contents of {project}/concat/src/main/cpp/concat/concat.h
are;
|
|
Creating Java Bindings
We need to expose the relevant C functions to the Kotlin/Java code. We do that by binding C functions to a Kotlin/Java class and its methods.
Add A Native Binding File
-
Create an empty C source file, at the following path.
{project}/concat/src/main/cpp/concat-jni.c
-
Add the following contents to the main
CMakeLists.txt
file ({project}/concat/src/main/cpp/CMakeLists.txt
).1 2 3 4
add_subdirectory(concat) add_library(concat-jni SHARED concat-jni.c) target_link_libraries(concat-jni concat) target_include_directories(concat-jni PUBLIC concat)
-
With
add_subdirecotry(concat)
, we are makingconcat
library a part of our ownconcat-jni
library. -
add_library(concat-jni SHARED concat-jni.c)
creates a shared library namedconcat-jni
and addsconcat-jni.c
as a source file for it.concat-jni
is a C library that we will write, and it is the library that will expose C functions from theconcat
library to our Kotlin/Java code. -
target_link_libraries(concat-jni concat)
links ourconcat-jni
library with the baseconcat
library, so that we can expose functions fromconcat
. -
target_include_directories(concat-jni PUBLIC concat)
will add{project}/concat/src/main/cpp/concat/
as an include directory for ourconcat-jni
library. So, we can#include <concat.h>
.
-
Add Kotlin/Java Class
|
|
- Class name could be anything.
- It doesn’t have to be a class, it can also be a Kotlin
object
. - In
init
, we callSystem.loadLibrary("$libraryName")
.- Here, library name must be only the name, no
lib
prefix or file extension. E.g. instead ofconcat-jni.dll
orlibconcat-jni.so
, we need to use justconcat-jni
here.
- Here, library name must be only the name, no
- Use the keyword
extenal
to declare methods that we want to expose from our native library.
Write Binding Code
- Once you add a method marked as
external
, Android Studio will give an error: “Cannot resolve corresponding JNI function”. - Just hover your cursor over the method name, and Android Studio will give you an option to “Create JNI function for <functionName>”, click on that.
- It will give you an option to choose a C file to add the “corresponding JNI function” to, choose
concat-jni.c
.
Now, you’ll have a skeleton of a binding function. How to fill in that function with useful code will be discussed in Part 2 of the article.