I studied Android JNI, but there are a few points I don't understand

I studied Android JNI, but there are a few points I don't understand

[[437215]]

This article is reprinted from the WeChat public account "Program Cat Adult", the author is Program Cat Adult. Please contact Program Cat Adult's public account to reprint this article.

Differences between Java threads and Native (OS) threads

Connection: Java threads are actually an encapsulation of an OS thread, and are essentially OS threads. [Previous versions of Java threads are not OS threads, but user-mode threads (Green Threads) constructed by the JVM, which cannot fully utilize the CPU. Later, they have been changed to use OS threads. ] [Reference https://mp.weixin.qq.com/s/Gxqnf5vjyaI8eSYejm7zeQ]

the difference:

Java threads can directly get JNIEnv, while OS threads need to attach to JVM before they can get JNIEnv. [My personal understanding is that the difference lies in whether JVM is attached]

  1. jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);

The Java thread can FindClass successfully, but the OS thread fails to FindClass because the ClassLoaders of the two are different. The ClassLoader held by the OS thread after AttachCurrentThread is the system's ClassLoader. If you want to FindClass successfully, you need to obtain a copy of the current library's ClassLoader during JNI_Onload and save it, and use this ClassLoader to operate the next time you FindClass.

  1. static jobject g_class_loader = NULL ;
  2. static jmethodID g_find_class_method = NULL ;
  3. void on_load() {
  4. JNIEnv *env = get_jni_env();
  5. if (!env) {
  6. return ;
  7. }
  8. jclass capture_class = (*env)->FindClass(env, "com/captureandroid/BMMCaptureEngine" );
  9. jclass class_class = (*env)->GetObjectClass(env, capture_class);
  10. jclass class_loader_class = (*env)->FindClass(env, "java/lang/ClassLoader" );
  11. jmethodID class_loader_mid = (*env)->GetMethodID(env, class_class, "getClassLoader" , "()Ljava/lang/ClassLoader;" );
  12. jobject local_class_loader = (*env)->CallObjectMethod(env, capture_class, class_loader_mid);
  13. g_class_loader = (*env)->NewGlobalRef(env, local_class_loader);
  14. g_find_class_method =
  15. (*env)->GetMethodID(env, class_loader_class, "findClass" , "(Ljava/lang/String;)Ljava/lang/Class;" );
  16. }
  17.  
  18. jclass find_class(const char * name ) {
  19. JNIEnv *env = bmm_util_get_jni_env();
  20. if (!env) {
  21. return   NULL ;
  22. }
  23. jclass ret = (*env)->FindClass(env, name );
  24. jthrowable exception = (*env)->ExceptionOccurred(env);
  25. if (exception) {
  26. (*env)->ExceptionClear(env);
  27. jstring name_str = (*env)->NewStringUTF(env, name );
  28. ret = (jclass)(*env)->CallObjectMethod(env, g_class_loader, g_find_class_method, name_str);
  29. (*env)->DeleteLocalRef(env, name_str);
  30. }
  31. return ret;
  32. }

What does JNI do?

Here is a passage from the official document https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp16696 translated by someone else:

The most important design goal of JNI is to provide binary compatibility between JVMs on different operating systems, so that a native library can run on JVMs of different systems without recompiling. To achieve this, JNI cannot care about the internal implementation of JVM when designing, because the internal implementation mechanism of JVM is constantly changing, and we must keep the JNI interface stable. The second design goal of JNI is efficiency. We may see that sometimes we may need to sacrifice a little efficiency to meet the first goal, so we need to make some choices between platform independence and efficiency. Finally, JNI must be a complete system. It must provide enough JVM functions for native programs to complete some useful tasks. JNI cannot only target a specific JVM, but also provide a series of standard interfaces so that programmers can load their native code libraries into different JVMs. Sometimes, calling the interface implemented under a specific JVM can provide efficiency, but in more cases, we need to use more general interfaces to solve the problem.

JNIEnv and JavaVM

It's a function pointer.

The following figure is the pointer structure of JNIEnv:

JNIEnv is actually an interface pointer pointing to local thread data, which contains a pointer to the function interface. Each interface function has a predefined offset position in this table, similar to the C++ virtual function table.

The code is as follows:

  1. typedef const struct JNINativeInterface *JNIEnv;
  2.  
  3. struct JNINativeInterface {
  4. void* reserved0;
  5. void* reserved1;
  6. void* reserved2;
  7. void* reserved3;
  8.  
  9. jint (*GetVersion)(JNIEnv *);
  10.  
  11. jclass (*DefineClass)(JNIEnv*, const char *, jobject, const jbyte*,
  12. jsize);
  13. jclass (*FindClass)(JNIEnv*, const char *);
  14. jobject (*AllocObject)(JNIEnv*, jclass);
  15. jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...);
  16. jobject (*NewObjectV)(JNIEnv*, jclass, jmethodID, va_list);
  17. jobject (*NewObjectA)(JNIEnv*, jclass, jmethodID, const jvalue*);
  18. ...
  19. };
  20. JavaVM-like
  21. struct JNIInvokeInterface {
  22. void* reserved0;
  23. void* reserved1;
  24. void* reserved2;
  25.  
  26. jint (*DestroyJavaVM)(JavaVM*);
  27. jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
  28. jint (*DetachCurrentThread)(JavaVM*);
  29. jint (*GetEnv)(JavaVM*, void**, jint);
  30. jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
  31. };
  32.  
  33. typedef const struct JNIInvokeInterface* JavaVM;

Knowledge point 1: Why use function tables instead of hard-coding certain function items?

The JNI namespace can be separated from the native code, and a virtual machine can provide multiple versions of JNI function tables for different scenarios. For example, a virtual machine can support two types of JNI function tables:

One is for debugging, doing more error checking.

One for publishing, which does less error checking and is more efficient.

Knowledge point 2: JNIEnv is thread-local, which means it is only valid in the current thread. Native methods cannot pass JNIenv from the current thread to another thread. JNIEnv cannot be used across threads [as for why JNIEnv is designed to be thread-local, I don't understand].

Knowledge point 3: Although threads do not share JNIEnv, they share JavaVM, and then the JNIEnv of the current thread can be obtained through GetEnv.

jint GetEnv(JavaVM *vm, void **env, jint version);

Knowledge point 4: Native methods receive JNI interface pointers as parameters. The virtual machine ensures that the same JNIEnv is passed to Native methods in the same thread. If different threads call Native methods, the JNIEnv passed to them is different. However, the function table indirectly pointed to by JNIEnv is shared among multiple threads.

Knowledge point 5: Why does calling a native method in C language require passing JNIEnv as a parameter, but not in C++?

  1. // C language
  2. jstring model_path = (*env)->NewStringUTF(env, path);
  3. // C++
  4. jstring model_path = env->NewStringUTF(path);

The JNIEnv listed above is in C language form. Java also encapsulates a layer of JNIEnv for C++. The simplified version of the code is:

  1. struct _JNIEnv {
  2. /* do not rename this; it does not seem to be entirely opaque */
  3. const struct JNINativeInterface* functions;
  4.  
  5. #if defined(__cplusplus)
  6.  
  7. jint GetVersion()
  8. { return functions->GetVersion(this); }
  9.  
  10. jclass FindClass(const char * name )
  11. { return functions->FindClass(this, name ); }
  12. #endif
  13. }

In fact, it is essentially an interface in the form of a C language call.

How to pass data in JNI

I won't go into detail here, but basically, basic types like int and float are copied, while objects and byte arrays use references. So it actually takes little time to transfer byte stream data from the Java layer to the Native layer, and no copying will occur [but if the Native layer wants to use and hold this data, it has to make a copy itself].

There are also some knowledge points such as GlobalReference, LocalReference and why to Delete LocalReference. These are relatively basic and will not be introduced here. I guess everyone understands them.

Recommended Reading

https://www.cnblogs.com/kexinxin/p/11689641.html

NDK official documentation

https://developer.android.com/ndk/guides

References

http://luori366.github.io/JNI_doc/jni_design_theory.html

https://www.cnblogs.com/kexinxin/p/11689641.html

https://developer.android.com/ndk/guides

<<:  Detailed explanation of the use and properties of three types of animations in App development

>>:  WeChat and Alipay personal payment codes are disabled. Does this have any impact on ordinary users?

Recommend

How to increase user operations for ToB during the COVID-19 epidemic?

Before the Spring Festival, I met with the market...

How do advertisers choose KOC in 2022?

In 2019, the concept of KOC (Key Opinion Consumer...

Why watch live broadcasts on Xiaohongshu?

Xiaohongshu needs to break out of its niche. For ...

Alcatel's new phone is equipped with three operating systems

January 4 (Reporter Zhang Qian) Recently, Alcatel...

How to attract traffic to hairy crabs on Zhihu?

Get 5000+ customers at 0 cost, how does Daxiex at...

Air turns into fuel? This technology may hold the code to energy

Produced by: Science Popularization China Produce...

How to fine-tune the operation of JD.com's no-source e-commerce store group

Nowadays, everything advocates refined operations...

He Jia's 21-day personal influence building plan

He Jia CEO Speech Coach • Tutored the CEO class o...