Since Android Studio uses CMake for compilation, there is no need to write android.mk, and there is no need to use javah to generate header files, directly write native methods, shortcuts can generate corresponding C++ methods. As long as you focus on writing C++ code, CMake can generate the corresponding SO library for a given CPU architecture.
The difference between JNI and NDK
NDK development can’t help but confuse THE difference between JNI and NDK.
JNI stands for Java Native Interface. It is used to make the Java language and C/C++ languages call each other. It has nothing to do with Android per se, but will be used in Android development as well as elsewhere.
The full name of NDK is Native Development Kit, and the full name of SDK is Software Development Kit, are Development kits. NDK is a toolkit for Android development, mainly used for C/C++ development, and provides related dynamic libraries.
Doing NDK development on Android is still a matter of learning THE JNI related skills, first being able to call each other from the Java layer to the C/C++ layer, and then learning the TECHNIQUES of NDK development.
Simple example
If you select Include C++ Support when creating an AS project, the C++ development environment is already configured.
When declaring native methods, it is better to write them in Java, which is much friendlier than Kotlin’s external keyword, and can directly generate C++ methods.
Declare native methods as follows:
public static native int plus(int a, int b);
Copy the code
The shortcut key generates the corresponding C++ method
extern "C" JNIEXPORT jint JNICALL Java_com_glumes_myapplication_NativeClass_plus(JNIEnv *env, jobject instance, jint a, jint b) { jint sum = a + b; return sum; }
Copy the code
This is a simple native method to calculate a+b, but it contains many basic contents. It receives the parameters from the Java layer in the C++ layer, converts them to the C++ layer data type, and returns the data type of the Java layer after calculation.
In the Java layer, there are only two parameters. In C++ code, there are four parameters, including at least the first two parameters.
The * env variable is an object of type JNIEnv, which is the environment in which the Java virtual machine runs and through which various objects within the Java Virtual machine can be accessed.
JNIEnv Object parameter env
JNIEnv* is the first parameter to define any native function. It is a pointer that accesses various data structures inside the virtual machine. It also points to a pointer to the JVM function table, where each entry points to a JNI function that accesses specific data structures within the JVM.
There are three classes of Pointers: JNIEnv * is itself a pointer, it points to a pointer, and every entry in the JVM function list is a pointer.
Jobject parameters
Jobject is the second parameter type in native functions, but it doesn’t have to be.
If the native method is a static method, the second argument is type jobject, which refers to the object on which the function is called.
If it is an instance method, the second argument is of type JClass, which refers to the class calling the function.
Basic data type conversion
The type of argument passed in Java is int, while in JNI it is jint, which involves java-jNI data type conversion.
As shown in the following table:
Java | Native | Signature | Symbol attribute | Word length |
byte | jbyte | B | unsigned | eight |
char | jchar | C | unsigned | 16 |
double | jdouble | D | A signed | A 64 – bit |
float | jfloat | F | A signed | 32 – |
int | jint | I | A signed | 32 – |
short | jshort | S | A signed | 16 |
long | jlong | J | A signed | A 64 – bit |
boolean | jboolean | Z | unsigned | eight |
void | void | V |
The basic data types we pass have relative data types in JNI.
Reference data type conversion
In addition to basic data types, reference data types also have one-to-one correspondence.
Java | Native | Signature |
All the objects | jobject | L+classname +; |
Class | jclass | Ljava/lang/Class; |
String | jstring | Ljava/lang/String; |
Throwable | jthrowable | Ljava/lang/Throwable; |
Object[] | jobjectArray | [L+classname +; |
byte[] | jbyteArray | [B |
char[] | jcharArray | [C |
double[] | jdoubleArray | [D |
float[] | jfloatArray | [F |
int[] | jintArray | [I |
short[] | jshortArray | [S |
long[] | jlongArray | [J |
boolean[] | jbooleanArray | [Z |
As you can see, the data types of all Java objects are represented by Jobject in JNI, except for the basic Java data types array, Class, String, and Throwable.
Now that you understand the parameter types, it’s time to complete the return value of the function as you would normally write code.
String String operations
Operations on basic data types, such as Boolean, int, float, and so on, are much the same, except that the JNI data type is represented by a j in front of the original data type.
For strings, the appropriate JNI functions must be used to convert jStrings to C/C++ strings.
For the following Native method, pass in a string and ask to return a string.
public static native String getNativeString(String str);
The corresponding C++ code generated is as follows:
extern "C" JNIEXPORT jstring JNICALL Java_com_glumes_cppso_SampleNativeMethod_getNativeString(JNIEnv *env, jclass type, Jstring str_) {// Generate a string of jString type jString returnValue = env->NewStringUTF(" Hello native String "); Const char * STR = env->GetStringUTFChars(str_, 0); Env ->ReleaseStringUTFChars(string, STR); // Return jString string return returnValue; }Copy the code
A Java layer string becomes a JString in JNI, but jString refers to a string inside the JVM. It is not a C-style char* string, so you cannot use JString in the same way as a C-style string.
JNI supports converting JString to UTF encoding and Unicode encoding. Java uses Unicode encoding by default, while C/C++ uses UTF encoding by default.
- GetStringUTFChars(jString String, jBoolean * isCopy) converts jString to utF-encoded strings
- GetStringChars(jString String, jBoolean * isCopy) convert jString to Unicode encoded string. So GetStringChars is not used very often.
The jString parameter is the string we need to convert, and the isCopy parameter has a value of JNI_TRUE or JNI_FALSE, which indicates whether a copy of the JVM source string is returned. If it is JNI_TRUE, the copy is returned and memory is allocated for the resulting string copy; JNI_FALSE directly returns a pointer to the JVM’s source string, meaning that the contents of the source string can be modified by a pointer, but this violates the Java rule that strings cannot be modified. In real development, NULL would be just fine.
Don’t forget to fully check when you’re done calling the GetStringUTFChars method. The GetStringUTFChars result is evaluated because the JVM needs to allocate memory for a new string that is generated, which returns NULL on failure and throws an OutOfMemoryError.
When you run out of UTF-encoded strings, don’t forget to free up the requested memory. Call the ReleaseStringUTFChars method to release.
The complete string conversion code is as follows:
Const char *utfStr = env->GetStringUTFChars(str_,NULL); If (utfStr == NULL){return NULL; Printf ("%s",utfStr); Env ->ReleaseStringUTFChars(str_,utfStr);Copy the code
In addition to converting JString to C-style strings, JNI provides for converting C-style strings to jString type.
The NewStringUTF function converts a UTF-encoded C-style string to jString, and the NewString function converts a Unicode-encoded C-style string to JString. This JString type is automatically converted to the Unicode encoding supported by Java.
In addition to converting between JString and C-style strings, JNI provides other functions.
Gets a pointer to the source string
In some cases, we just need to get a direct pointer to a Java string without converting it to a C-style string.
For example, if a string is very large, more than 1m long, and we only need to read the string, converting it to a C-style string is unnecessary (it can also be read with a direct string pointer), and we also need to allocate memory for the C-style string.
To do this, JNI provides GetStringCritical and ReleaseStringCritical functions to return a direct pointer to a string, so that only one pointer can be allocated in memory.
const jchar *c_str = NULL; c_str = env->GetStringCritical(str_, NULL); if (c_str == NULL) { // error handle } env->ReleaseStringCritical(str_, c_str);
Like GetStringUTFChars, the allocated pointer memory needs to be freed after use.
Note that GetStringUTFChars returns a pointer to a C-style string of const jchar, whereas GetStringUTFChars returns a pointer to a C-style string. GetStringCritical returns a direct pointer to the source Java string.
In addition, GetStringCritical has additional restrictions.
Native code between GetStringCritical and ReleaseStringCritical cannot call any Native or JNI functions that cause the thread to block or wait for other threads in the JVM.
Because GetStringCritical gets a direct pointer to the string inside the JVM, getting this direct pointer will cause the GC thread to be suspended, and when the GC thread is suspended, it will block the caller if another thread triggers the GC to continue running. Therefore, any native code in the middle of the GetStringCritical and ReleaseStringCritical functions must not perform blocking calls or allocate memory for new objects in the JVM, otherwise the JVM may deadlock.
It is also important to check whether the return value is NULL due to memory overflow, because there is still the possibility of data copying when the JVM executes GetStringCritical, especially if the array is not contiguous, in order to return a pointer to contiguous memory space, The JVM must copy all data.
Get the length of the string:
Since UTF-8 encoded strings end in \0 and Unicode strings do not, the function to obtain the string length is different for both encodings.
- GetStringLength gets the length of the Unicode encoded string.
- GetStringUTFLength gets the length of the utF-8 encoded string, or uses the STRlen function in C.
In this case, strings refer to strings in the Java layer. The parameters passed in are of type JSTING, and the Java layer is Unicode encoded by default, so GetStringLength is mostly used.
Gets the string content of the specified range
JNI provides functions to obtain the contents of a specified range of strings, which in this case are strings in the Java layer. The function copies the source string into a pre-allocated buffer.
- GetStringRegion gets the Unicode encoded content of the specified string.
- GetStringUTFRegion Gets the specified utF-8 encoded string content.
jchar outbuf[128],inbuf[128]; int len = env->GetStringLength(str_); env->GetStringRegion(str_,0,len,outbuf); LOGD(“%s”,outbuf);
String Summary of String function operations
A summary of functions on strings
JNI function | describe |
GetStringChars / ReleaseStringChars | Get or release a pointer to a Unicode encoded string (C/C++ string) |
GetStringUTFChars / ReleaseStringUTFChars | Get or release a pointer to a UTF-8 encoded string (C/C++ string) |
GetStringLength | Returns the length of the Unicode encoded string |
getStringUTFLength | Returns the length of the UTF-8 encoded string |
NewString | Converts a Unicode encoded C/C++ string to a Java string |
NewStringUTF | Converts utF-8 encoded C/C++ strings to Java strings |
GetStringCritical / ReleaseStringCritical | Gets or releases a pointer to the contents of a string (Java string) |
GetStringRegion | Gets or sets the content of the specified range of Unicode encoded strings |
GetStringUTFRegion | Gets or sets the contents of the specified range of utF-8 encoded strings |