ANDROID JNI TUTORIAL
Complete Android JNI Tutorial: Calling Native C/C++ Code from Java
In Android development, the Java Native Interface (JNI) allows Java code to interact with native code written in languages such as C or C++. JNI is often used when you need to optimize performance-intensive parts of your app, reuse existing native code libraries, or directly interact with hardware features that aren't accessible through Java APIs.
This tutorial will guide you step-by-step through the process of setting up JNI in an Android project, writing some simple native C++ code, and calling it from your Java code.
Why Use JNI?
JNI provides a bridge between Java and native code (written in C or C++). Some common reasons to use JNI in Android apps include:
- Performance: Native code can perform faster than Java for certain tasks, such as image processing or computationally intensive calculations.
- Reuse Existing Code: If you already have C or C++ code, you can use JNI to reuse that code in your Android application without rewriting it in Java.
- Low-level Access: JNI gives access to platform-specific libraries and APIs that might not be available in the Java SDK.
Step 1: Create a New Android Project
- Open Android Studio and create a new project.
- Choose an Empty Activity template.
- Select Java as the programming language (or Kotlin if preferred).
- Enter your project name and choose a suitable minimum SDK.
- Finish the setup, and Android Studio will create a basic project for you.
Step 2: Add C++ Support to Your Project
- Enable C++ Support:
- During project setup, you can choose C++ support. If you missed this step, you can add it later manually.
- If you didn't enable C++ support initially, go to File > New > New Module, and select C++ under the Phone & Tablet section. This will create necessary files for JNI support.
- Folder Structure:
- Android Studio creates a
cppdirectory underapp/src/main/where you will place your C/C++ source code. The project structure should look like this:app/ └─ src/ └─ main/ ├─ java/ ├─ cpp/ └─ CMakeLists.txt
- Android Studio creates a
Step 3: Write Native C++ Code
Now, let's write some simple C++ code that we will call from Java using JNI. We’ll create a function that returns a simple string from C++ to Java.
-
Create a New C++ Source File:
- In
cppfolder, create a new file callednative-lib.cpp.
- In
-
Write the Native Function:
Inside
native-lib.cpp, define a simple C++ function to be accessed from Java:#include <jni.h> #include <string> extern "C" JNIEXPORT jstring JNICALL Java_com_example_myapplication_MainActivity_stringFromJNI(JNIEnv* env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }JNIEXPORTandJNICALLare macros used to define the function signature in a way that JNI can recognize.Java_com_example_myapplication_MainActivity_stringFromJNIis the function name following JNI naming conventions:Java_prefix- Package path:
com_example_myapplication - Class name:
MainActivity - Function name:
stringFromJNI
JNIEnv* envis used to interact with Java objects.NewStringUTFcreates a new JavaStringfrom the C++ stringhello.
Step 4: Declare the Native Method in Java
In MainActivity.java (or MainActivity.kt if using Kotlin), declare the native method that will be implemented in the C++ code.
-
Open
MainActivity.javaand add the following:package com.example.myapp; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { // Declare the native method public native String stringFromJNI(); // Load the native library static { System.loadLibrary("native-lib"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Call the native method and print the result String result = stringFromJNI(); System.out.println(result); // Output: "Hello from C++" } }native: The keywordnativeis used in Java to declare a method whose implementation is in native code.System.loadLibrary("native-lib"): This loads the shared library (compiled.sofile). It should match the name of the.sofile that we will generate from the C++ code.- The
stringFromJNI()method will call the C++ code and return the string to Java.
Step 5: Configure CMake to Build Native Code
CMake is used to build native C++ code. To configure CMake for building the C++ code, we need to edit the CMakeLists.txt file.
-
Open
CMakeLists.txtand ensure it contains the following:cmake_minimum_required(VERSION 3.4.1) # Add your native source files here add_library(native-lib SHARED src/main/cpp/native-lib.cpp) # Find the log library for Android logging find_library(log-lib log) # Link the log library with your native-lib target_link_libraries(native-lib ${log-lib})add_library(native-lib SHARED src/main/cpp/native-lib.cpp)tells CMake to compilenative-lib.cppand generate a shared library (native-lib.so).find_library(log-lib log)finds the Android log library to link with your native code for logging purposes.target_link_libraries(native-lib ${log-lib})links the native library with the Android log library.
Step 6: Build and Run the App
- Build the Project:
- Click Build > Make Project to compile the C++ code into the shared library.
- Run the App:
- Click Run to launch the app on a connected device or emulator.
- The app will display "Hello from C++" in the console, as the
stringFromJNI()method calls the C++ function and returns the result to Java.
Step 7: JNI Directory Structure
To better understand how the JNI integration works in Android Studio, here is the expected project structure:
app/
├─ src/
│ ├─ main/
│ │ ├─ java/
│ │ │ └─ com/
│ │ │ └─ example/
│ │ │ └─ myapplication/
│ │ │ └─ MainActivity.java
│ │ ├─ cpp/
│ │ │ └─ native-lib.cpp
│ │ └─ CMakeLists.txt
└─ build.gradle
native-lib.cpp: The C++ source file with the JNI method.CMakeLists.txt: The CMake configuration file to build the native code.MainActivity.java: The Java activity that declares and calls the native method.
Step 8: Additional JNI Features and Tips
-
Pass Data Between Java and Native Code:
- JNI allows you to pass data between Java and native code. For example, you can pass primitive types like
int,boolean,float, or even complex types like arrays and objects. - You can use JNI functions like
GetIntArrayElements,GetStringUTFChars, andSetObjectFieldto work with Java objects from C++.
- JNI allows you to pass data between Java and native code. For example, you can pass primitive types like
-
Handle Errors in JNI:
- When dealing with JNI, always check for errors. For example, if you call a JNI function and it fails, you can use
env->ExceptionCheck()andenv->ExceptionClear()to handle exceptions.
- When dealing with JNI, always check for errors. For example, if you call a JNI function and it fails, you can use
-
Use the Android NDK:
- If you’re working with more complex native libraries or need to perform low-level tasks like interfacing with hardware, the Android NDK (Native Development Kit) is a useful tool. The NDK allows you to write and compile native code using tools such as CMake or ndk-build.
Conclusion
JNI provides a powerful way to use C/C++ code in Android applications. By following this tutorial, you learned how to set up JNI in an Android project, write native C++ code, call it from Java, and configure your project using CMake. JNI is especially useful for performance-critical code or reusing existing libraries, but it does require careful handling of memory and exceptions to avoid issues.
Now that you've mastered the basics, you can start building more complex JNI features and take full advantage of native code in your Android apps.

0 Comments