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
-
Create native c++ projects
Click Next as prompted
-
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
-
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
-
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
-
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
-
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.
-
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.
-
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
-
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.
-
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 -
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.
-
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.