Android Framework Layer Foundation 2--JNI Principle

Android Framework Layer Foundation 2--JNI Principle

The source code is based on Android8.0 analysis

1. Overview of JNI

JNI is short for java Native Interface, which is a java local call. There are two things you can do with jni:

  • Functions in java programs can call functions written in Native language. Native generally refers to functions written in c/c++.
  • Native program functions can call functions in java

In Android source code, jni is used extensively, but there are also many application scenarios, such as audio and video development, hot repair, plug-in, reverse development, source code call. Next, let's look at the use of jni

2. JNI usage based on static registration

Refer to this blog for detailed steps on using Jni

Android JNI Learning (2) - hello world of Actual JNI

Let me briefly talk about jni's call
The specific environment configuration is relevant, you can refer to the blog above.

1. Generate native methods in Java

public class JniTest {
    static {
        System.loadLibrary("jni-test");
    }

    public static void main(String[] args) {
        JniTest jniTest = new JniTest();
        System.out.printf(jniTest.get());
        
    }

    public static native String get();

    public static native void set(String str);
}

In the above class, the process of loading dynamic libraries in static code blocks is started, and two native methods, get and set, are declared.

2. Get the jni header file

  • Generating class files from javac
  • Generating header files from javah
  • javac package name/JniTest.java (package name in/split)
  • javah package name. JniTest (package name divided by.

Generated a com_heshucheng_androidjni_JniTest header file

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_heshucheng_androidjni_JniTest */

#ifndef _Included_com_heshucheng_androidjni_JniTest
#define _Included_com_heshucheng_androidjni_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_heshucheng_androidjni_JniTest
 * Method:    get
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_heshucheng_androidjni_JniTest_get
  (JNIEnv *, jclass);

/*
 * Class:     com_heshucheng_androidjni_JniTest
 * Method:    set
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_heshucheng_androidjni_JniTest_set
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

Explain

  • The function name follows the following rules:Java_ Package name_ Class name_ Method
  • jstring is a parameter that represents a String type
  • JNIEnv: Represents a pointer to the JNI environment through which to access interface methods provided by JNI
  • jobject: Represents this in a Java object
  • JNIEXPORT and JNICALL: These are macros defined in JNI and can be used in jni. Definition found in H

3. Implement JNI method

The next step is to implement the jni method, taking the C implementation as an example.
Create a subdirectory with a random name. Copy the previously generated header file to this directory. And create test.c File

#include "com_heshucheng_androidjni_JniText"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_com_heshucheng_androidjni_JniTest_get
  (JNIEnv  *env, jobject obj){
     printf("invoke get");
     return (*env)->NewStringUTF(env,"Hellow form JNI");

}

JNIEXPORT void JNICALL Java_com_heshucheng_androidjni_JniTest_set
  (JNIEnv *env, jclass obj, jstring,string){
    printf("invoke set");
    char* str = (char *)(* env)->GetStringUTFChars(env,string,NULL);
    printf("%s\n"str);
    (* env)->ReleaseStringUTFChars(env,string,str)  
    
  }

Finally, the so library is compiled and called in java.

4. Summary

This is called static registration.

In this scenario, when we call the native method get in java, we look for Java_from JNI Com_ Heshucheng_ Androidjni_ JniTest_ The get function, which fails if it is not found and then connects with it, actually saves the JNI function pointer and calls native_again You can use this function pointer directly when using the init method.

Static registration is the method name of an individual, associating java's native methods with JNI through a method pointer. If java's Native method knows its function pointer in JNI, it can avoid these shortcomings.

3. android Source Based on Dynamic Registration

Let me analyze jni using jni calls from AudioRecord source code as an example

1. Use of jni in source code

Look at AudioRecord below. stop method in Java

    public void stop()
    throws IllegalStateException {
        if (mState != STATE_INITIALIZED) {
            throw new IllegalStateException("stop() called on an uninitialized AudioRecord.");
        }

        // stop recording
        synchronized(mRecordingStateLock) {
            handleFullVolumeRec(false);
            native_stop();
            mRecordingState = RECORDSTATE_STOPPED;
        }
    }

......
  private native final void native_stop();

The internship for this native method is in android_ Media_ Android_in AudioRecord Media_ AudioRecord_ Stop

Directory: framework/base/core/jni/android_media_AudioRecord.cpp

android_media_AudioRecord_stop(JNIEnv *env, jobject thiz)
{
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == NULL ) {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return;
    }

    lpRecorder->stop();
    //ALOGV("Called lpRecorder->stop()");
}

2. Implementation of Dynamic Registration

There is a structure in JNI that records the association between the Native and JNI methods of java. This is the JNINativeMethod, which is in jni.h is defined

typedf struct{
	coust char* name;//The name of the java method,	
	coust char* signture;//Signature information for java methods
	void* fnPtr;//Corresponding method pointers in JNI
}

Android_ Media_ There is an array gMethods in AudioRecord

Directory: framework/base/core/jni/android_media_AudioRecord.cpp

static const JNINativeMethod gMethods[] = {
    // name,               signature,  funcPtr
    {"native_start",         "(II)I",    (void *)android_media_AudioRecord_start},
    {"native_stop",          "()V",    (void *)android_media_AudioRecord_stop},
    {"native_setup",         "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILjava/lang/String;J)I",
                                      (void *)android_media_AudioRecord_setup},
    {"native_finalize",      "()V",    (void *)android_media_AudioRecord_finalize},
    {"native_release",       "()V",    (void *)android_media_AudioRecord_release},
    {"native_read_in_byte_array",
                             "([BIIZ)I",
                                     (void *)android_media_AudioRecord_readInArray<jbyteArray>},
    {"native_read_in_short_array",
                             "([SIIZ)I",
                                     (void *)android_media_AudioRecord_readInArray<jshortArray>},
    {"native_read_in_float_array",
                             "([FIIZ)I",
                                     (void *)android_media_AudioRecord_readInArray<jfloatArray>},
    {"native_read_in_direct_buffer","(Ljava/lang/Object;IZ)I",
                                       (void *)android_media_AudioRecord_readInDirectBuffer},
    {"native_get_buffer_size_in_frames",
                             "()I", (void *)android_media_AudioRecord_get_buffer_size_in_frames},
    {"native_set_marker_pos","(I)I",   (void *)android_media_AudioRecord_set_marker_pos},
    {"native_get_marker_pos","()I",    (void *)android_media_AudioRecord_get_marker_pos},
    {"native_set_pos_update_period",
                             "(I)I",   (void *)android_media_AudioRecord_set_pos_update_period},
    {"native_get_pos_update_period",
                             "()I",    (void *)android_media_AudioRecord_get_pos_update_period},
    {"native_get_min_buff_size",
                             "(III)I",   (void *)android_media_AudioRecord_get_min_buff_size},
    {"native_setInputDevice", "(I)Z", (void *)android_media_AudioRecord_setInputDevice},
    {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioRecord_getRoutedDeviceId},
    {"native_enableDeviceCallback", "()V", (void *)android_media_AudioRecord_enableDeviceCallback},
    {"native_disableDeviceCallback", "()V",
                                        (void *)android_media_AudioRecord_disableDeviceCallback},
    {"native_get_timestamp", "(Landroid/media/AudioTimestamp;I)I",
                                       (void *)android_media_AudioRecord_get_timestamp},
};

The gMethods array stores the relationship between AudioRecord's Native method and JNI layer functions, but this gMethods array is not enough and needs to be registered.

The registered function is register_android_media_AudioRecord

Directory: framework/base/core/jni/android_media_AudioRecord.cpp

 int register_android_media_AudioRecord(JNIEnv *env)
{
    .....
    return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}

Continue tracking

Directory: framework/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;
}

Here the registerNativeMethods function for AndroidRuntime is returned, and tracing continues

Directory: framework/base/core/jni/AndroidRuntime

    const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

In the registerNativeMethods function, a jniRegisterNativeMethods is returned, which is then defined as JNIHelp for the JNI class help class. 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* 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(&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;
}

Here we can see that the Registerer Natives of JNIENV was finally called to complete the registration of JNI. JNIENV is described in more detail later.

4. Conversion of data types

Above we solved jni's registration problem, let's take a look at jni's data conversion problem. Calling the Native function in Java passes java-type parameters that are converted to different parameters at the JNI level.

The data types of java are divided into basic data types and reference data types. jni treats the two differently.

1. Conversion of basic types

Basic type conversion is relatively simple. As shown in the table below, the last column represents the signature format, which is described later.

Java Native type Symbol Properties Word length Signature Format
boolean jboolean Unsigned 8-bit B
byte jbyte Unsigned 8-bit C
char jchar Unsigned 16-bit D
short jshort Signed 16-bit F
int jint Signed 32-bit I
long jlong Signed 64-bit S
float jfloat Signed 32-bit J
double jdouble Signed 64-bit Z
void void V

The basic type above, except for the last line, is simply preceded by j

2. Conversion of reference types

java reference type native Signature Format
All objects jobject L+classname +;
Class jclass Ljava/lang/Class;
String jstring Ljava/lang/String;
Throwable jthrowable Ljava/lang/Throwable;
Object[] jobjectArray [L+classname +;
boolean[] jbooleanArray [Z
byte[] jbyteArray [B
char[] jcharArray [C
short[] jshortArray [S
int[] jintArray [I
long[] jlongArray [J
float[] floatArray [F
double[] jdoubleArray [D

As you can see from the diagram above, all JNI layer data types of arrays need to end with "Array" and the signature format begins with "["

Reference data types also have an inheritance relationship, as shown in the following figure

Header file type analysis generated by our previous static registration

 Java_com_heshucheng_androidjni_JniTest_set
  (JNIEnv *env, jclass obj, jstring,string)

You can see that strings in the java layer become strings in the jni layer

3. Method Signature

Previously, we looked at each type followed by a signature format. Method signatures consist of signature formats. So, what is the use of method signatures? Let's go back to the previous gMethods array

static const JNINativeMethod gMethods[] = {
...
  {"native_release",       "()V",    (void *)android_media_AudioRecord_release},
  {"native_read_in_byte_array", "([BIIZ)I",(void *)android_media_AudioRecord_readInArray<jbyteArray>},
 ...
 }

In the gMethods array, "()V" and "([BIIZ)I" are the signature methods.

Causes
We all know that java is an overloaded method, which can define methods with the same method name and different inverse parameters. Because of this, specific methods in java cannot be found in JNI only by method name. To solve this problem, JNI combines parameter types and return values as method signatures. The corresponding java method can be found with the method signature and method name.

Signature Format

(Parameter signature format...) Return value signature format

When generating method signatures, java also provides javap commands to automatically generate method signatures, using the following

Javap-s - P path/class. Class

Where s denotes the output internal type signature, p denotes the printing of all methods and members (the default prints public), and the results are eventually printed in cmd.

5. Introduction to JNIEnv

1. Overview of JNIEnv

JNIEnv is a pointer to all JNI methods. This pointer is valid only for the thread that created it and cannot be passed across processes, so different threads of JNIEnv are independent of each other. JNIEnv has two main functions:

  • Calling java methods
  • Manipulate Java (get variables and objects in java)

JNIEnv Internal Structure
[External chain picture transfer failed (img-BytDM08d-1566559589146)]( http://wiki.jikexueyuan.com/project/deep-android-v1/images/chapter2/image003.png )]

2. Definition of JNIEnv

Let's look at the definition of JNIEnv:

Directory: libnativehelper/include_Jni/nativehelper/jni.h

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv; //JNIEnv Definition in c++
typedef _JavaVM JavaVM; 
#else
typedef const struct JNINativeInterface* JNIEnv;//JNI types in c
typedef const struct JNIInvokeInterface* JavaVM;
#endif

In the code above, use u cplusplus distinguishes C from c++ and you can see that the c++ type is _ JNIEnv, C is JNINativeInterface. Continue with View Definition

Directory: libnativehelper/include_Jni/nativehelper/jni.h

struct _JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */
    const struct JNINativeInterface* functions;

#if defined(__cplusplus)
	...
	//Find a class with the specified java name
    jclass FindClass(const char* name) 
    { return functions->FindClass(this, name); }

    //Get methods in java
    jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
    { return functions->GetMethodID(this, clazz, name, sig); }

	//Get member variables in java
    jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
    { return functions->GetFieldID(this, clazz, name, sig); }
...
}

_ JNIEnv is a structure that contains the JNINativeInterface inside. In _ There are many functions defined in JNIEnv, three of which are common. It can also be found that they end up calling functions defined in JNINativeInterface.

Take a look at the definition in JNINativeInterface

struct JNINativeInterface {
....

    jclass      (*FindClass)(JNIEnv*, const char*);

    jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);

	jfieldID    (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
ยทยทยท
}

There are many function pointers defined in the JNINativeInterface structure corresponding to the JNIEnv structure, only three function pointer definitions are given above. By defining these function pointers, you can navigate to the JNI function table in the virtual machine, thus enabling the JNI layer to call methods in the Java world.

How to call java methods in the same process but in different threads

In the source code above, we can find a JavaVM, which is the representative of the virtual machine in JNI layer. There is only one JavaVM in a virtual machine process. Therefore, all threads of the process can use this JavaVM. You can get the JNIEnv of this thread by calling the AtchCurrent function of Java, so you can call the Java method in different threads. Also remember to call the DetachCurrentThread function to free up resources before a thread using the AttachCurrentThread function exits

3.jfieldID and jmethodID

As we know earlier, you can manipulate java objects and methods with JNIEnv. So how did he achieve it?

We know that a Java object is actually operated on by its member objects and member functions. So in JNI rules, member variables and member functions of Java classes are represented by jfieldID and jmethodID, which can be obtained from the following two functions of JNIEnv:

jfieldID GetFieldID(jclass clazz,const char*name, const char *sig);

jmethodID GetMethodID(jclass clazz, const char*name,const char *sig);

Where jclass represents a Java class, name represents the name of a member function or variable, sig is the signature information for the function and variable. As shown earlier, member functions and member variables are information about classes, and the first parameter to both functions is jclass.

The process of obtaining jfieldID and jmethodID

    jclass clazz;
	//Class es object for Java-tier MediaRecorder
    clazz = env->FindClass("android/media/MediaRecorder");
    if (clazz == NULL) {
        return;
    }
    //Get jfieldID object
    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");//2
    if (fields.context == NULL) {
        return;
    }
   
    //Get jmethodID object
    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");//4
    if (fields.post_event == NULL) {
        return;
    }

When successful, you can call java directly

//Passed in the jmethodID object obtained above
env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, NULL);//1

Six. JNI and garbage collection

In java, there are four reference types, strong and weak, which have varying degrees of impact on virtual machine garbage collection. Similarly, there are different types of references in JNI: local reference, global reference, weak global reference, which are described below

1. Local References

JNIEnv provides functions that provide reference types that are essentially local, so local references are also the most common type of reference in JNI. Local references are characterized by the following:

  • When the Native function returns, the local reference is automatically released
  • Valid only for the thread that created it, not across threads
  • Local references are JVM-managed reference types

2. Global References

Global and local references are almost the opposite, and they have the following main features:

  • The Native function is not automatically released when it returns, so global references need to be released manually and not by GC.
  • Global references are available across threads
  • Global references are not controlled by the JVM

Global references are used to create global references through JNIEnv's NewGlobalRef function, which calls JNIEnv's DeleteGlobalRef function to release global references

3. Weak Global References

Weak global reference is a special kind of global reference. It is similar to global reference in that weak global reference can be recycled by gc. When a weak global reference is recycled by GC, it will point to NULL after it is recycled by GC. So before accessing a weak global reference, you must first determine whether it is recycled or not by isSameObject function of JNIEnv.

Weak global references are created through the NewWeakGlobalRef function and released by DeleteWeakGlobalRef

7. Exception handling in JNI

There are exceptions in NI, but they are different from C++, Java exceptions. An exception is generated when an error occurs in calling some functions of JNIEnv, but this exception does not interrupt the execution of local functions until the virtual machine returns from the JNI layer to the Java layer. Although exceptions generated in the JNI layer do not interrupt local functions, once exceptions are generated, some resource cleanup can only be done (such as releasing global references or ReleaseStringChars). If you call JNIEnv functions other than those described above, the program will die.

 virtualbool scanFile(const char* path, long long lastModified,

long long fileSize)

 {

       jstring pathStr;

       //When the NewStringUTF call fails, it returns directly and does nothing else.

        if((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;

       ......

}

JNI Env provides three functions to help intercept and modify these exceptions in code:

  • The ExceptionOccured function to determine if an exception has occurred.
  • ExceptionClear function to clean up exceptions that occur in the current JNI layer.
  • ThrowNew function to throw an exception to the Java layer.

Exception handling is a must for JNI-tier code, and readers should be careful when writing code.

8. Reference material

Exploration of Android Art Development
Understanding Android Volume 1
Android Advanced Decryption

Tags: JNI

Posted by starsol on Thu, 19 May 2022 20:46:33 +0300