I read some JNI introductory tutorials on the Internet, which are very unfriendly to novices. People who are easy to read are confused, and decide to write an introductory tutorial from 0 to 1 by myself.
Regarding JNI, Google also provides an introductory tutorial. For details, please check: NDK Introductory Tutorial
1. Introduction
JNI (Java Native Interface) is a programming framework and technology provided by the Java programming language, which is used to call Native code (usually written in C/C++) in Java applications to implement underlying functions and interact with operating systems and hardware . JNI allows developers to write code written in native languages such as C/C++, and then interact with Java code through the JNI interface. For example, common audio and video processing, image processing, maps, etc. will use JNI.
2. NDK and CMake
The Native Development Kit (NDK) is a set of tools that enables you to use C and C++ code in Android applications and provides a number of platform libraries that you can use to manage native activities and access physical device components such as sensors and touch enter.
You can use NDK in Android Studio 2.2 or higher to compile C and C++ codes into Native libraries, and then use Android Studio's integrated build system Gradle to package Native libraries into APKs. The Java code can then call the functions in the Native library through the JNI framework.
The default build tool for Android Studio to compile Native libraries is CMake . Since many existing projects use the ndk-build build toolkit, Android Studio also supports ndk-build . However, if you are creating new native libraries, you should use CMake. CMake is an external build tool that works with Gradle to build native libraries.
Both ndk-build and CMake are tools for building native code. Starting from version 4.0 of the Android Gradle Plugin, Google recommends using CMake.
3. Android.mk, Application.mk and CMakeLists.txt
CMakeLists.txt
and Android.mk
are build script files used to build and manage native code in Android applications.
CMakeLists.txt : It is a CMake configuration file that describes the project's build process and required build settings. In Android projects, the CMakeLists.txt file is usually located app
in the root directory of the module.
A CMakeLists.txt file can contain the following:
- Defines the minimum CMake version to build.
- Declare native libraries to build.
- Specifies a source code file.
- Configure compile options and link libraries.
- Defines the name and properties of the generated shared library.
- Configure the build output path, etc.
Using CMake, you can configure the C/C++ compiler, library path, compilation flags, etc. according to the needs of the project, so as to generate a build file suitable for the target platform. For more usage methods, please refer to Configuring CMake
Android.mk : It is a build script file in Makefile format used in Android applications. It is a build system based on GNU Make for building and managing native code in applications. By default, it's located in the app project directory jni/Android
.mk
under .
In the early days of Android apps, Android.mk
it was a standard build script file used primarily to build native code. It provides a way to describe native libraries and compilation settings.
Android.mk
The file is usually located in the directory of the Android project jni
, which contains the following content:
- Defines the name of the native library to build.
- Specifies a source code file.
- Configure compile options and link libraries.
- Defines the name and properties of the generated shared library.
- Specify the output path of the shared library, etc.
Android.mk
You can also use predefined variables and functions, as well as Makefile syntax to control the build process. For more usage methods, please refer to Android.mk
Application.mk: Specifies project-level settings for ndk-build. By default, it's located in the app project directory jni/Application.mk
under . We usually specify the ABI version of the compiled Native library here, namely armeabi-v7a, arm64-v8a, x86, etc. For more configuration, please refer to Application.mk
4. Data type
Since C/C++ is used, there must be a difference in the conversion of data types. Please learn the basic knowledge of C/C++ by yourself. This is the premise of using JNI. Here we will talk about the data type conversion in JNI.
JNI (Java Native Interface) supports multiple data types for data transfer and type conversion between Java code and Native code. The following are some common JNI data types:
-
Basic data types:
jboolean
: Boolean type, corresponding to Javaboolean
.jbyte
: byte type, corresponding to that in Javabyte
.jchar
: Character type, corresponding to Javachar
.jshort
: Short integer, corresponding to Javashort
.jint
: Integer type, corresponding to that in Javaint
.jlong
: Long integer, corresponding to Javalong
.jfloat
: Single-precision floating-point type, corresponding to Javafloat
.jdouble
: Double-precision floating-point type, corresponding to Javadouble
.
-
Reference type:
jobject
: General object reference type, corresponding to JavaObject
.jclass
: Class reference type, corresponding to JavaClass
.jstring
: String type, corresponding to JavaString
.jarray
: Array type, used to represent array objects in Java.jbooleanArray
: Boolean array type, corresponding to Javaboolean[]
.jbyteArray
: Byte array type, corresponding to Javabyte[]
.jcharArray
: Character array type, corresponding to Javachar[]
.jshortArray
: Short integer array type, corresponding to Javashort[]
.jintArray
: Integer array type, corresponding to Javaint[]
.jlongArray
: Long integer array type, corresponding to Javalong[]
.jfloatArray
: Single-precision floating-point array type, corresponding to Javafloat[]
.jdoubleArray
: Double-precision floating-point array type, corresponding to Javadouble[]
.
-
Other types:
jthrowable
: Exception type, used to throw Java exceptions.
In JNI, these data types are used to declare the parameter and return value types of Native methods.
V. Definition
1. File naming:
The files generated by using ndk-build or CMake tools are usually .so, and the naming convention is as follows:
lib+library name.so, for example: libXXX.so XXX represents the name of the specific library
2. Function naming:
In JNI, function naming follows specific rules to ensure correct mapping and interaction between Java code and native code. The convention for JNI function naming is based on the following form:
Java_package_ClassName_MethodName
Java
The : prefix indicates that this is a JNI function.package
: Indicates the package name of the Java class, using underscores instead of dots.ClassName
: Indicates the name of a Java class.MethodName
: Indicates the name of a Java method.
For the JNI function naming rules, there are some details that need attention:
- Underscores in JNI function names
_
are used to separate different elements to indicate hierarchy and namespace relationships. - If the JNI function is a static method, add an underscore before the method name
_
. - For JNI functions, the signature of the return type and parameter types should match that of the Java method. Signatures denote different types with specific characters.
Here are some examples showing how to follow the JNI function naming rules:
//Java 层代码JNIDemo.java
public class JNIDemo {
static {
System.loadLibrary("libjni");
}
public native String showLog();
}
//Native层代码 jnidemo.cpp
extern "C"
JNIEXPORT jstring JNICALL Java_com_example_jni_JNIDemo_showLog(JNIEnv* env, jobject job) {
return env->NewStringUTF("hello world");
}
In the above example, we assume that there is a com.example.jni.JNIDemo
Java class named, which contains a method named showLog , and the function Java_com_example_jni_JNIDemo_showLog of the Native layer corresponds to the package name + class name + method name of the Java layer , so as to map correctly , when the name is not correct, it will cause a compile-time error.
3. extern "C" : Indicates the compatibility between C language and C++.
4. JNIEXPORT and JNICALL are two macros defined in JNI to identify the purpose of the function . The definitions of these two macros are different in different system environments. In the Android environment, it is defined as follows
#define JNIIMPORT
#define JNIEXPORT __attribute__ ((visibility ("default")))
#define JNICALL
JNIEXPORT is used to indicate whether the function can be exported (visibility of the function). In ordinary C language, if you want to limit the use of functions or variables to the current file, you need to add static modification to it. But if you want to expose it to the specified file of the shared library, you need to control it by hiding and displaying the symbol.
After GCC4.0, the symbol visibility option -fvisibility=vis is provided, vis can be the default value default, or hidden means hidden.
The corresponding code visibility attribute is __attribute__((visibility("default"))) or __attribute__((visibility("hidden")))
In order to simplify the symbol output form, you can simplify its writing through EXPORT. As follows:
#define EXPORT __attribute__((visibility("default")))
EXPORT int Func();
So JNIEXPORT can be considered as #define JNIEXPORT __attribute__((visibility("default"))) In this way, of course, the specific implementation may be complicated Some, to judge different compilers, etc.
JNIEXPORT and JNICALL are empty definitions in the Linux environment, so these two macros can be omitted in the JNI function declaration under Linux.
5.jstring: The return value type of the function.
6. JNIEnv* : It is the first parameter of all native functions, and it is a pointer to the JVM function table. Each entry in the function table points to a JNI function, and each function is used to access a specific data structure in the JVM.
7. jobject : Represents a Java class or an instance of a Java class that defines a native function:
- If the native function is static, it represents the class Class object
- If the native function is not static, it represents the instance object of the class
6. Development steps
-
Configuration environment: refer to install and configure NDK and CMake
-
Configure Android.mk, Application.mk or CMakeLists.txt.
-
Write Native code: Write Native code in C or C++, and these codes will implement the functions you want to call in Java.
-
Create a JNI interface file: Create a JNI interface file corresponding to the Native code, which describes the interaction between the Java code and the Native code.
.h格式的头文件
-
Implement JNI method: Define the local method to be called in the JNI interface file. You need to implement these methods and connect it with native code.
-
Generate Native library: Use local development tools (such as GCC, Clang, etc.) to compile Native code into a shared library (such as
.so
a file). -
System.loadLibrary("your-library-name")
Load native library in Java: Use the method to load the generated native library in Java code . -
Call Native method: declare Native method in Java code, and call Native code through JNI interface
I will introduce specific practical examples in " Android JNI from 0 to 1 Getting Started Tutorial (2) "