Android JNI from 0 to 1 Getting Started Tutorial (1)

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.txtand Android.mkare 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 appin 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.mkit was a standard build script file used primarily to build native code. It provides a way to describe native libraries and compilation settings.

Android.mkThe 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.mkYou 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:

  1. Basic data types:

    • jboolean: Boolean type, corresponding to Java boolean.
    • jbyte: byte type, corresponding to that in Java byte.
    • jchar: Character type, corresponding to Java char.
    • jshort: Short integer, corresponding to Java short.
    • jint: Integer type, corresponding to that in Java int.
    • jlong: Long integer, corresponding to Java long.
    • jfloat: Single-precision floating-point type, corresponding to Java float.
    • jdouble: Double-precision floating-point type, corresponding to Java double.
  2. Reference type:

    • jobject: General object reference type, corresponding to Java Object.
    • jclass: Class reference type, corresponding to Java Class.
    • jstring: String type, corresponding to Java String.
    • jarray: Array type, used to represent array objects in Java.
    • jbooleanArray: Boolean array type, corresponding to Java boolean[].
    • jbyteArray: Byte array type, corresponding to Java byte[].
    • jcharArray: Character array type, corresponding to Java char[].
    • jshortArray: Short integer array type, corresponding to Java short[].
    • jintArray: Integer array type, corresponding to Java int[].
    • jlongArray: Long integer array type, corresponding to Java long[].
    • jfloatArray: Single-precision floating-point array type, corresponding to Java float[].
    • jdoubleArray: Double-precision floating-point array type, corresponding to Java double[].
  3. 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
  • JavaThe : 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.JNIDemoJava 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

  1. Configuration environment: refer to install and configure NDK and CMake

  2. Configure Android.mk, Application.mk or CMakeLists.txt.

  3. Write Native code: Write Native code in C or C++, and these codes will implement the functions you want to call in Java.

  4. 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格式的头文件

  5. 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.

  6. Generate Native library: Use local development tools (such as GCC, Clang, etc.) to compile Native code into a shared library (such as .soa file).

  7. System.loadLibrary("your-library-name")Load native library in Java: Use the method to load the generated native library in Java code .

  8. 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) "

Guess you like

Origin blog.csdn.net/gs12software/article/details/131529052