One, foreword


JNI is simply Java’s way of calling native C/C++, and the JVM uses a lot of JNI technology to make Java run on different platforms. It allows certain methods of Java classes to be implemented in native methods that can also use Java objects in the same way that Java code calls Java objects. Native methods can create new Java objects or objects created using Java applications.

2. Simple examples


Now let’s take a look at the official example of creating a Native C++ project in Android Studio:

The project structure is as follows:

You can see that there is an extra CPP directory than normal Android projects, and there is a native method in MainActivity, stringFromJNI(), which is an empty implementation in the Java layer. Its implementation is in native-lib.cpp:

This method returns a string, now we run the project:

JNIEnv and Jobject


Let’s move on to the stringFromJNI method:

extern "C" JNIEXPORT jstring JNICALL
Java_com_tencent_learnndk_MainActivity_stringFromJNI(JNIEnv *env, jobject instance) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
Copy the code

JNIEXPORT and JNICALL are JNI keywords, indicating that this function is to be called by JNI. Next, look at the method parameters.

3.1 JNIEnv * env

JNIEnv represents the context in which Java calls native languages and is a pointer that encapsulates almost all JNI methods. What methods are available in JNI? Please refer to JNI Methods and usage examples.

JNIEnv is only valid on the thread that created it and cannot be passed across threads. Jnienvs from different threads are independent of each other. Threads created in a native environment that need to access JNI must call AttachCurrentThread association and DetachCurrentThread to unlink.

In addition, JNIEnv is called differently in C and C++ :

/ / C style:
(*env)->NewStringUTF(env, "Hellow World!" );/ / c + + style:
env->NewStringUTF(" Hellow World!" );Copy the code

3.2 jobject instance

If a native method is not static, the instance represents a class instance of the native method. If a native method is static, it represents the class instance of the native method’s class. The following is an example:

Java code:

public native String test(a);
 
public static native String testStatic(a);
Copy the code

Jni code:

extern "C"
JNIEXPORT jstring JNICALL
Java_com_tencent_learnndk_MainActivity_test(JNIEnv *env, jobject instance) {
}
 
extern "C"
JNIEXPORT jstring JNICALL
Java_com_tencent_learnndk_MainActivity_testStatic(JNIEnv *env, jclass type) {}Copy the code

Mapping between Java and C/C++ types


Now we add a new native method to MainActivity with the parameter name:

And automatically generate JNI methods according to code prompts:

You can see that Java String parameters are changed to JString in native methods. That’s because JNI is an interface language, so there is an intermediate transition, and datatype conversion is an important type docking method in this process. Let’s look at each type of mapping.

Basic data types:

Java type JNI type C/C + + type
boolean jboolean unsigned char
byte jbyte char
char jchar unsingned short
short jshort short
int jint int
long jlong long
float jfloat float
double jdouble double

Reference type:

Java type Primitive types Java type Primitive types
Java.lang.Class jclass char[] jcharArray
Java.lang.Throwable jthrowable short[] jshortArray
Java.lang.String jstring int[] jintArray
Other object jobject long[] jlongArray
Java.lang.Object[] jobjectArray float[] jfloatArray
boolean[] jbooleanArray double[] jdoubleArray
byte[] jbyteArray Other arrays jarray

JNI calls Java code


All of the above describes Java calling C/C++ side code, but there is another important aspect of JNI that is accessing Java side code in C/C++ native code. Let’s look at an example.

package com.tencent.learnndk;
 
public class Person {
 
    private String name;
 
    private int age;
 
    public int getAge(a) {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getName(a) {
        return name;
    }
 
    @Override
    public String toString(a) {
        return "Person{name='" + name + "', age=" + age + '} '; }}Copy the code

Modify the MainActivity code as follows:

public class MainActivity extends AppCompatActivity {
 
    static {
        System.loadLibrary("native-lib");
    }
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(getPerson().toString());
    }
 
    public native Person getPerson(a);
}
Copy the code

Modify the native-lib. CPP code as follows:

#include <jni.h>
#include <string>
#include<android/log.h>
 
#define  LOG_TAG "learnNdk"
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
 
extern "C" JNIEXPORT jobject JNICALL
Java_com_tencent_learnndk_MainActivity_getPerson(JNIEnv *env, jobject instance) {
 
    //1. Get the full path of the Java class
    const char *person_java = "com/tencent/learnndk/Person";
    const char *method = "<init>"; // The identity of the Java constructor
 
    //2. Find the Java object class to work with
    jclass j_person_class = env->FindClass(person_java);
 
    //3. Get the empty parameter constructor
    jmethodID person_constructor = env->GetMethodID(j_person_class, method, "()V");
 
    //4. Create an object
    jobject person_obj = env->NewObject(j_person_class, person_constructor);
 
    //5. Get the signature of the setName method and the corresponding setName method
    const char *nameSig = "(Ljava/lang/String;) V";
    jmethodID nameMethodId = env->GetMethodID(j_person_class, "setName", nameSig);
 
    //6. Get the setAge method signature and get the setAge method signature
    const char *ageSig = "(I)V";
    jmethodID ageMethodId = env->GetMethodID(j_person_class, "setAge", ageSig);
 
    //7. Calling a Java object function
    const char *name = "lerendan";
    jstring newStringName = env->NewStringUTF(name);
    env->CallVoidMethod(person_obj, nameMethodId, newStringName);
    env->CallVoidMethod(person_obj, ageMethodId, 28);
 
    const char *sig = "()Ljava/lang/String;";
    jmethodID jtoString = env->GetMethodID(j_person_class, "toString", sig);
    jobject obj_string = env->CallObjectMethod(person_obj, jtoString);
    jstring perStr = static_cast<jstring >(obj_string);
    const char *itemStr2 = env->GetStringUTFChars(perStr, NULL);
    LOGD("Person: %s", itemStr2);
    return person_obj;
}
Copy the code

Run as follows:

Native-lib.cpp GetMethodId()

Sig: The type descriptor of this method, for example, "()V", where parentheses are method parameters and parentheses are return value typesjmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
Copy the code

A new problem in the above code is the type descriptor SIG. We can also view the type descriptors by using the Javap command, as described in my previous article Breaking Android Compilation peg-8 -class bytecode.

In addition to viewing it from the command line, you can also write it yourself according to certain rules. The format of the type descriptor can refer to the following table:

Java type Signature (descriptor) Java type Signature (descriptor)
boolean Z byte B
char C short S
int I long J
float F double D
void V Other reference types L+ full class name +;
type[] [ method type (Parameter) Return value

Dynamic registration


Until now we’ve been using Java_packagename_classname_methodname in JNI to match Java methods, which we call static registration. Dynamic registration means that the method name can not be so long, in the Android AOSP source code is a lot of use of dynamic registration. Now let’s look at an example of dynamic registration:

Add to MainActivity:

public native void dynamicNativeTest1(a);
 
public native String dynamicNativeTest2(a);
Copy the code

Native – lib. CPP:

void dynamicNative1(JNIEnv *env, jobject jobj) {
    LOGD("DynamicNative1 dynamic registration");
}
 
jstring dynamicNative2(JNIEnv *env, jobject jobj, jint i) {
    return env->NewStringUTF("I'm dynamically registered dynamicNative2 method");
}
 
// An array of methods that need to be dynamically registered
static const JNINativeMethod jniNativeMethod[] = {
        {"dynamicNative"."()V",                   (void *) dynamicNative1},
        {"dynamicNative"."(I)Ljava/lang/String;", (jstring *) dynamicNative2}
};
 
// The class name that needs to dynamically register native methods
static const char *mClassName = "com/tencent/learnndk/MainActivity";
 
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *javaVm, void *pVoid) {
    // Create a new EVN from the virtual machine
    JNIEnv *jniEnv = nullptr;
    jint result = javaVm->GetEnv(reinterpret_cast<void **>(&jniEnv), JNI_VERSION_1_6);
    if(result ! = JNI_OK) {return JNI_ERR; // Report an error
    }
    jclass mainActivityClass = jniEnv->FindClass(mClassName);
    jniEnv->RegisterNatives(mainActivityClass, jniNativeMethod,
                            sizeof(jniNativeMethod) / sizeof(JNINativeMethod));// Number of dynamic registrations
 
    return JNI_VERSION_1_6;
}
Copy the code