Introduction to Android JNI and NDK

Introduction to Android JNI and NDK

Why use JNI

  • 1, native language has better performance.
  • 2, Before the birth of Java, many libraries were written in native language, so there is no need to implement them again in Java.
  • 3, Better security: not easy to decompile.

What is JNI

  • Full name: Java Native Interface. Literally, it is the interface between Java and the local language. It defines a set of specifications for communication between Java and native languages. (this is defined in Java and has nothing to do with Android.)
  • NDK is a set of development components that compile native language into executable files of a specific platform. (compile C/C + + into so library, which is defined by Android and has nothing to do with Java.)

Therefore, jni and ndk are two things. ndk generates executable so files, and jni loads so for java calls. If you have so files, you can use them directly.

3. Creation of Native project

3.1. Create a Native project

The cmake script file defines how to compile and link.

3.2 Native project structure

Previous AS versions need to specify the ndk path in the local.properties file: ndk.dir=xxx. The new version of AS will automatically the appropriate ndk version. If there is no local version, it will recommend a version for you to download.

3.3. Declare local methods in Java.

  • Generally, we have a special Java class for accessing native methods. For example, NativeBridge.java of this project.
public class NativeBridge {
    static {
        System.loadLibrary("native-lib");
    }

    // Static registration of local method 1.
    public static native String dataFromNative(int param);

    // Local method 2 dynamic registration.
    public static native String nativeMethod1(int param);

    ///Local method 3 dynamic registration.
    public static native String nativeMethod2(int param);

}

After declaration, it needs to be registered and implemented in jni layer. The registration native method is divided into static registration and dynamic registration.

4. Registration and implementation of Native methods.

4.1 comparison between static registration and dynamic registration of Native method.

  • Static registration is simple to write and can be generated with one click of AS shortcut key. But the running efficiency is low (when calling the native method for the first time, you need to search the native method of jni layer to establish the corresponding relationship.)
  • Dynamic registration is efficient (because we have established the mapping relationship of methods), and it is relatively troublesome to write.

4.2. Static registration

extern "C" JNIEXPORT jstring JNICALL
Java_com_hongenit_jnindkdemo_NativeBridge_dataFromNative(JNIEnv *env, jclass clazz, jint param) {
    std::string hello = "Returned by static method Native character string";
    return env->NewStringUTF(hello.c_str());
}

4.3 dynamic registration

  1. Prepare the JNI nativemethod struct array of the method to register.
    // Array of JNI local methods.
    static JNINativeMethod gMethods[] = {
            {"nativeMethod1", "(I)Ljava/lang/String;", (void*)nativeMethod1},
            {"nativeMethod2", "(I)Ljava/lang/String;", (void*)nativeMethod1},
    };
    

    Of JNI nativemethod structure

    java data typenative typeField Descriptors
    Basic data type
    booleanjbooleanZ
    bytejbyteB
    charjcharC
    shortjshortS
    intjintI
    longjlongJ
    floatjfloatF
    doublejdoubleD
    voidvoidV
    Object reference typeStart with "L" and start with ";" At the end, the package and class names separated by "/". If the internal class, use $to connect the internal class;
    SurfacejobjectLandroid/view/Surface;
    StringjstringLjava/lang/String;
    Array data typeThe corresponding basic data type is preceded by brackets[
    int[]jintArray[I
    float[]jfloatArray[f
    Surface[]jobjectArray[Landroid/view/Surface;
  2. Override the JNI_OnLoad method.
    extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
    {
        JNIEnv* env = NULL;
        jint result = -1;
        if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
            return result;
        }
        // Dynamically register native methods.
        register_gmethods(vm, env);
        return JNI_VERSION_1_6;
    }
    
  3. The RegisterNatives method is called in JNI_OnLoad to register the native method.
    void register_gmethods(JavaVM *pVm, JNIEnv *pEnv) {
        static const char* const jclassName = "com/hongenit/jnindkdemo/NativeBridge";
        jclass jclazz = pEnv->FindClass(jclassName);
        pEnv->RegisterNatives(jclazz,gMethods,int(sizeof(gMethods)/sizeof(gMethods[0])));
    }
    

    The generated so is in the build directory.

4.4 implementation of Native method.

5. cmake compilation script

# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.10.2)

project("jnindkdemo")

# Name and create a dynamic library (the difference from the static library is that it is not compiled into the target), and specify its source file. Multiple libraries can be defined, and CMake will compile them for you. gradle will automatically package the so library into apk.
add_library(native-lib SHARED native-lib.cpp)


# Search for a library from the NDK system library and get an alias. Through this alias, you can locate the path to the system library.
find_library(log-lib log)

# Specify which libraries need Cmake to link to your target library. You can link multiple libraries, such as libraries defined in the script, precompiled third-party libraries, and system libraries.
target_link_libraries(
        native-lib
        play gnustl_shared
        ${log-lib}
)

5.1. Use of third-party library

  • Suppose you need to use a third-party so library. Add a defined library before linking to the target library.
#---------------------------Third party so Library----------------
#Add header file path (relative to this file path)
include_directories(include)

#Set the variable of the path where the so library is located
set(SO_PATH ${CMAKE_CURRENT_SOURCE_DIR}/libs/${ANDROID_ABI})  #The CMAKE_CURRENT_SOURCE_DIR variable is the directory where the current cmake file is located

add_library(gnustl_shared SHARED IMPORTED)
set_target_properties(gnustl_shared PROPERTIES IMPORTED_LOCATION ${SO_PATH}/libgnustl_shared.so)

add_library(play SHARED IMPORTED)
set_target_properties(play PROPERTIES IMPORTED_LOCATION ${SO_PATH}/libplay.so)

If third-party library A (play) needs to rely on another third-party library B, it must also link B (gnustl_shared) to the target library. Otherwise, an error will be reported as follows:

java.lang.UnsatisfiedLinkError: dlopen failed: library "libgnustl_shared.so" not found: needed by /data/app//lib/arm64/libplay.so in namespace. . . 
  • The following shows that the header file is in the include directory, so the header file of so library should be placed in the include directory, and then the header file can be introduced into the target program and the method can be called.
#Add header file path (relative to this file path)
include_directories(include)

eg. call the so library to get the version number

#include <dhplay.h>
...
jstring nativeMethod1(JNIEnv *env, jclass clazz, jint param) {
	...
	unsigned version = PLAY_GetSdkVersion();
	...
}

6. Call the Native method

Generally, in a class that specifically accesses native methods, the so library is loaded with System.loadLibrary in the static code block of the class. Calling these declarations in the native method of the Java layer executes the corresponding implementation of native.

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

    // Statically register local methods.
    public static native String dataFromNative(int param);

    // Dynamically register local method 1.
    public static native String nativeMethod1(int param);

    ///Dynamically register local methods 2.
    public static native String nativeMethod2(int param);

}

7. Native calls Java methods.

7.1. Define the Java method to be called.

public class NativeBridge {
	...
	
    // Method for Native calls.
    public void printResult(String result) {
        System.out.println(" print result = " + result);
    }
}

7.2. Native call process.

  1. Gets the bytecode of the class where the Java method is located

    static const char *const jclassName = "com/hongenit/jnindkdemo/NativeBridge";
    // Gets the bytecode object of NativeBridge
    jclass jclazz = env->FindClass(jclassName);
    

    It can also be obtained by the following two methods

    // Get jclass through object instance, which is equivalent to getClass() function in Java
    jclass GetObjectClass(jobject obj): 
    
    // The jclass object of its parent class can be obtained through jclass
    jclass getSuperClass(jclass obj): 
    
  2. Get object

    The methodId of the construction method is fixed to '', and different construction methods are called according to different signatures.

    jobject obj= env->NewObject(jclazz,env->GetMethodID(jclazz, "<init>", "()V"));
    
  3. Call method

    // Gets the methodID of the Java method to call
    jmethodID methodId = env->GetMethodID(jclazz, "printResult", "(Ljava/lang/String;)V");
    // Call Java methods.
    env->CallVoidMethod(obj,methodId,env->NewStringUTF(("call from native " + std::to_string(param)).c_str()));
    
void nativeMethod2(JNIEnv *env, jclass clazz, jint param) {
    
    static const char *const jclassName = "com/hongenit/jnindkdemo/NativeBridge";
    // Gets the bytecode object of NativeBridge
    jclass jclazz = env->FindClass(jclassName);
    // Get NativeBridge object
    jobject obj= env->NewObject(jclazz,env->GetMethodID(jclazz, "<init>", "()V"));
    // Gets the methodID of the Java method to call
    jmethodID methodId = env->GetMethodID(jclazz, "printResult", "(Ljava/lang/String;)V");
    // Call Java methods.
    env->CallVoidMethod(obj,methodId,env->NewStringUTF(("call from native " + std::to_string(param)).c_str()));

}

matters needing attention

1. Memory leak of global reference object

reference

https://www.jianshu.com/p/d8be99605c65

https://blog.csdn.net/carson_ho/article/details/73250163

https://blog.csdn.net/q610098308/article/details/79395232

Tags: Java Android Apache JNI NDK

Posted on Fri, 12 Nov 2021 21:53:49 -0500 by bob_rock