Table of contents
- The goal of this blog post?
- Install NDK and CMake
- Link Gradle to your native code
- Create a CMake build script
- The JNI ( Java Native Interface.)
- The C++ code
- The JNI ( Java Native Interface.)
- Running the code
Resources
- Create Hello-CMake with Android Studio
- Add C and C++ code to your project
- Install NDK and CMake
- Link Gradle to your native library
- OpenGL Basics
My app on the Google play store
My app's GitHub code
The Goal of this blog post
- The goal of this blog post is simple. use C++ code to log a statement to the Android Studio Logcat
Install NDK and CMake
- Documentation
- The first thing that we need to do is to install the The Android Native Development Kit (NDK) and CMake (an external build tool that works alongside Gradle).
- You can read a more detailed version inside of the documentation but essentially inside of android studio we go,
-
Tools > SDK Manager > SDK Tools tab > Select the NDK (Side by side) and CMake checkboxes > select ok
Link Gradle to your native code
- Documentation
- Now in order for us to use our C++ code with Gradle, we need to tell Gradle where our CMake build file is. Gradle can then use the instructions we provide inside of this file and build our C++ code
- We can inform Gradle inside of the app's build.gradle file:
android {
...
defaultConfig {...}
buildTypes {...}
// Encapsulates your external native build configurations.
externalNativeBuild {
// Encapsulates your CMake build configurations.
cmake {
// Provides a relative path to your CMake build script.
path 'src/main/cpp/CMakeLists.txt'
}
}
}
- The
externalNativeBuild
section is the important part. At this point you should be able to run the Gradle build and receive an error stating that the file:src/main/cpp/CMakeLists.txt
can not be found. This is a good error, we are on the right path
Create a CMake build script
Now we can create the
src/main/cpp/CMakeLists.txt
file and fill it with this code:
# Sets the minimum version of CMake required to build your native library.
# This ensures that a certain set of CMake features is available to
# your build.
cmake_minimum_required(VERSION 3.4.1)
# Specifies a library name, specifies whether the library is STATIC or
# SHARED, and provides relative paths to the source code. You can
# define multiple libraries by adding multiple add_library() commands,
# and CMake builds them for you. When you build your app, Gradle
# automatically packages shared libraries with your APK.
#Add a library to the project using the specified source files.
add_library(gl_code SHARED
gl_code.cpp)
# add lib dependencies
target_link_libraries(gl_code
android
log
EGL
GLESv2)
The important section of this code is the
add_library(gl_code SHARED gl_code.cpp)
. So lets brake it down:add_library()
: as the documentation states:Adding a library to the project using the specified source files.
Which basically means, we are creating a place for our C++ codegl_code
: has to be a globally unique identifier and is how we will refer to our C++ code throughout the application. If you change this name, you must clean your project and then rebuild it for it to work againSHARED
: Just means it is a dynamic library that may be linked by other targets and loaded at runtime.gl_code.cpp
: This is the source file that will be compiled into the library(the file that holds our C++ code).
The JNI ( Java Native Interface.)
- Documentation
- As the documentation states:
It defines a way for the bytecode that Android compiles from managed code (written in the Java or Kotlin programming languages) to interact with native code (written in C/C++). JNI is vendor-neutral, has support for loading code from dynamic shared libraries, and while cumbersome at times is reasonably efficient.
- Which is really just nerd talk for: We can create a Kotlin/Java class and use said class to run some C++ code
- We can do that like this:
class NativeLoading {
init {
System.loadLibrary("gl_code");
}
external fun init()
}
- The
System.loadLibrary("gl_code")
is what is going to allow us to access thegl_code.cpp
file we mentioned earlier and defined its identifier as:gl_code
The actual C++ code
- So now we can actually create some C++ code that will log a statement for us:
#include <jni.h>
#include <android/log.h>
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "native-lib", __VA_ARGS__))
extern "C"
JNIEXPORT void JNICALL
Java_com_example_clicker_nativeLibraryClasses_NativeLoading_init(JNIEnv *env, jobject thiz) {
LOGI("int %s,", "--------------THIS IS A LOG FROM THE NATIVE SIDE ----------------------");
}
I apologise
- I have ran out of time to write this blog post(I set a 1hr time cap) so from here on out, Chat GPT will explain the code for you:
1. Header Files
#include <jni.h>
#include <android/log.h>
#include <jni.h>:
- This includes the JNI header file, which provides the necessary functions and types to interact between Java/Kotlin and C++. JNI allows native code (C/C++) to communicate with Java objects and methods in a virtual machine. #include : This includes Android's logging utility, used for printing messages to the log. The android/log.h header provides functions to log messages in native code (similar to Log.d() in Java/Kotlin).
2. Logging Macro Definition
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "native-lib", __VA_ARGS__))
LOGI: This defines a macro for logging information messages. Macros in C/C++ are like shorthand codes that get expanded when the program is compiled.
android_log_print(): This function logs a message. It takes a priority level (ANDROID_LOG_INFO for informational messages), a tag ("native-lib" in this case, which will help identify the source of the log), and the message (VA_ARGS__ allows for variable arguments).
The macro makes it easy to log messages without typing the full function each time, using LOGI(...) instead.
3. C++ extern "C" Declaration
extern "C"
The extern "C" tells the compiler to use C linkage for the following code. Since JNI functions must be compatible with Java’s calling conventions (which follow C linkage), this ensures the function name is not mangled by the C++ compiler.
4. JNIEXPORT and JNICALL
JNIEXPORT void JNICALL
JNIEXPORT: A macro indicating that this function will be used as part of the JNI interface, making it accessible from Java/Kotlin.
JNICALL: A macro that ensures the function uses the correct calling convention for JNI (used to handle the interaction between Java and C++).
5. JNI Function Declaration
Java_com_example_clicker_nativeLibraryClasses_NativeLoading_init(JNIEnv *env, jobject thiz)
This is the native implementation of the init() method that you declared in Kotlin as external fun init().
The function name follows a strict JNI naming convention: Java_packageName_className_methodName. In this case:
Java_com_example_clicker_nativeLibraryClasses_NativeLoading_init corresponds to the init() function in the NativeLoading class located in the com.example.clicker.nativeLibraryClasses package.
The function has two parameters:
JNIEnv *env: A pointer to the JNI environment, which gives access to JNI functions, such as creating Java objects, calling methods, etc.
jobject thiz: A reference to the instance of the calling Java/Kotlin object (this).
6. Logging a Message
LOGI("int %s,", "--------------THIS IS A LOG FROM THE NATIVE SIDE ----------------------");
This line logs an informational message from the native code. It uses the LOGI macro defined earlier to print a message:
"int %s,": This is a formatted string. %s is a placeholder for a string value, which is replaced by the second argument.
"--------------THIS IS A LOG FROM THE NATIVE SIDE ----------------------": The string to be inserted into the placeholder.
Summary
This C++ function logs a message when the init() function is called from Kotlin.
The function adheres to JNI standards, allowing it to interact with Kotlin/Java.
The LOGI() macro simplifies logging in native code, outputting messages to the Android log with the tag "native-lib".
This setup is useful for debugging and logging from native code in Android. When you call NativeLoading.init() from Kotlin, this C++ code is executed, and a log message will appear in the Android log (logcat) with the message: "--------------THIS IS A LOG FROM THE NATIVE SIDE ----------------------".
Running the code:
- To run the code we simple implement it like anyother type of kotlin class:
val nativeCode= NativeLoading()
nativeCode.init()
- doing this will produce a log like any other
Conclusion
- Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter.
Top comments (0)