Comprehensive analysis of JNI in Android development


foreword

JNI (Java Native Interface, Java local interface) is a set of standard local programming interfaces defined on the Java platform. JNI allows Java code to interoperate with native code (such as code implemented by C/C++ that depends on specific hardware and operating systems), that is, Java code can call native code, and native code can also call Java code. Calling native code through JNI can realize functions that cannot be realized in Java language. On the Android platform, the Dalvik virtual machine implements the interface defined by JNI.

Summary: JNI is a bridge between java and local Native code


1. The position of JNI in the Android system

Android adopts a layered architecture: the upper application layer and the application framework layer are mainly developed in Java language; the lower layer runs a Linux kernel, and integrates various core libraries and third-party libraries on top of the kernel to provide all necessary information for the system to run. The required services are developed in C and C++ languages. The link connecting these two parts is JNI.

insert image description here
It can be seen from the figure that JNI can directly call the local code library, and can realize the interaction with the application layer and the application framework layer through the Dalvik virtual machine. The code of the Android JNI part is mainly located in the upper two layers of the Android architecture:
□Application layer: developed using NDK, mainly implemented using the standard JNI programming model.
□Application framework layer: Android defines a set of JNI programming model, and uses the function registration method to make up for the deficiency of the standard JNI programming model.
The JNI part of the Android application framework layer is organized according to modules, and different modules will be compiled into different shared libraries to provide different services for the upper layer.
NDK与JNI的区别:NDK是为便于开发基于JNI的应用而提供的一套开发和编译工具集;而JNI则是一套编程接口,可以运用在应用层,也可以运用在应用框架层,以实现Java代码与本地代码的互操作。

The structure of the JNI programming model is very clear and can be summarized in the following three steps:

  1. The Java layer declares Native methods.
  2. The JNI layer implements the Native method declared by the Java layer, and the JNI layer can call the underlying library or call back the Java layer method. This part will be compiled into a dynamic library (SO file) for the system to load.
  3. Load the shared library generated after the JNI layer code is compiled.

Two, JNI framework layer instance analysis

In the development of Android applications, the log system is generally used by calling the Java interface provided by android.util.Log.java of the application framework layer. For example, we will write the following code to output the log:

Log.d(TAG,"debug 1og");

This Java interface actually calls the system runtime library (that is, the local library) through JNI and finally calls the kernel driver Logger to write the Log to the kernel space. In Android, the Log system is very general, and its JNI structure is very simple, so today we will use it as an example to analyze the specific calling process of JNI.

1. Log system Java layer analysis

First look at the Java layer part of the Log system. Open the Log.java file, and you can see that only two native methods, isLoggable and println_native, are defined here. The code is as follows (example):

package android.util; 
public final class Log ( 
......

public static int d(String tag,String msg)(
//使用Native方法打印日志.LOG_ID_MAIN表示日志ID,有4种:main、radio.events、system 
	return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
//声明Native方法isLoggable
public static native boolean isLoggable(String tag, int level); 

......

//声明Native方法printin_native
/**@hide*/ 
public static native int printin_native(int bufID,int priority, String tag, String msg); 
)

What the Java layer needs to do is as simple as that. You only need to declare the method as native without implementing it, and you can call it directly without any compilation errors.

2. The JNI layer of the Log system

The JNI layer is the most critical part of implementing Java layer methods. For the Log class, the corresponding JNI file is android_util_Log.cpp. The code is as follows (example):

#include "jni.h” //符合JNI规范的头文件,必须包含进来#include "JNIHelp.h"
//Android为更好地支持JNI提供的头文件 
#include "utils/misc.h"
#include "android_runtime/AndroidRuntime.h" 

/*这里便是Java层声明的isLoggable方法的实现代码。
 *JNI方法增加了JNIEnv和jobject两个参数,其余参数和返回值只是将Java参数映射成JNI
 *的数据类型,然后通过调用本地库和JNIEnv提供的JNI函数处理数据,最后返回给Java层*/

static jboolean isLoggable(const char* tag, jint level) {
    
    
    return __android_log_is_loggable(level, tag, ANDROID_LOG_INFO);
}

static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
{
    
    
    if (tag == NULL) {
    
    
        return false;
    }
	//这里调用了JNI函数
    const char* chars = env->GetStringUTFChars(tag, NULL);
    if (!chars) {
    
    
        return false;
    }

    jboolean result = false;
    if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
    
    
        char buf2[200];
        snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %zu characters\n",
                chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));

        jniThrowException(env, "java/lang/IllegalArgumentException", buf2);
    } else {
    
    
    	//这里调用了本地库函数
        result = isLoggable(chars, level);
    }

    env->ReleaseStringUTFChars(tag, chars);
    return result;
}
//以下是Java层声明的printin_Native方法的实现代码
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
        jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
    
    
    const char* tag = NULL;
    const char* msg = NULL;

    if (msgObj == NULL) {
    
    
        jniThrowNullPointerException(env, "println needs a message");
        return -1;
    }

    if (bufID < 0 || bufID >= LOG_ID_MAX) {
    
    
        jniThrowNullPointerException(env, "bad bufID");
        return -1;
    }

    if (tagObj != NULL)
        tag = env->GetStringUTFChars(tagObj, NULL);//这里调用了JNI函数
    msg = env->GetStringUTFChars(msgObj, NULL);

    int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);

    if (tag != NULL)
        env->ReleaseStringUTFChars(tagObj, tag);//这里调用了JNI函数释放资源
    env->ReleaseStringUTFChars(msgObj, msg);

    return res;
}

It can be seen from here that the implementation method of the JNI layer is only a mapping with the method declared by the Java layer according to certain rules, and then the JNI function provided by the native library function or JNIEnv can be used to respond to the call of the Java layer.

3. JNI method registration of the Log system

The JNI layer has implemented the Native method declared by the Java layer. But how are these two methods related? Or the source code in android_util_Log.cpp:

/*
 * JNI registration.
 */
static JNINativeMethod gMethods[] = {
    
    
    /* name, signature, funcPtr */
    {
    
     "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
    {
    
     "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
};

An array gMethods is defined here to store data of type JNINativeMethod. The definition of JNINativeMethod can be found in the jni.h file:

typedef struct {
    
    
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

It can be seen that JNINativeMethod is a structure type, which saves the one-to-one correspondence between the declared function and the implemented function.
Look at the first sentence in gMethods:

( "isLoggable", "(Ljava/lang/String;I)2", (void*) android_util_Log_isLoggable } 
  • The native function declared by the Java layer is named isLoggable.
  • The signature of the Native function declared by the Java layer is (Ljava/lang/String;I)Z.
  • The pointer to the implementation method of the JNI layer is (void*)android_util_Log_isLoggable.

So far, we have given the corresponding relationship between Java layer methods and JNI layer methods. But how to tell the virtual machine this correspondence? Continue to analyze the source code of android_util_Log.cpp. Navigate to the following sections:

int register_android_util_Log(JNIEnv* env)
{
    
    
    jclass clazz = FindClassOrDie(env, "android/util/Log");

    levels.verbose = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "VERBOSE", "I"));
    levels.debug = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "DEBUG", "I"));
    levels.info = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "INFO", "I"));
    levels.warn = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "WARN", "I"));
    levels.error = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ERROR", "I"));
    levels.assert = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ASSERT", "I"));

    return RegisterMethodsOrDie(env, "android/util/Log", gMethods, NELEM(gMethods));
}

This function finally calls AndroidRuntime::registerNativeMethods, and passes the gMethods array, Java layer class name, and a pointer of JNIEnv type to registerNativeMethods. Then we open AndroidRuntime.cpp and locate the registerNativeMethods function:

/*
 * Register native methods using JNI.
 */
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
    const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    
    
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

Then open JNIHelp.cpp

extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    
    
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);

    ALOGV("Registering %s's %d native methods...", className, numMethods);

    scoped_local_ref<jclass> c(env, findClass(env, className));
    if (c.get() == NULL) {
    
    
        char* msg;
        asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className);
        e->FatalError(msg);
    }

    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
    
    
        char* msg;
        asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className);
        e->FatalError(msg);
    }

    return 0;
}

Here, the RegisterNatives method of JNIEnv is finally called, and the associated information stored in gMethods is passed to the virtual machine, and RegisterNatives is found in jni.h:

jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,jint);

Its function is to register local methods to the class specified by jclass, so that the corresponding relationship between the java layer and the jni layer can be obtained delicately, and the interoperability between java and C/C++ can be realized.

Now that the whole process is basically clear, let's take a look at where the register_android_util_Log registration method is called?
It turns out that when the Android system is started, when the init process starts Zygote, a virtual machine will be created, and then the jni method of the system will be registered. The main code is:

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    
    
    ... ...
    
    if (startVm(&mJavaVM, &env, zygote) != 0) {
    
    
        return;
    }
    onVmCreated(env);

    /*
     * Register android functions.
     */
    if (startReg(env) < 0) {
    
    
        ALOGE("Unable to register all android natives\n");
        return;
    }

    ... ...

Which calls startReg:

/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
    
    
    /*
     * This hook causes all future threads created in this process to be
     * attached to the JavaVM.  (This needs to go away in favor of JNI
     * Attach calls.)
     */
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);

    ALOGV("--- registering native functions ---\n");

    /*
     * Every "register" function calls one or more things that return
     * a local reference (e.g. FindClass).  Because we haven't really
     * started the VM yet, they're all getting stored in the base frame
     * and never released.  Use Push/Pop to manage the storage.
     */
    env->PushLocalFrame(200);

    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
    
    
        env->PopLocalFrame(NULL);
        return -1;
    }
    env->PopLocalFrame(NULL);

    //createJavaThread("fubar", quickTest, (void*) "hello");

    return 0;
}

Then call register_jni_procs, where the function has a gRegJNI parameter, and look at the place where it is defined:

static const RegJNIRec gRegJNI[] = {
    
    
    REG_JNI(register_com_android_internal_os_RuntimeInit),
    ... ...
    REG_JNI(register_android_util_Log),
    ... ...

Sure enough, it contains the register_android_util_Log function, so that the jni of the Log system is registered when the system starts
JNIEnv是一个指针,指向一个线程相关的结构,该结构维护了一个数组,该数组每个元素指向一个JNI函数,通过JNIEnv操作JNI函数,访问虚拟机,进而操作java对象

The above is the legendary JNI dynamic registration method, which is mainly used for JNI registration in the framework layer of Android.

3. Call the JNI implementation method in java

1. Conversion between java data type and JNI data type

  1. Basic type conversion relationship
    The parameters passed by calling jni's native method in java are of java type. These parameters must be converted into JNI type by Dalvik virtual machine before they can be recognized by the JNI layer, as shown in the figure:
    java type JNI type word length
    boolean jboolean 8
    byte jbyte 8
    char jchar 16
    short jshort 16
    int jint 32
    long long 64
    float jfloat 32
    double jdouble 64
    void void -

For ease of use, it is deliberately defined as:

# define JNI_FALSE 0
# define JNI_TRUE 1
typedef jint jsize;//jsize整数类型表示大小
  1. Reference type conversion relationship The
    reference type of JNI defines nine array types, and four types of jobject, jclass, jstring, and jthrowable. Their inheritance relationship is as follows: The corresponding relationship between them
    JNI type inheritance relationship
    and java types is as follows:
    java type JNI type
    java.lang.Class jclass
    java.lang.String jstring
    java.lang.Throwable jthrowable
    Object[]、boolean[]、byte[]、char[]、short[]、int[]、long[]、float[]、double[] jobjectArray、jbooleanArray、jbyteArray、jcharArray、jshortArray、jintArray、jlongArray、jfloatArray、jdoubleArray
    java.lang.Object jobject

2. JNI method naming rules

In the Log system, the JNI implementation method is different from the Java declaration method. For example, the Native method name declared by the Java layer is isLoggable, while the method name of the corresponding NI implementation method is android_util_Log_isLoggable. It can be seen that in addition to the corresponding relationship between data types, there is also a corresponding relationship between method names.

The JNI interface pointer is the first parameter of the JNI implementation method, and its type is JNIEnv. The second parameter differs depending on whether the native method is static or non-static. The second parameter of a non-static native method is a reference to a Java object while the second parameter of a static native method is a reference to its Java class. The rest of the parameters correspond to the parameters of the Java method.
The JNI specification provides the naming rules of the JNI implementation method. The method name is composed of the following parts:

  • java prefix
  • fully qualified class name
  • Underscore (_) separator
  • Add the first parameter JNIEnv* env
  • Add the second parameter jobject
  • Other parameters are mapped by type
  • Return values ​​are mapped by type

Continue to take the Log system as an example to explain. Some methods in Java are declared as follows:

public static native boolean isLoggable(String tag,int level);

Some methods of JNI are implemented as follows:

static jboolean android_util_Log_isLoggable(JNIEnv*env,jobject clazz,jstring tag,jint level).....)

It can be seen from the NI implementation method of the Log system that Android does not strictly abide by the method naming convention of JNI.
Android在框架层采用函数注册的方式,建立Java层声明方法与JNI层实现方法之间的对应关系,可以不遵守上述命名规则。

Strictly follow the naming rules here to register, that is, static registration. Generally, it is more convenient to use this method when developing NDK

3. JNI method signature rules

With the corresponding relationship between data types, JNI can correctly identify and convert Java types. So how does JNI identify Java methods? Java supports method overloading, and a method cannot be uniquely determined only by the function name. So JNI provides a set of signature rules, using a string to uniquely determine a method. Its rules are as follows:

(参数1类型签名参数2类型签名…参数n类型签名)返回值类型签名 //中间没有空格

The rules for type signatures are as follows:

Java type type signature Java type type signature
boolean Z long J
byte B float F
char C double D
short S kind L fully qualified class name;
int I array [element type signature

Remember the code in gMethods:

static JNINativeMethod gMethods[] = {
    
    
    /* name, signature, funcPtr */
    {
    
     "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
    {
    
     "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
};

Now you should understand that isLoggable has two parameters, one is String type, the other is int type, and the return value is boolean.

4. JNI operates java objects

As mentioned earlier, JNI is a bridge between java and Native, which can interoperate with each other. What I mentioned earlier is that the java layer calls the Native method, so how does the Native layer call or operate the java layer? Then you need to pay attention to the second parameter jobject of the JNI method function

1. Access java objects

To operate a jobject is to access this object and operate its variables and methods. There are many class or object operation functions provided by JNI, and there are two commonly used ones: FindClass and GetObjectClass, which have different function prototypes in C and C++.
The function prototype in C++ is as follows:

jclass FindClass(const char* name); //查找类信息
jclass GetObjectClass(jobject obj); //返回对象的类

The function prototype in C is as follows:

jclass (*Findclass)(JNIEnv,const char);
jclass (*GetObjectClass)(JNIEnv*, jobject);

We can see how the Log system operates on Java objects. Open android_util_Log.cpp and locate the register_android_util_Log function:

int register_android_util_Log(JNIEnv* env){
    
    
	jclass clazz = env->FindClass("android/util/Log");
	... ...
}

Just pass in the fully qualified class name of the class to be searched for (separated by "/" path) to FindClass, and then the method returns a jclass object, so that the methods and variables of this class can be operated.

2. Manipulating member variables and methods

The information clazz of the class we got above, through which we can operate its variables and methods

levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));

Where DEBUG is the name of the Java domain to be accessed, and I is the type signature of the Java domain. The function prototype of GetStaticFieldID is as follows:

jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)

This function returns a jfieldID, which represents a Java member variable. Finally, pass the jfieldID to the GetStaticIntField method to get the value of the member variable DEBUG of the Java layer, which is 3. You can open Log.java to see if the value of the variable DEBUG is 3.
The functions provided by JNI to manipulate fields and methods are listed in the following table.

access object variable call instance method access static variables call static method
GetFieldID GetMethodID GetStaticFieldID GetStaticMethodID
Get<Type>Field Call<Type>Method GetStatic<Type>Field CallStatic<Type>Method
Set<Type>Field CallNonvirtual<Type>Method SetStatic<Type>Field -

3. Global references, weak global references and local references

The life cycle of a Java object is managed by the virtual machine. The virtual machine maintains a reference count of an object inside. If the reference count of an object is 0, the object will be reclaimed by the garbage collector and the memory will be released. Here is a question, if the Native method is used in the Java object, what impact will it have on the life cycle of the object? Before answering this question, let's look at the example of the Log system. code show as below:

//static jobject clazz_ref1 = NULL;
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz,jstring tag,jint level){
    
    
	//clazz_ref1 = clazz;
	//static jobject clazz_ref2 = NULL;
	//clazz_ref2 = clazz;
	if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY _KEY_MAX)(
		//异常处理代码
		... ...
	}else{
    
    
		result = isLoggable(chars, level);
	}
	
}

The code commented out above refers to java objects in two ways. Can the virtual machine correctly add reference counts to this object in this form of writing? ? ? The answer is no, the virtual machine cannot increase its count correctly, the clazz object may be recycled, then clazz_ref1 or clazz_ref2 may refer to a wild pointer.

Fortunately, JNIEnv has provided us with solutions: local references, global references and weak global references.

  1. Local reference: You can increase the reference count, the scope of action is this thread, and the life cycle is a Native call. Local references include references created by most JNI functions, Native method return values ​​and parameters. A local reference is only valid in the thread that created its Native method, and is only valid in one call of the Native method. After the method returns, it is recycled by the virtual machine (different from local variables in C, which will be recycled immediately after returning) .
  2. Global references: Can increase the reference count. The scope of action is multi-threading, multiple Native methods, life cycle to explicit release. Global references are created through the JNI function NewGlobalRef and released through DeleteGlobalRef. If the programmer doesn't release it explicitly, it will never be garbage collected.
  3. Weak global references: reference counts cannot be increased. The scope of action is multi-threading, multiple Native methods, life cycle to explicit release. However, its corresponding Java object life cycle still depends on the virtual machine, which means that even if the weak global reference has not been released, the Java object it refers to may have been released. Weak global references are created by the JNI function NewWeakGlobalRef and released by DeleteWeakGlobalRef. The advantage of a weak global reference is that it can save the object without preventing the object from being recycled.
    IsSameObject函数用来判断弱引用对应的对象是否已经被回收,方法是用弱全局引用和NULL进行比较,如果返回JNI_TRUE,则说明弱全局引用指向的对象已经被回收。
    So the above code can be changed to:
//static joject g_clazz_ref = NULL;
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz,jstring tag,jint level){
    
    
	//g_class_ref = env->NewGlobalRef(clazz);
	if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY _KEY_MAX)(
		//异常处理代码
		... ...
	}else{
    
    
		result = isLoggable(chars, level);
	}
}
//在不使用该类的时候显式删除
env->DeleteGlobalRef(g_clazz_ref)

Five, JNI exception handling

JNI provides two ways to check for exceptions:

  1. Check if the return value of the last JNI function call is NULL
  2. Determine whether an exception has occurred by calling the JNI function ExceptionOccurred()

There are also two ways to handle exceptions:

  1. The Native method can choose to return immediately, and the exception will be thrown in the Java code that calls the Native method, so the Java layer must catch it
  2. The Native method calls ExceptionClear() to clear the exception, and then executes its own exception handling code

The functions provided by JNI to check and handle exceptions are shown in the following table.

JNI exception handling function Functional description
Throw throws the existing exception
ThrowNew throw new exception
ExceptionOccurred Determine whether an exception occurs and obtain a reference to the exception
ExceptionCheck Determine whether an exception occurs
ExceptionDescribe exception stack information
ExceptionClear Clear an unhandled exception
FatalError critical error, exit

注意异常出现后,Native相关代码必须先检查清除异常,然后才能进行其他的JNI函数调用。当有异常未被清除时,只有以下JNI异常处理函数可被安全地调用:ExceptionOccurred()、ExceptionDescribe()、ExceptionClear()
Next, let's go back to the source code analysis:

static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
{
    
    
    if (tag == NULL) {
    
    
        return false;
    }

    const char* chars = env->GetStringUTFChars(tag, NULL);
    if (!chars) {
    
    
        return false;
    }

    jboolean result = false;
    if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
    
    
        char buf2[200];
        snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %zu characters\n",
                chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));

        jniThrowException(env, "java/lang/IllegalArgumentException", buf2);
    } else {
    
    
        result = isLoggable(chars, level);
    }

    env->ReleaseStringUTFChars(tag, chars);
    return result;
}

Find the jniThrowException function definition:

extern "C" int jniThrowException(C_JNIEnv* c_env, const char* className, const char* msg) {
    
    
    JNIEnv* env = reinterpret_cast<JNIEnv*>(c_env);
    jclass exceptionClass = env->FindClass(className);

    if (exceptionClass == NULL) {
    
    
        ALOGD("Unable to find exception class %s", className);
        /* ClassNotFoundException now pending */
        return -1;
    }

    if (env->ThrowNew(exceptionClass, msg) != JNI_OK) {
    
    
        ALOGD("Failed throwing '%s' '%s'", className, msg);
        /* an exception, most likely OOM, will now be pending */
        return -1;
    }

    env->DeleteLocalRef(exceptionClass);
    return 0;
}

You will see that calling ThrowNew throws a new exception to the Java layer that calls it

6. Comparison of two registration methods

1. Static registration

Advantages: Conform to JNI specification, convenient and simple
Disadvantages: Need to follow the cumbersome naming rules of JNI implementation methods. Loading the code of the shared library, if the application layer calls frequently, the efficiency will be seriously affected. The efficiency of the virtual machine searching and locating the JNI implementation method in the shared library is also affected.

2. Dynamic Registration

Advantages: high efficiency, no need to follow the naming rules, easy to modify and transplant.
Disadvantages: The implementation steps are more than static registration, which is a little troublesome.

How to use dynamic registration in NDK development? Above code:

static JNINativeMethod gmethods[] = {
    
    
    {
    
    "show", "()Ljava/lang/String;, (void*)Java_com_hision_jni_AppJniActivity_show}
};

static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* gMethods, int numMethods){
    
    
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
    
    
        return JNI_FALSE;
    }
    //调用JNIEnv提供的注册函数向虚拟机注册
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0){
    
    
        return JNI_FALSE;
    }
    return JNI_TRUE;

}

static int registerNatives(JNIEnv* env){
    
    
    if (!registerNativeMethods(env, "com/hision/jni/AppJniActivity", methods, sizeof(methods) / sizeof(methods[0]))) {
    
    
        return  JNI_FALSE;
    }
    return JNI_TRUE;
}

/*虚拟机执行System.loadLibrary("app_jni")后,进入libapp_jni.so后
*会首先执行这个方法,所以我们在这里做注册的动作*/

jint JNI_OnLoad (JavavM* vm, void* reserved){
    
    
    jint result = -1;
    JNIEnv* env = NULL;
    if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4)){
    
    
        goto fail;
    }
    //最终调用(*env)->RegisterNatives,这跟Log系统是一样的
    if (registerNatives(env) != JNI_TRUE) {
    
    
        goto fail;
    }
    result = JNI_VERSION_1_4;

fail:
    return result;
}


Summarize

This article takes the JNI instance of the Log system as the lead, and runs through the main aspects of JNI technology. If you really read this article carefully, you will have enough understanding of JNI, which will be helpful for in-depth study of the framework layer code. If you find an error in the article, please correct me!

Guess you like

Origin blog.csdn.net/h397318057/article/details/130055402