JNI mechanism of Android Framework layer

1. JavaVM: Indicates the Java virtual machine
2. JNIEnv: Indicates the context of the JNI environment, such as registration, lookup classes, exceptions, etc.
3.jclass: Java class represented in JNI
4.jmethodID: The method in the Java class represented in JNI
5. jfiledID: attribute in the Java class represented in JNI
Thread: Through the AttachCurrentThread and DetachCurrentThread methods in JNI, the combination with Java thread is realized.
They are all in a header file called jni.h, which is a very important header file in the JNI mechanism.
Source code path: /libnativehelper/include/nativehelper/jni.h
The source files in the libnativehelper directory will generate a dynamic library of libnativehelper.so after compilation. In fact, jni.h is a header file written by Android according to the standard of Java local calling, which includes basic types (type mapping), and definitions of data structures such as JavaVM, JNIEnv, jclass, jmethodID, and jfiledID.
JavaVM corresponds to the JNIInvokeInterface structure in jni.h, representing a virtual machine. JNIEnv corresponds to the JNINativeInterface structure, representing the JNI environment. During the use of JNI, most of the functions called come from the JNINativeInterface structure. For example, it handles functions such as searching for Java properties and methods, accessing Java properties, and calling Java methods. In addition, in the JNINativeInterface structure, a JNINativeMethod structure is involved, which represents a method implemented locally, that is, the native method, which will be used when JNI is registered later.

Two, the existence of JNI in Android
The existence of JNI in Android is mainly divided into two types: the use of JNI in the framework layer and the application layer. This chapter mainly looks at the JNI in the framework layer.
Java framework layer, the main source code path of JNI content is: /frameworks/base/core/jni
The code inside will generate a dynamic library of libandroid_runtiem.so. The use of JNI in the Log to be analyzed next is in this directory.
3. JNI usage of Framework layer Log
Log of the Java framework layer
When programming, everyone has used Log. In fact, the Log tool we often use finally calls the native method in the Java framework layer.
Paste the source code path: /frameworks/base/core/java/android/util/Log.java
The implementation of Log’s JNI is in a source file called android_util_Log.cpp.
Source path:
Header file: /frameworks/base/core/jni/android_util_Log.h.
Source file: /frameworks/base/core/jni/android_util_Log.cpp.
In the android_util_Log.cpp source file, we can find the figure of println_native
/*

  • JNI registration.
    /
    static const 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 },
    { “logger_entry_max_payload_native”, “()I”, (void*) android_util_Log_logger_entry_max_payload_native },
    };
    JNINativeMethod is a structure mentioned earlier, this structure represents an implemented local method. This structure is defined in the jni.h file as follows:
    typedef struct {
    const char* name;
    const char* signature;
    void* fnPtr;
    } JNINativeMethod;
    It has three pointer variables, the first is a character pointer, which can represent a string, that is, the name of the native method; the second is also a character pointer, which can also represent a string, representing the parameters and return value of the native method (There is a special representation method); the third is an unspecified type pointer, which represents a function pointer, pointing to the jni function corresponding to this native method.
    With the understanding of JNINativeMethod, you can understand the meaning of println_native in the android_util_Log.cpp source file. Its corresponding jni implementation function is android_util_Log_println_native(). In the jni implementation function, the __android_log_buf_write() method is called again. __android_log_buf_write is the bottom-level local Log library of Android on top of the C library based on the local framework layer (non-Java framework layer).
    The source code path of the Log library is:
    Header file: system/core/include/cutils/log.h.
    Source file: system/core/liblog.
    After compilation, liblog.so dynamic library and liblog.a static library will be generated

Log’s JNI registration
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”));
return RegisterMethodsOrDie(env, “android/util/Log”, gMethods, NELEM(gMethods));
}
This is the registration of jni in the android_util_Log.cpp source file. You can see that the RegisterMethodsOrDie() method is called, and the gMethods array we saw earlier is passed in for JNI registration. Is it over here? When I first saw this, I thought it was over. However, who called the register_android_util_Log() method? What is done in the RegisterMethodsOrDie() function?
The register_android_util_Log() method is only defined in the android_util_Log.cpp source file. It is necessary to find out who called it in order to further understand the registration process of Log’s JNI. The Android source code environment has a very good method to find the files that have appeared through strings.
Similar to: find . -type f -name “*.cpp” | xargs grep “register_android_util_Log”
Through the results, you can find an AndoridRuntime.cpp, what the hell is this? It can only be said that it is very strong, and it is a tool class when the system is running, providing support for the operation of Android. Part of the encapsulation of JNI is also in this class.
Source path:
/frameworks/base/include/android_runtime/AndroidRuntime.h.
/frameworsk/base/core/jni/AndroidRuntime.cpp.
It can be found that it is also in the /frameworsk/base/core/jni directory, indicating that it is also in the libandroid_runtime.so dynamic library.
In the AndroidRuntime.cpp source file gRegJNI array, the register_android_util_Log method was found.
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_os_RuntimeInit),
REG_JNI(register_android_os_SystemClock),
REG_JNI(register_android_util_EventLog),
REG_JNI(register_android_util_Log),
REG_JNI(register_android_util_MemoryIntArray),
//omitted
}
Then, in the AndroidRuntime::startReg() method of the AndroidRuntime.cpp source file, the gRegJNI array is used.

/*
 * Register android native functions with the VM.
 */
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{<!-- -->
    ATRACE_NAME("RegisterAndroidNatives");
    /*
     * 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 ---\
");
    /*
     * 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;
}

According to the source code comment, here is registering the local method with the virtual machine. Also in the AndroidRuntime.cpp source file, AndroidRuntime::start() calls AndroidRuntime::startReg(). Here, if you need to continue to look down, you have to find out who called the AndroidRuntime::start() method . However, knowing who called it already involves knowledge of the zygote.
zygote is the name of a daemon started by the init process reading init.rc. If you start from the bottom, you have to introduce the local stage of the Android startup process. This part belongs to the expanded understanding.
The Linux kernel runs, and the general content of Linux usually exists in the form of binary code.
The kernel loads the root file system, which is common to Linux.
The init process runs, the first process in user space.
Run the init.rc script.
Mount the system and date filesystems.
Run various services, mainly various daemon processes.
The local part is started and a series of daemon processes are formed, among which the daemon process named zygote will continue to complete the initialization of the Java part.
Run a daemon called zygote from a local executable.
zygote runs ZygoteInit (into the Java program).
ZygoteInit runs SystemServer (Java class), and forks off new processes.
SystemServer first runs the initialization in the libandroid_servers.so library (enters the local program).
Execute system initialization in libanroid_servers.so.
The Java initialization in SystemServer is called again (reentry Java program).
Create a ServerThread thread.
The ServerThread thread establishes each service and then enters the loop.
ActivityManagerService sends relevant information at the end of startup.
These are the knowledge cited in the reference book. If you want to learn more, you can read “Android Core Principles and Efficient Development of System-level Applications” or “In-depth Understanding of Android System”
Back to our zygote process, init.rc contains an init.

r

o

.

z

the y

g

o

t

e

.

r

c

.

i

no

i

t

.

r

c

and

i

no

i

t

.

{ro.zygote}.rc. init.rc and init.

ro.zygote.rc. init.rc and init.{ro.zygote}.rc source path: /system/core/rootdir.
In init.zygote32.rc, the relevant content is as follows:

service zygote /system/bin/app_process -Xzygote /system/bin –zygote –start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
writepid /dev/cpuset/foreground/tasks
This is a special syntax in the Android system. It starts a process named zygote, which is the executable program /system/bin/app_process.
The source path is: /frameworks/base/cmds/app_process.
In this directory, there is a source file of app_main.cpp, the relevant code is as follows:
// omitted
if (zygote) {
runtime.start(“com.android.internal.os.ZygoteInit”, args, zygote);
} else if (className) {
runtime.start(“com.android.internal.os.RuntimeInit”, args, zygote);
} else {
fprintf(stderr, “Error: no class name or –zygote supplied.\
”);
app_usage();
LOG_ALWAYS_FATAL(“app_process: no class name or –zygote supplied.”);
return 10;
}
In the app_main.cpp source file, there is an AppRuntime class, which inherits AndroidRuntime. runtime is an instance of the AppRuntime class, and runtime.start() is equivalent to calling the AndroidRuntime::start() method. So far, the front and back are connected. In a nutshell, when the system starts the zygote process, it will call the AndroidRuntime::start() method, then call AndroidRuntime::startReg(), and then call the register_android_util_Log() method. The last question remains, what does the RegisterMethodsOrDie() function in its method body do after register_android_util_Log() is called?

RegisterMethodsOrDie() function
RegisterMethodsOrDie() This method is a method declared in /frameworks/base/core/jni/core_jni_helpers.h.
static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
int res = AndroidRuntime::registerNativeMethods(env, className, gMethods, numMethods);
LOG_ALWAYS_FATAL_IF(res < 0, “Unable to register native methods.”);
return res;
}
In it, we can see that it actually returned to the AndroidRuntime class, called its registerNativeMethods() method, and finally called jniRegisterNativeMethods() to register the local method. And jniRegisterNativeMethods() is a method in the /libnativehelper/JNIHelp.h source file, which contains:

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* tmp;
        const char* msg;
        if (asprintf( &tmp,
                     "Native registration unable to find class '%s'; aborting...",
                     className) == -1) {<!-- -->
            // Allocation failed, print default warning.
            msg = "Native registration unable to find class; aborting...";
        } else {<!-- -->
            msg = tmp;
        }
        e->FatalError(msg);
    }
    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {<!-- -->
        char* tmp;
        const char* msg;
        if (asprintf( & amp;tmp, "RegisterNatives failed for '%s'; aborting...", className) == -1) {<!-- -->
            // Allocation failed, print default warning.
            msg = "RegisterNatives failed; aborting...";
        } else {<!-- -->
            msg = tmp;
        }
        e->FatalError(msg);
    }

    return 0;
}

It can be seen from this code that the JNI registration is performed by calling the RegisterNatives pointer function of (*env). So the final action is handed over to the JNI environment represented by the JNINativeInterface structure for execution. When calling a native method at the Java layer, there is no need to find the corresponding JNI function based on the native method package and name. Instead, you can quickly find the pointer to the corresponding JNI function through the registered mapping relationship, so as to start the function call, greatly improving the execution efficiency