Like attention, no more lost, your support means a lot to me!

🔥 Hi, I’m Chouchou. GitHub · Android-Notebook has been included in this article. Welcome to grow up with Chouchou Peng. (Contact information at GitHub)


directory


1. An overview of the

  • What is JNI function registration? When a Java VIRTUAL machine calls a native method, it needs to call the corresponding JNI function. JNI function registration discusses how to determine the mapping between Natvie method and JNI function.

  • JNI function registration methods: static registration + dynamic registration

  • Advantages and disadvantages of the two registration methods:

    • Static registration has the advantage of simplicity because it uses convention based naming conventions, so function declarations can be generated automatically through Javah or the IDE. The disadvantage is that when you change the Java class name or method name, you need to change the JNI function name synchronously.
    • The advantage of dynamic registration is that it is flexible, because dynamic registration allows you to define mappings between Java method and JNI function names freely, and you only need to change the mappings when Java class or method names are used. The disadvantage is that the convenience of static registration-based conventions is sacrificed.

Tip: Some sources describe registering JNI functions as linking JNI functions/linking local methods, which means the same thing.


2. Static registration

Static registration is based on “convention” naming rules, javah can automatically generate the corresponding function declaration of native methods. Such as:

HelloWorld.java

package com.xurui.hellojni; public class HelloWorld { public native void sayHi(); } // Execute javac -h.helloWorld.java (merge javac and Javah)Copy the code

com_xurui_hellojni_HelloWorld.h

. JNIEXPORT void JNICALL Java_com_xurui_hellojni_HelloWorld_sayHi (JNIEnv *, jobject); .Copy the code

2.1 Naming Rules

Statically registered naming rules are classified into “no overload” and “overloaded” : The “short name” rule is used when there is no overload, and the “long name” rule is used when there is overload.

  • Short Name rule

    • 1, prefixJava_;
    • 2. The fully qualified name of the class (with an underscore delimiter)_);
    • 3. Method name;
  • Long Name rule (Long Name)

    • 4. Append the short name with two underscores (__) and parameter descriptors

Tip: You can use the Javap command to generate header files that conform to naming conventions.

1.2 Source Code Analysis

I load the so library as a clue to browse the source code, the initial determination of static registration search source. However, because did not find the source code to call this method, also did not consult the corresponding data, so can only say preliminary determination.

java_vm_ext.cc

STD ::unique_ptr<Libraries> libraries_; Void * FindNativeMethod(Thread* self, ArtMethod* m, STD ::string jni_short_name(m->JniShortName()); std::string jni_long_name(m->JniLongName()); Void * native_code = FindNativeMethodInternal(self, declaring_class_loader_allocator, shorty, jni_short_name, jni_long_name); return native_code; } -> 2, void* FindNativeMethodInternal(Thread* self, void* declaring_class_loader_allocator, const char* shorty, const std::string& jni_short_name, const std::string& jni_long_name) { for (const auto& lib : libraries_) { SharedLibrary* const library = lib.second; If (library->GetClassLoaderAllocator()! = declaring_class_loader_allocator) { continue; Const char* arg_shorty = library->NeedsNativeBridge()? shorty : nullptr; Void * fn = dlsym(library, jni_long_name) {fn = dlsym(library, jni_long_name)} if (fn! = nullptr) { return fn; } } return nullptr; }Copy the code

art_method.cc

STD ::string ArtMethod::JniShortName() {return GetJniShortName(GetDeclaringClassDescriptor(), GetName()); } STD ::string ArtMethod::JniLongName() {STD ::string long_name; long_name += JniShortName(); long_name += "__"; std::string signature(GetSignature().ToString()); signature.erase(0, 1); signature.erase(signature.begin() + signature.find(')'), signature.end()); long_name += MangleForJni(signature); return long_name; }Copy the code

descriptors_names.cc

STD ::string GetJniShortName(const STD ::string& class_descriptor, const STD ::string& method) {omitted}Copy the code

The above code has been very simplified, the main flow is as follows:

  • 1. Calculate short and long names of native methods;
  • 2. Determine the classloader that defines the native method class in the loaded SO librarylibraries_To search for JNI functions that implement native methods.
  • 3. Establish internal data structures so that calls to native methods can be directed directly to JNI functions.

About load so library process before I wrote an article said: “the NDK | about so library from loading to unloading process”, after loading the Shared library is stored in the libraries_ in the table.


3. Dynamic registration

In addition to static registration based on conventions, dynamic registration can also be used to determine the mapping between native methods and JNI functions. Dynamic registration requires RegisterNatives(…) Function.

3.1 RegisterNatives (…). function

JNI_Onload(…) Function to perform dynamic registration, for example:

android_media_MediaPlayer.cpp

jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { ... if (register_android_media_MediaPlayer(env) < 0) { ALOGE("ERROR: MediaPlayer native registration failed\n"); goto bail; }... } - > 2, call AndroidRuntime: : registerNativeMethods static int register_android_media_MediaPlayer (JNIEnv * env) {return AndroidRuntime::registerNativeMethods(env, "android/media/MediaPlayer", gMethods, NELEM(gMethods)); } static const JNINativeMethod gMethods[] = {{"nativeSetDataSource", "(Landroid/ OS /IBinder; Ljava/lang/String; [Ljava/lang/String;" "[Ljava/lang/String;)V", (void *)android_media_MediaPlayer_setDataSourceAndHeaders }, { "_setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD} }, ... }Copy the code

The above code, gMethods array defines the native method the mapping relationship with JNI methods, and call AndroidRuntime: : registerNativeMethods function will eventually called RegisterNatives (…). Function to bind each mapping in the gMethods array in turn.

JNIHelp.cpp

- > call AndroidRuntime: : registerNativeMethods - > final call is: JNIHelp.cpp extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); scoped_local_ref<jclass> c(env, findClass(env, className)); RegisterNatives if (*env)->RegisterNatives(e, c.et (), gMethods, numMethods) < 0) {... } return 0; }Copy the code

Where JNINativeMethod is a structure defined in jni.h:

typedef struct { const char* name; Const char* signature; The method descriptor for native methods void* fnPtr; JNI function pointer} JNINativeMethod;Copy the code

Note: It should be noted that many sources refer to Signature as a JNI concept, but it is actually a string used to describe methods in JVM bytecodes, a bytecode concept.


4. Timing of registering JNI functions

After my summary, there are three kinds of opportunities to register JNI functions, and these three scenarios are relatively common:

Timing of registration Corresponding registration mode
1. When the VM invokes native methods for the first time Static registration
2. When the Android VM starts Dynamic registration
3. When loading so library Dynamic registration
  • 1. When a VM invokes native method for the first time: This timing corresponds to static registration. When a VM invokes native method for the first time, it searches for the corresponding JNI function and registers it.

  • 2. Android VIRTUAL machine startup: During the App process startup process, JNI function registration will be performed after the VIRTUAL machine is created. Native methods can be found in many Framework sources, but not calling System.loadLibrary(…). In fact, the registration is completed when the virtual machine is started.

AndroidRuntime.cpp

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) { ... if (startReg(env) < 0) { ALOGE("Unable to register all android natives\n"); }... } start -> startReg:  int AndroidRuntime::startReg(JNIEnv* env) { androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);  env->PushLocalFrame(200); if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) { env->PopLocalFrame(NULL); return -1; } env->PopLocalFrame(NULL); return 0; } startReg - > register_jni_procs:  static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env) { for (size_t i = 0; i < count; If (array[I].mproc (env) < 0) {return -1; } } return 0; } static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_os_RuntimeInit), REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit), REG_JNI(register_android_os_SystemClock), REG_JNI(register_android_util_EventLog), ... } struct RegJNIRec { int (*mProc)(JNIEnv*); }Copy the code

As you can see, startReg() is called when the Android virtual machine starts. It iterates over calls to the gRegJNI array, which is a set of Pointers to functions that register JNI functions.

  • 3. Loading so library:When the SO library is loaded, a callback is calledJNI_Onload(..), so this is a good time to register JNI functions, such as those mentioned aboveMediaPlayerThis is also the time to register JNI functions.

5. To summarize

  • Takes an exam the advice

1, you should understand the two ways to register JNI functions: static registration & dynamic registration; 2. Understand the function naming convention for static registration, and RegisterNatives(…) for dynamic registration call. ; 3. Know the three times to register JNI functions.


The resources

  • Principles of Android JNI by Gityuan
  • JNI Programming Guide

Creation is not easy, your “three lian” is chouchou’s biggest motivation, we will see you next time!