Demystifying JNI: Boost Performance and Access Native Features

Samir Dubey
4 min readApr 18, 2024

--

Introduction:

Java, with its portability and ease of use, has long been a preferred language for developing applications across various platforms. However, there are times when developers encounter scenarios where Java’s capabilities fall short, especially when it comes to performance-critical tasks or accessing platform-specific features. This is where JNI (Java Native Interface) comes into play.

JNI acts as a bridge, allowing Java code to interact with native code written in languages like C and C++. This powerful tool opens up a world of possibilities, enabling developers to leverage existing native libraries, access hardware-specific functionalities, and optimize performance in critical sections of their applications.

In the world of Android, JNI plays a crucial role in the Android Open Source Project (AOSP). Android developers frequently use JNI to access native code for tasks such as graphics rendering, audio processing, and interfacing with hardware components like sensors and cameras. By seamlessly integrating Java and native code, JNI empowers Android developers to build high-performance, feature-rich applications.

JNI Syntex

Let’s break down each part of the syntax:

  • JNIEXPORT: This macro indicates that the function is exportable from a shared library (DLL or SO) and is callable from Java.
  • <return_type>: This specifies the return type of the JNI function. It could be any valid C data type or a JNI data type (e.g., jint, jfloat, jstring, etc.), representing the corresponding Java data type.
  • JNICALL: This macro specifies the calling convention used for the function. It’s typically used for platform-specific considerations.
  • Java_<package_path>_<class_name>_<method_name>: This part of the function name follows a specific naming convention. It starts with “Java_” followed by the fully qualified package path of the Java class, the class name, and finally the method name. Each component is separated by underscores.
  • (JNIEnv *env, jobject obj, <arguments>): These are the parameters passed to the JNI function.
  • JNIEnv *env: This parameter provides access to JNI functions and data structures.
  • jobject obj: This parameter represents the object instance from which the native method is called.
  • <arguments>: These are optional and represent any arguments that the JNI function may take. They follow the same syntax as regular C function parameters.

Following the syntax, you would replace <return_type>, <package_path>, <class_name>, <method_name>, and <arguments> with appropriate values for your JNI function.

Example:

Let’s dive deeper into understanding JNI with a simple example. Consider a scenario where we need to perform basic mathematical operations — addition and subtraction — using JNI within an Android application.

In our example, we’ll have two native methods: one for addition without arguments and another for subtraction with arguments.

In the code above, we define two JNI functions: add and subtract. The add function adds two hardcoded numbers (5 and 3) and returns the result, while the subtract function takes two arguments, subtracts them, and returns the result.

Now, let’s see how we can access these native methods from Java:

In the Java code, we define two native methods corresponding to the JNI functions add and subtract. We load the native library math_operations using System.loadLibrary() and then call these native methods to perform addition and subtraction operations.

Example Explanation :

The first part of our example consists of the native C code. Here, we utilize the JNI naming convention to define two functions: Java_com_example_math_MathOperations_add Java_com_example_math_MathOperations_subtract

These functions are declared as JNIEXPORT to indicate that they will be accessible from Java. In the add function, we simply add two hardcoded integers, while in the subtract function, we subtract the second integer from the first, both passed as arguments.

In the second part of our example, we can see how we can access these native methods from Java. We create a Java class MathOperations, where we declare two native methods corresponding to the JNI functions defined in C. We load the native library using System.loadLibrary() and then call these native methods to perform addition and subtraction operations. The results are then printed to the console.

Conclusion:

JNI is a powerful tool that enables seamless integration between Java and native code, opening up new possibilities for developers. By leveraging JNI, developers can access platform-specific features, optimize performance-critical tasks, and reuse existing native libraries within their Java applications. As demonstrated in our example, JNI empowers developers to combine the strengths of both Java and native code to build robust, high-performance applications.

--

--

Samir Dubey

AOSP Engineer @ Zebra Technologies | AOSP Enthusiast | Android Framework | Linux