preface

Audio and video series of articles have been published 2, C/C++ foundation we have learned, so the beginning of this article really into NDK learning, before entering NDK learning we have to learn JNI foundation. In order to ensure the output of this series of articles, try to a week.

introduce

JNI is one of the most powerful features of the Java programming language. It allows some methods of Java classes to be implemented natively, while allowing them to be called and used just like normal Java methods. These native methods 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 that can examine, modify, and call the methods of these objects to perform tasks.

Environment configuration

Install AS + NDK + CMake + LLDB

  • AS: Android development tool.
  • NDK: This toolset allows you to use C and C++ code for Android.
  • CMake: An external build tool that can be used with Gradle to build native libraries. This component is not required if you only plan to use the NdK-build.
  • LLDB: Debug mode.

The local properties configuration:

ndk.dir=/Users/devyk/Data/Android/SDK/ndk-bundle
sdk.dir=/Users/devyk/Data/Android/SDK
Copy the code

Build. Gradle configuration:

android {
...
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"}}}Copy the code

A simple example

  1. Create native c++ projects

    Click Next as prompted

  2. Basic code generation

    public class MainActivity extends AppCompatActivity {
    
        /** * 1. Load native library */
        static {
            System.loadLibrary("native-lib");
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            TextView tv = findViewById(R.id.sample_text);
            /**3. Call native c++ functions */
            tv.setText(stringFromJNI());
        }
    
        /** * 2. Define native functions */
        public native String stringFromJNI(a);
    }
    Copy the code

    Native – lib. CPP code:

    #include <jni.h>
    #include <string>
    
    extern "C" JNIEXPORT jstring JNICALL
    Java_com_devyk_ndk_1sample_MainActivity_stringFromJNI(
            JNIEnv* env,
            jobject /* this */) {
        std: :string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
    Copy the code

    The “Hello from C++” string will appear on the screen after running, and a simple native project will be created.

Introduction to JNI learning

Data types and type descriptors

There are two types of data in Java:

  • Basic data types: Boolean, CHAR, byte, int, short, long, float, double.
  • Reference data types: String, Object[], Class, Object, and other classes.

1.1 Basic data types

Primitive data types can be mapped directly to the corresponding primitive data types in C/C++, as shown in the following table. JNI makes this mapping transparent to developers with type definitions.

Java type JNI type C/C + + type
boolean jboolean Unsigned char unsigned 8-bit integer
byte jbyte Char (signed 8-bit integer)
char jchar Unsingned short (unsigned 16-bit integer)
short jshort Short (signed 16-bit integer)
int jint Int (signed 32-bit integer)
long jlong Long (signed 64-bit integer)
float jfloat Float (signed 32-bit float)
double jdouble Double (signed 64-bit double)

1.2 Reference Types:

Unlike primitive data types, reference types are opaque to native methods, and reference type mappings are shown in the following table. Their internal data structures are not directly exposed to native code.

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

1.3 Data type descriptors

When storing the name of a data type in a JVM, it is stored using the specified descriptor instead of the usual int, float, etc.

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

Example:

  • Represents a String

Java type: java.lang.string

JNI descriptor: Ljava/lang/String; (L + class full name +;)

  • Represents an array

Java type: String[] JNI descriptor: [Ljava/lang/String; Java type: int [] [] JNI descriptor: [[I

  • Represents a method

Long func(int n, String s, int[] arr); JNI descriptor: (ILjava/lang/String; [I)J

Java method: void func(); JNI descriptor: ()V

You can also use the javap -s full path command to obtain the method signature

2. Introduction to JNIEnv and JavaVm

2.1 JNIEnv:

JNIEnv represents the context in which Java calls native languages and is a pointer that encapsulates almost all JNI methods.

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.

JavaVm 2.2:

JavaVM is the representative of virtual machine in THE JNI layer. Each process has only one JavaVM, and all threads share one JavaVM.

2.3 Code Style (C/C++)

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

3. JNI API

Refer to the official API documentation or the JNI method Guide and usage examples

4. Operations on data types

JNI processes the data passed by Java

  1. Defining native functions

    public class MainActivity extends AppCompatActivity {
    
        /** * 1. Load native library */
        static {
            System.loadLibrary("native-lib");
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            /** 1. Java data is passed to native */
            test1(true,
                    (byte) 1.', ',
                    (short) 3.4.3.3 f.2.2d,
                    "DevYK".28.new int[] {1.2.3.4.5.6.7},
                    new String[]{"1"."2"."4"},
                    new Person("YangKun"),
                    new boolean[] {false.true}); }/** * Java passes data to native */
        public native void test1(
                boolean b,
                byte b1,
                char c,
                short s,
                long l,
                float f,
                double d,
                String name,
                int age,
                int[] i,
                String[] strs,
                Person person,
                boolean[] bArray
        );
    }
    Copy the code
  2. Jni processes the data passed by Java

    #include <jni.h>
    #include <string>
    #include <android/log.h>
    
    #include <iostream>
    
    #define TAG "native-lib"
    // __VA_ARGS__ represents... Variable parameter of
    #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG,  __VA_ARGS__);
    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG,  __VA_ARGS__);
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG,  __VA_ARGS__);
    
    
    
    extern "C"// Support C code
    JNIEXPORT void JNICALL
    Java_com_devyk_ndk_1sample_MainActivity_test1(JNIEnv *env, jobject instance, jboolean jboolean1, jbyte jbyte1, jchar jchar1, jshort jshort1, jlong jlong1, jfloat jfloat1, jdouble jdouble1, jstring name_, jint age, jintArray i_, jobjectArray strs, jobject person, jbooleanArray bArray_ ) {
    
    
        //1. Receive Boolean values from Java
        unsigned char b_boolean = jboolean1;
        LOGD("boolean-> %d", b_boolean);
    
        //2. Receive Boolean values from Java
        char c_byte = jbyte1;
        LOGD("jbyte-> %d", c_byte);
    
    
        //3. Accept the char value passed by Java
        unsigned short c_char = jchar1;
        LOGD("char-> %d", c_char);
    
    
        //4. Receive the short value from Java
        short s_short = jshort1;
        LOGD("short-> %d", s_short);
    
        //5. Receive the long value from Java
        long l_long = jlong1;
        LOGD("long-> %d", l_long);
    
        //6. Receive float values from Java
        float f_float = jfloat1;
        LOGD("float-> %f", f_float);
    
        //7. Receive a double from Java
        double d_double = jdouble1;
        LOGD("double-> %f", d_double);
    
        //8. Receive String values from Java
        const char *name_string = env->GetStringUTFChars(name_, 0);
        LOGD("string-> %s", name_string);
    
        //9. Receive an int from Java
        int age_java = age;
        LOGD("int:%d", age_java);
    
        //10. Print int passed by Java []
        jint *intArray = env->GetIntArrayElements(i_, NULL);
        // Get the array length
        jsize intArraySize = env->GetArrayLength(i_);
        for (int i = 0; i < intArraySize; ++i) {
            LOGD("IntArray - > % d.", intArray[i]);
        }
        // Free the array
        env->ReleaseIntArrayElements(i_, intArray, 0);
    
        //11. Print the String passed by Java []
        jsize stringArrayLength = env->GetArrayLength(strs);
        for (int i = 0; i < stringArrayLength; ++i) {
            jobject jobject1 = env->GetObjectArrayElement(strs, i);
            // strong JNI String
            jstring stringArrayData = static_cast<jstring >(jobject1);
    
            / / C String
            const char *itemStr = env->GetStringUTFChars(stringArrayData, NULL);
            LOGD("String[%d]: %s", i, itemStr);
            / / recycling String []
            env->ReleaseStringUTFChars(stringArrayData, itemStr);
        }
    
    
    
        //12. Print the Object passed by Java
        //12.1 Obtain the bytecode
        const char *person_class_str = "com/devyk/ndk_sample/Person";
        //12.2 转 jni jclass
        jclass person_class = env->FindClass(person_class_str);
        //12.3 Get method signature javap-a
        const char *sig = "()Ljava/lang/String;";
        jmethodID jmethodID1 = env->GetMethodID(person_class, "getName", sig);
    
        jobject obj_string = env->CallObjectMethod(person, jmethodID1);
        jstring perStr = static_cast<jstring >(obj_string);
        const char *itemStr2 = env->GetStringUTFChars(perStr, NULL);
        LOGD("Person: %s", itemStr2);
        env->DeleteLocalRef(person_class); / / recycling
        env->DeleteLocalRef(person); / / recycling
    
    
        //13. Print the booleanArray passed by Java
        jsize booArrayLength = env->GetArrayLength(bArray_);
        jboolean *bArray = env->GetBooleanArrayElements(bArray_, NULL);
        for (int i = 0; i < booArrayLength; ++i) {
            bool b =  bArray[i];
            jboolean b2 =  bArray[i];
            LOGD("boolean:%d",b)
            LOGD("jboolean:%d",b2)
        }
        / / recycling
        env->ReleaseBooleanArrayElements(bArray_, bArray, 0);
    
    }
    Copy the code

Output:

Native lib: Boolean -> 1 > native lib: jbyte-> 1 > native lib: char-> 44 > native lib: short-> 3 > native-lib: long-> 4 > native-lib:float-> 3.300000 > native lib: double-> 2.200000 > native lib: string-> DevYK > native lib: int:28 > native lib: double-> 2.200000 > native lib: string-> DevYK > native lib: int:28 > native lib: IntArray ->1: > native-lib: intArray->2: > native-lib: intArray->3: > native-lib: intArray->4: > native-lib: intArray IntArray ->5: > native lib: intArray->6: > native lib: intArray->7: > native lib: String[0]: 1 > native lib: String[1]: 2 > native lib: String[2]: 4 > native lib: Person: Yang > native lib: Boolean :0 > native lib: jboolean:0 > native lib: String[2]: 4 > native lib: Person: Yang > native lib: Boolean :0 > native lib: jboolean:0 > native lib: String[2]: 4 > native lib: Person: Yang > native lib: jboolean:0 > native lib: String boolean:1 > native-lib: jboolean:1Copy the code

JNI handles Java objects

  1. Define a Java object

    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
  2. Defining a native interface

    public class MainActivity extends AppCompatActivity {
    
        private String TAG = this.getClass().getSimpleName();
    
        /** * 1. Load native library */
        static {
            System.loadLibrary("native-lib");
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            TextView text = findViewById(R.id.sample_text);
            /** Handles Java objects */
            String str = getPerson().toString();
            text.setText(str);
        }
        public native Person getPerson(a);
    }
    Copy the code

    According to the above code, we know that if successful, the phone screen will print and display data.

  3. The processing of the JNI

    extern "C"
    JNIEXPORT jobject JNICALL
    Java_com_devyk_ndk_1sample_MainActivity_getPerson(JNIEnv *env, jobject instance) {
    
        //1. Get the full path of the Java class
        const char *person_java = "com/devyk/ndk_sample/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 = "DevYK";
        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

    Output:

    You can see that Native returns data to Java.

5. JNI dynamic registration

In front of our study are static registration, static registration is a simple convenient, but also faces a major problem, if the current class definition of native method name change or package name change, so this change will face in the CPP implementation will also change, if will face this situation, you can try the JNI dynamic registration, The following code looks like this:

public class MainActivity extends AppCompatActivity {

    private String TAG = this.getClass().getSimpleName();

    /** * 1. Load native library */
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView text = findViewById(R.id.sample_text);

        /** Dynamically registered native */
        dynamicRegister("I'm dynamically registered.");
    }




    /** * dynamic register */
    public native void dynamicRegister(String name);
}
Copy the code

CPP:

#include <jni.h>
#include <string>
#include <android/log.h>

#include <iostream>

#define TAG "native-lib"
// __VA_ARGS__ represents... Variable parameter of
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG,  __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG,  __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG,  __VA_ARGS__);


/** * TODO dynamic register */

/** * corresponds to the full path name of the Java class. Use/instead of */
const char *classPathName = "com/devyk/ndk_sample/MainActivity";

extern "C"  // Supports THE C language
JNIEXPORT void JNICALL // Tell the virtual machine that this is a JNI function
native_dynamicRegister(JNIEnv *env, jobject instance, jstring name) {
    const char *j_name = env->GetStringUTFChars(name, NULL);
    LOGD("Dynamic registration: %s", j_name)
    / / release
    env->ReleaseStringUTFChars(name, j_name);
}


/* typepedef struct {const char* name; const char* signature; void* fnPtr; } JNINativeMethod; * /
static const JNINativeMethod jniNativeMethod[] = {
        {"dynamicRegister"."(Ljava/lang/String;) V", (void *) (native_dynamicRegister)}
};


/** * This function is defined in the jni.h header. The JNI_OnLoad() function */ is called when system.loadLibrary ()
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(classPathName);
    jniEnv->RegisterNatives(mainActivityClass, jniNativeMethod,
                            sizeof(jniNativeMethod) / sizeof(JNINativeMethod));// Number of dynamic registrations

    return JNI_VERSION_1_6;
}
Copy the code

Output:

Dynamic registration: I signed up dynamically

6. Exception handling

Exception handling is an important feature of the Java programming language. Exception behavior in JNI is different from that in Java, where when an exception is thrown, the virtual machine stops executing the code block and enters the call stack to reverse check the exception handler block that can handle a particular type of exception. This is also called catching exceptions. The virtual machine clears the exception and gives control to the exception handler. JNI, by contrast, requires developers to explicitly implement the exception-handling flow after an exception occurs.

Catch exception:

The JNIEvn interface provides a set of exception-related functions that can be viewed at run time using Java classes, such as the following code:

    public native void dynamicRegister2(String name);


    /** * Test to throw exception **@throws NullPointerException
     */
    private void testException(a) throws NullPointerException {
        throw new NullPointerException("MainActivity testException NullPointerException");
    }
Copy the code

DynamicRegister2 The native method needs to explicitly handle exception information when the testException method is called. JNI provides ExceptionOccurred function to query for pending exceptions in a virtual machine. After using ExceptionClear, the exception handler needs to explicitly clear the exception with the ExceptionClear function as follows:

 jthrowable exc = env->ExceptionOccurred(); // Check for exceptions
    if (exc) {// If an exception occurs
        env->ExceptionDescribe(); // Displays abnormal information
        env->ExceptionClear(); // Clear the exception that occurred
    }
Copy the code

Throw an exception:

JNI also allows native code to throw exceptions. Because exceptions are Java classes, you should first use the FindClass function to find the exception class. The ThrowNew function can be used to instantiate and throw a new exception, as shown in the following code:

 jthrowable exc = env->ExceptionOccurred(); // Check for exceptions
    if (exc) {// If an exception occurs
        jclass newExcCls = env->FindClass("java/lang/IllegalArgumentException");
        env->ThrowNew(newExcCls, "An abnormal message has occurred in JNI."); // Return a new exception to Java
    }
Copy the code

Because the code execution of the native function is not controlled by the virtual machine, throwing an exception does not stop the execution of the native function and give control to the exception handler. By the time an exception is thrown, the native function should free up all allocated native resources, such as memory and appropriate return values. References obtained through the JNIEvn interface are local references and are automatically released by the virtual machine once the native function is returned.

Sample code:

public class MainActivity extends AppCompatActivity {

    private String TAG = this.getClass().getSimpleName();

    /** * 1. Load native library */
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        dynamicRegister2("Test exception Handling");
    }


    public native void dynamicRegister2(String name);


    /** * Test to throw exception **@throws NullPointerException
     */
    private void testException(a) throws NullPointerException {
        throw new NullPointerException("MainActivity testException NullPointerException"); }}Copy the code

Native – lib. CPP file

#include <jni.h>
#include <string>
#include <android/log.h>

#include <iostream>

#define TAG "native-lib"
// __VA_ARGS__ represents... Variable parameter of
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG,  __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG,  __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG,  __VA_ARGS__);


/** * TODO dynamic register */. .extern "C"  // Supports THE C language
JNIEXPORT void JNICALL // Tell the virtual machine that this is a JNI function
native_dynamicRegister2(JNIEnv *env, jobject instance, jstring name) {
    const char *j_name = env->GetStringUTFChars(name, NULL);
    LOGD("Dynamic registration: %s", j_name)

    jclass clazz = env->GetObjectClass(instance);// Get the current class
    jmethodID mid =env->GetMethodID(clazz, "testException"."()V");// Executes Java test code that throws an exception
    env->CallVoidMethod(instance, mid); // The execution throws an exception
    jthrowable exc = env->ExceptionOccurred(); // Check for exceptions
    if (exc) {// If an exception occurs
        env->ExceptionDescribe(); // Displays abnormal information
        env->ExceptionClear(); // Clear the exception that occurred
        jclass newExcCls = env->FindClass("java/lang/IllegalArgumentException");
        env->ThrowNew(newExcCls, "An abnormal message has occurred in JNI."); // Return a new exception to Java
    }

    / / releaseenv->ReleaseStringUTFChars(name, j_name); }...Copy the code

Dynamic registration is used again.

The final effect is as follows:

You can see that both the exception thrown by Java is caught and a new JNI exception message is thrown.

7. Local and global references

References play a very important role in Java programming. The virtual machine manages the lifetime of class instances by tracking their references and collecting garbage that is not referenced. Because native code is not a managed environment, JNI provides a set of functions that allow native code to explicitly manage object references and native code during usage. JNI supports three types of references: local, global, and weak global. These types of references are described below.

Local reference:

Most JNI functions return local references. Local applications cannot be cached and reused in subsequent calls, mainly because their lifetime is limited to native methods and local references are released once the native methods return. For example, the FindClass function returns a local reference that is automatically released when the native method returns, or the DeleteLocalRef function can be used to explicitly release the native code. The following code looks like this:

jclass personClass;
extern "C"  // Supports THE C language
JNIEXPORT void JNICALL // Tell the virtual machine that this is a JNI function
native_test4(JNIEnv *env, jobject instance) {
    LOGD("Test local reference")
    if (personClass == NULL) {
        const char *person_class = "com/devyk/ndk_sample/Person";
        personClass = env->FindClass(person_class);
        LOGD("PersonClass == null executed.")}//Java Person constructor instantiated
    const char *sig = "()V";
    const char *method = "<init>";//Java constructor identifier
    jmethodID init = env->GetMethodID(personClass, method, sig);
    // Create it
    env->NewObject(personClass, init);
}

Copy the code

Effect:

I think it’s the same as described. Local references cannot be reused in subsequent calls, so how to solve this problem is to promote local references to global references or call DeleteLocalRef explicitly to release. We demonstrate this in global references.

Global reference:

Global references remain valid during subsequent calls to native methods unless they are explicitly released by native code.

  1. Creating a global reference

    A local reference can be initialized to a global reference using the NewGlobalRef function, as shown in the following code:

    jclass personClass;
    extern "C"  // Supports THE C language
    JNIEXPORT void JNICALL // Tell the virtual machine that this is a JNI function
    native_test4(JNIEnv *env, jobject instance) {
        LOGD("Test local reference")
    
    
        if (personClass == NULL) {
            //1. Upgrade the global solution to the problem of non-reuse
     				const char *person_class = "com/devyk/ndk_sample/Person";
            jclass jclass1 = env->FindClass(person_class);
            personClass = static_cast<jclass>(env->NewGlobalRef(jclass1));
            LOGD("PersonClass == null executed.")}//Java Person constructor instantiated
        const char *sig = "()V";
        const char *method = "<init>";//Java constructor identifier
        jmethodID init = env->GetMethodID(personClass, method, sig);
        // Create it
        env->NewObject(personClass, init);
    
        //2. Explicitly release actively delete global references
        env->DeleteLocalRef(personClass);
        personClass = NULL;
    }
    Copy the code

  2. Deleting a Global reference

    When native code no longer needs a global reference, it can always be released using DeleteGlobalRef, as shown in the following code:

        env->DeleteLocalRef(personClass);
        personClass = NULL;
    Copy the code

Weak global reference

Another type of global reference is a weak global reference. Like a global reference, a weak global reference survives subsequent calls to a native method. Unlike global references, weak global references do not prevent potential objects from being garbage collected.

  1. Create a weak global reference

    A weak global reference can be initialized using the NewWeakGlobalRef function, as follows:

    jclass personClass;
    extern "C"  // Supports THE C language
    JNIEXPORT void JNICALL // Tell the virtual machine that this is a JNI function
    native_test4(JNIEnv *env, jobject instance) {
        LOGD("Test local reference")
    
    
        if (personClass == NULL) {
            //1. Upgrade the global solution to the problem of non-reuse
            const char *person_class = "com/devyk/ndk_sample/Person";
            jclass jclass1 = env->FindClass(person_class);
    // personClass = static_cast
            
             (env->NewGlobalRef(jclass1));
            
            personClass = static_cast<jclass>(env->NewWeakGlobalRef(jclass1));
            LOGD("PersonClass == null executed.")}//Java Person constructor instantiated
        const char *sig = "()V";
        const char *method = "<init>";//Java constructor identifier
        jmethodID init = env->GetMethodID(personClass, method, sig);
        // Create it
        env->NewObject(personClass, init);
    
        //2. Explicitly release actively delete local references
    // env->DeleteLocalRef(personClass);
        env->DeleteWeakGlobalRef(personClass);
        personClass = NULL;
    
    }
    Copy the code
  2. Validation of weak global references

    The IsSameObject function can be used to verify that a weak global reference still points to an active class instance.

  3. Delete a weak global reference

     env->DeleteWeakGlobalRef(personClass);
    Copy the code

    Global references remain in effect until they are released and can be used by other native functions and native threads.

JNI thread operations

As part of a multithreaded environment, virtual machines support running native code. Keep some of the constraints of JNI technology in mind when developing artifacts:

  • Local references are valid only during the execution of the native method and in the context of the thread executing the native method. Local references cannot be shared between multiple threads. Only global references can be shared by multiple threads.
  • The JNIEvn interface pointer passed to each native method is also valid in the thread associated with the method call and cannot be cached or used by other threads.

Synchronous:

Synchronization is the ultimate feature of multithreaded programming. Similar to Java synchronization, JNI’s monitor allows native code to take advantage of Java object synchronization, and the virtual machine ensures that threads accessing the monitor can execute safely while other threads wait for the monitor objects to become available.

jint MonitorEnter(jobject obj)
Copy the code

Calls to the MonitorEnter function should match calls to MonitorExit to avoid deadlocks in the code.

Example:

    public void test4(View view) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run(a) { count(); nativeCount(); } }).start(); }}private void count(a) {
        synchronized (this) {
            count++;
            Log.d("Java"."count="+ count); }}public native void nativeCount(a);
Copy the code

Native code:

extern "C"  // Supports THE C language
JNIEXPORT void JNICALL // Tell the virtual machine that this is a JNI function
native_count(JNIEnv *env, jobject instance) {

    jclass cls = env->GetObjectClass(instance);
    jfieldID fieldID = env->GetFieldID(cls, "count"."I");

    /*if (env->MonitorEnter(instance) ! = JNI_OK) { LOGE("%s: MonitorEnter() failed", __FUNCTION__); } * /

    /* synchronized block */
    int val = env->GetIntField(instance, fieldID);
    val++;
    LOGI("count=%d", val);
    env->SetIntField(instance, fieldID, val);

    /*if (env->ExceptionOccurred()) { LOGE("ExceptionOccurred()..." ); if (env->MonitorExit(instance) ! = JNI_OK) { LOGE("%s: MonitorExit() failed", __FUNCTION__); }; } if (env->MonitorExit(instance) ! = JNI_OK) { LOGE("%s: MonitorExit() failed", __FUNCTION__); }; * /

}
Copy the code

There is no synchronization in Native, print as follows:

> ** Output :** > > com.devyk.ndk_sample D/Java: count=1
> com.devyk.ndk_sample I/native-lib:  count=2
> com.devyk.ndk_sample D/Java: 		count=3
> com.devyk.ndk_sample I/native-lib:  count=4
> com.devyk.ndk_sample D/Java: 		count=5
> com.devyk.ndk_sample I/native-lib: count=6
> com.devyk.ndk_sample D/Java: 		count=7
> com.devyk.ndk_sample I/native-lib: count=8
> com.devyk.ndk_sample D/Java: 		count=9
> com.devyk.ndk_sample I/native-lib: count=10
> com.devyk.ndk_sample D/Java: 		count=11
> com.devyk.ndk_sample I/native-lib: count=12
> com.devyk.ndk_sample D/Java: 		count=13
> com.devyk.ndk_sample I/native-lib: count=15
> com.devyk.ndk_sample D/Java: 		count=15
> com.devyk.ndk_sample I/native-lib: count=16
> com.devyk.ndk_sample D/Java:		count=17
> com.devyk.ndk_sample I/native-lib: count=18
> com.devyk.ndk_sample D/Java: 		count=19
> com.devyk.ndk_sample I/native-lib: count=20
Copy the code

With multiple threads working on the count field, you can see that the visibility of count is no longer guaranteed. This requires the JNI local implementation to be synchronized as well.

Let’s uncomment:

Print the following:

> ** Output :** > > com.devyk.ndk_sample D/Java: count=1
> com.devyk.ndk_sample I/native-lib: 	count=2
> com.devyk.ndk_sample D/Java: 			count=3
> com.devyk.ndk_sample I/native-lib: 	count=4
> com.devyk.ndk_sample D/Java: 			count=5
> com.devyk.ndk_sample I/native-lib: 	count=6
> com.devyk.ndk_sample D/Java: 			count=7
> com.devyk.ndk_sample I/native-lib: 	count=8
> com.devyk.ndk_sample D/Java: 			count=9
> com.devyk.ndk_sample I/native-lib: 	count=10
> com.devyk.ndk_sample D/Java: 			count=11
> com.devyk.ndk_sample I/native-lib: 	count=12
> com.devyk.ndk_sample D/Java: 			count=13
> com.devyk.ndk_sample D/Java: 			count=14
> com.devyk.ndk_sample I/native-lib: 	count=15
> com.devyk.ndk_sample I/native-lib:	 count=16
> com.devyk.ndk_sample D/Java: 			count=17
> com.devyk.ndk_sample I/native-lib: 	count=18
> com.devyk.ndk_sample D/Java: 			count=19
> com.devyk.ndk_sample I/native-lib: 	count=20
Copy the code

Now count is guaranteed to be visible.

Native threads:

These native builds can use native threads in parallel to perform specific tasks. Because virtual machines are unaware of native threads, they cannot communicate directly with Java builds. Native threads should first be attached to the virtual machine in order to interact with the active parts of the application.

JNI provides the AttachCurrentThread function via the JavaVM interface pointer to allow native code to attach native threads to the virtual machine. As shown in the following code, the JavaVM interface pointer should be cached as early as possible, otherwise it cannot be retrieved.

JavaVM * JVM; . JNIEnv* env =NULL; . jvm->AttachCurrentThread(&env,0);// Attach native threads to the JVM. jvm->DetachCurrentThread();// Release native threads attached to the JVM
Copy the code

The call to AttachCurrentThread allows the application to get a pointer to the JNIEnv interface that is valid for the current thread. Reattaching an already attached native thread has no side effects. When the native thread completes, the DetachCurrentThread function can be used to separate the native thread from the virtual machine.

Example:

MainActivity.java

    public void test5(View view) {
        testThread();
    }

    // AndroidUI operation, let C++ thread inside to call
    public void updateUI(a) {
        if (Looper.getMainLooper() == Looper.myLooper()) {
            new AlertDialog.Builder(MainActivity.this)
                    .setTitle("UI")
                    .setMessage("Native runs on the main thread, updates the UI directly...")
                    .setPositiveButton("Confirm".null)
                    .show();
        } else {
            runOnUiThread(new Runnable() {
                @Override
                public void run(a) {
                    new AlertDialog.Builder(MainActivity.this)
                            .setTitle("UI")
                            .setMessage("Native runs when the child thread switches to the master thread to update the UI...")
                            .setPositiveButton("Confirm".null) .show(); }}); }}public native void testThread(a);
    public native void unThread(a);

    @Override
    protected void onDestroy(a) {
        super.onDestroy();
        unThread();
    }
Copy the code

native-lib.cpp

JavaVM * jvm;
jobject instance;
void * customThread(void * pVoid) {
    // call JNIEnv *env
    // JNIEnv *env cannot span threads. Only JavaVM can span threads

    JNIEnv * env = NULL; // new env
    int result = jvm->AttachCurrentThread(&env, 0); // Attach native threads to the JVM
    if(result ! =0) {
        return 0;
    }

    jclass mainActivityClass = env->GetObjectClass(instance);

    // Get the updateUI for MainActivity
    const char * sig = "()V";
    jmethodID updateUI = env->GetMethodID(mainActivityClass, "updateUI", sig);

    env->CallVoidMethod(instance, updateUI);

    // Release native threads attached to the JVM
    jvm->DetachCurrentThread();

    return 0;
}

extern "C"  // Supports THE C language
JNIEXPORT void JNICALL // Tell the virtual machine that this is a JNI function
native_testThread(JNIEnv *env, jobject thiz) {
    instance = env->NewGlobalRef(thiz); // Global is not released, so it can be used in threads
    // If it is not global, the function is released as soon as it finishes
    pthread_t pthreadID;
    pthread_create(&pthreadID, 0, customThread, instance);
    pthread_join(pthreadID, 0);

}

extern "C"  // Supports THE C language
JNIEXPORT void JNICALL // Tell the virtual machine that this is a JNI function
native_unThread(JNIEnv *env, jobject thiz) {

    if (NULL! = instance) { env->DeleteGlobalRef(instance); instance =NULL; }}Copy the code

Effect:

conclusion

This document provides a comprehensive overview of how JNI technology enables Java applications to communicate with native code. For more information about JNI technology, you can download the JNI user manual.