1.1 the JNI (Java Native Interface)

JNI describes a technique that provides a solution for Java bytecode invocation of C/C++

1.2 the NDK (Native Development Kit)

The Android NDK is a set of tools that allow you to embed C or C++ (” native code “) into your Android applications. The NDK describes the toolset. Being able to use native code in Android apps is particularly useful for developers who want to do one or more of the following:

  • Porting their applications between platforms.
  • Reuse existing libraries or provide their own libraries for reuse.
  • Improve performance in some cases, especially in computationally intensive applications like games.

1.3 the JNI registered

1.3.1 Environment Configuration

There is already an article here that quotes others, which is very detailed about JNI environment configuration

1.3.2 Static Registration

When the Java layer calls the Navtie function, it looks up the corresponding JNI function based on the function name in the JNI library. If not found, an error is reported. If found, the association between native function and JNI function will be established, which is actually to save the function pointer of JNI function. The next time you call native, you can use the function pointer directly.

JNI function name format (package name inside the “. Java_ + package name (com_example_AUTO_jnitest) + class name (_MainActivity) + function name (_stringFromJNI)

Disadvantages of static registration:

  • JNI function names are required to follow the naming format of the JNI specification;
  • Long, error-prone names;
  • The first call will search the corresponding function in JNI according to the function name, which will affect the execution efficiency.
  • All Java classes that declare native functions need to be compiled. For each generated class file, javah tools need to generate a header file.

Static registration example

Class JNITest package name: com.hqk.jnitestone

package com.hqk.jnitestone;

public class JNITest  {

static {
    System.loadLibrary("native-lib");
}

    public static native String sayHello(a);
}

Copy the code

Corresponding to c++ code, CPP class name: native-lib.cpp

#include <jni.h>
#include <string>


extern "C"
JNIEXPORT jstring JNICALL
Java_com_hqk_jnitestone_JNITest_sayHello(JNIEnv *env, jclass clazz) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
Copy the code

Note:

  • If the CPP class name is native-lib. CPP, the corresponding Java interaction class must be added
    static {
        System.loadLibrary("native-lib");
    }
Copy the code
  • If the package name of the Java interaction class is com.hqk.jnitestone, the CPP corresponding method needs to convert the corresponding [.] to [_]. (Refer to above format)

1.3.2 Dynamic Registration

By providing a function mapping table registered with the JVM virtual machine, the JVM can use the function mapping table to call the corresponding function without having to look up the function to be called by the function name.

Java and JNI use the JNINativeMethod structure to establish the function mapping table, which is defined in the jni.h header file, the structure content is as follows:

typedef struct {
    const char* name; // Corresponds to the method name of the interactive Java class
    const char* signature;	// Function signatures corresponding to interactive methods (see article 1.4.3)
    void*       fnPtr;	// The pointer function to the interactive CPP method (pointing to the corresponding function)
} JNINativeMethod;
Copy the code

1. After creating the mapping table, call the RegisterNatives function to register the mapping table to the JVM. 2. When the Java layer loads the JNI library through system. loadLibrary, it looks up the JNI_OnLoad function in the library. You can think of JNI_OnLoad as an entry function to the JNI library, where you need to do all the function mapping and dynamic registration, as well as some other initialization.

So with the concept out of the way, let’s do it

JAVA class JNITest package name: com.hqk.jnitestone

package com.hqk.jnitestone;

public class JNITest  {
    static {
        System.loadLibrary("native-lib");
    }

    public static native String sayHello(a);

    public static native String sayHello2(a);
}

Copy the code

Note that there is a sayHello2 method that returns String

CPP class name native-lib. CPP corresponding code

#include <jni.h>
#include <string>
#include <android/log.h>

extern "C"
JNIEXPORT jstring JNICALL
Java_com_hqk_jnitestone_JNITest_sayHello(JNIEnv *env, jclass clazz) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

// CPP interaction methods
jstring sayHello2(JNIEnv *env, jobject thiz) {
    std::string hello = "Hello, I am dynamic registration success!";
    return env->NewStringUTF(hello.c_str());
}

// Dynamically register the function structure array
static const JNINativeMethod gMethods[] = { 
        {"sayHello2".// The method name corresponding to the Java interaction class
        "()Ljava/lang/String;".// Function signature corresponding to method name (see article 1.4.3)
         (jstring *) sayHello2 // Pointer function corresponding to CPP interaction class}};JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    __android_log_print(ANDROID_LOG_INFO, "native"."Jni_OnLoad");
    JNIEnv *env = NULL;
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) ! = JNI_OK)// Get the JNIEnv from JavaVM, typically using version 1.4
        return - 1;
        // Note that FindClass must correspond to the package name of the interaction class with the [/] symbol
    jclass clazz = env->FindClass("com/hqk/jnitestone/JNITest");
    if(! clazz) { __android_log_print(ANDROID_LOG_INFO,"native"."cannot get class: com/hqk/jnitestone/JNITest");
        return - 1;
    }
    if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) {
        __android_log_print(ANDROID_LOG_INFO, "native"."register native method failed! \n");
        return - 1;
    }
    return JNI_VERSION_1_4;
}
Copy the code

As you can see from the code above

JNI_OnLoad () : JNI_OnLoad (); CPP: JNI_OnLoad (); In the end, the RegisterNatives dynamic registration is realized. 2. The CPP class has a new JNINativeMethod structure array; SayHello2 (CPP) ¶ sayHello2 (CPP) ¶

1.4 Data type conversion

1.4.1 Basic data conversion

1.4.2 Reference Data Type Conversion

Except for classes, Strings, Throwable, and arrays of primitive data types, the data types of all Java objects are represented by Jobject in JNI. String is also a reference type in Java, but because it is used more frequently, a separate jString type is created in JNI.

  • Reference types cannot be directly used in the Native layer, and can only be used after type conversion according to JNI functions.
    • Multi-dimensional arrays (including two-dimensional arrays) are reference types and need to use jobjectArray to access their values.

For example, a two-dimensional array of integers is an array pointing to a one-bit array. The declaration is used as follows:

    // Get a class reference to a one-dimensional array, that is, type jintArray
    jclass intArrayClass = env->FindClass("[I");   
    // Construct an array of objects that points to a one-dimensional array of the jintArray class. The initial size of the array is Length and the type is jsize
    jobjectArray obejctIntArray  =  env->NewObjectArray(length ,intArrayClass , NULL);
Copy the code

1.4.3 JNI Function Signature Information

Because Java supports function overloading, it is impossible to find the corresponding JNI function based on the function name alone. To solve this problem, JNI uses parameter types and return value types as signature information for functions.

  • Function signature information format defined by the JNI specification:

(Parameter 1 type character…) Return value type character

  • Function signature examples:

3.JNI commonly used data types and corresponding characters:

1.4.4 JNIEnv is introduced

  • JNIEnv concept: A JNIEnv is a thread-specific structure that represents the environment in which Java is run in this thread. A series of JNI system functions can be called from JNIEnv.
  • JNIEnv thread dependency: There is a JNIEnv pointer in each thread. JNIEnv is valid only on the thread it is in, and it cannot be passed between threads.

Note: the JNIEnv is obtained in a child thread created by C++ by calling AttachCurrentThread on JavaVM. When the child thread exits, JavaVM’s DetachCurrentThread function is called to release the corresponding resource, otherwise an error occurs.

JNIEnv:

  • Access Java member variables and member methods;
  • Call Java constructors to create Java objects, etc.

1.5 compile JNI

1.5.1 Cmake compilation

CMake is a cross-platform compilation tool that does not directly compile objects. Instead, it generates corresponding makefiles or project files according to custom language rules (cmakelists.txt) and then invokes the underlying compilation. Cmake compilation is supported after Android Studio 2.2.


cmake_minimum_required(VERSION 3.41.)

add_library(
        native-lib
        SHARED
        native-lib.cpp)
find_library(
        log-lib
        log)
target_link_libraries(
        native-lib
        ${log-lib})
Copy the code

1.5.1.1 add_library instruction

Grammar: add_library (libname SHARED | STATIC | MODULE [source])

Compile a library file from a set of source files and save it as libname.so (the lib prefix was automatically added by CMake when the file was generated). There are three library file types, which default to STATIC if not written;

  • SHARED: indicates a dynamic library, which can be called dynamically in (Java) code using system.loadLibrary (name);
  • STATIC: represents a STATIC library that is integrated into the code and called at compile time;
  • MODULE: valid only for systems that use dyId. If dyId is not supported, it is treated as SHARED.
  • EXCLUDE_FROM_ALL: indicates that the library is not built by default unless other components depend on it or are manually built;
Libcompresse.c (compress SHARED compresse.c)Copy the code

1.5.1.2 target_link_libraries command

Grammar: target_link_libraries (target library < the debug | optimized > library2…).

This directive can be used to add a shared library link for target as well as to add a shared library link for your own shared library. Such as:

Target_link_libraries (compress libjpeg ${log-lib})Copy the code

1.5.1.3 find_library instruction

Grammar: find_library ( name1 path1 path2 …)

The VAR variable represents the full path to the found library, including the library file name. Such as:

find_library(libX  X11 /usr/lib)
find_library(log-lib log) # path is empty, should be to find the system environment variable pathCopy the code

1.5.1.4 More detailed usage

References: More on Cmake in detail

1.5.2 Abi architecture

Application Binary Interface (ABI) Application binary interface. Each combination of different cpus and instruction sets has a defined ABI (Application binary Interface). A program can only run on that CPU if it complies with this interface specification, so the same program code needs to build different libraries for different ABIs to be compatible with multiple cpus. Of course, different architectures do not necessarily mean incompatible cpus.

  • Armeabi devices are compatible only with Armeabi;
  • Armeabi-v7a devices are compatible with ArmeabI-V7A and ArmeABI;
  • Arm64-v8a devices compatible with ARM64-V8A, ArMEABI-V7A, ArmeABI;
  • X86 devices are compatible with X86 and Armeabi;
  • X86_64 devices are compatible with X86_64, X86, and Armeabi;
  • Mips64 devices are compatible with MIPS64 and MIPS.
  • MIPS is only compatible with MIPS;

According to the compatibility summary above, we can also get some rules:

  • Armeabi’s SO file is basically a panacea that runs on devices other than MIPS and MIPS64, but still suffers from performance loss on non-Armeabi devices;
  • 64-bit CPU architectures are always backward compatible with their corresponding 32-bit instruction sets. For example, X86_64 is X86 compatible, ARM64-V8A is Armeabi-V7A compatible, and MIPS64 is MIPS compatible.

1.6 Project Address

GitHub address: Click to download