I haven’t posted this article for a long time. I started to plan this article at the end of October, but now it’s almost half of December. It’s so difficult for me… But it’s done. I have to have a late snack tonight. Shenzhen north, two kilometers to the north of the ** barbecue, anyone come? It’s on me. I’ll ask again if it’s not.

First look at the table of contents, you feel useful to you and then continue to see, after all, there are more than 10,000 words display, afraid of useless words delay your precious time.

Why are you writing this article?

I wrote a previous article about C code generation and debugging the SO library. Some time ago, there were some problems in the inheritance of an audio detection library, and reviewed the JNI part, by the way, sorted out and shared with you.

Goals and expectations

This article is a series of NDK/JNI basic to advanced tutorials, the goal is to watch this article friends can use Android C/C++ code, integration of C/C++ libraries have a relatively basic understanding, and can be skillfully applied to the project.

Before we learn JNI, let’s ask ourselves a few questions:

Three questions before school?

What is understanding? For what? And why?

What is JNI/NDK? What’s the difference?

What is JNI?

JNI, full name Java Native Interface, is a Java Native Interface. JNI is a feature of Java calling Native language. Through JNI, Java can interact with C/C++ models. To put it simply, JNI is the general term for calling C/C++ in Java.

What is the NDK?

Android NDK is a Native Develop Kit that allows you to use C, C++ and other languages to implement some applications in Native code. This helps you reuse code bases written in these languages when developing certain types of applications.

Both JNI and NDK call C/C++ codebase. So in general, there’s not much difference except for the application scenarios. The subtle difference is that JNI can be used with Both Java and Android, while NDK can only be used with Android.

So, what is JNI/NDK?

What is JNI/NDK used for?

In a word, quickly call C/C++ dynamic library. It does nothing but call C/C++.

It’s that simple. After we know what to do, what’s the use of learning this thing?

What can I benefit from studying JNI/NDK?

I can think of two points, one is to make me happy to use C/C++ library in development, the second is to have a deeper understanding of security attack and defense. In fact, either of these two points gives me enough dynamics to go down. So, what do you think? Get him.

How to use JNI/NDK?

How do I configure the JNI/NDK environment?

Configuring the NDK environment is relatively simple. We can do this in three simple steps:

  • Step 1: Download the NDK. You can download it from Google official or directly open AS. The latter is recommended. LLDB and CMake can also be downloaded here.
  • Step 2: Configure the NDK path. You can directly configure the NDK path in the AS.
  • Step 3: Open the console, CD to the NDK specified directory, verify whether the NDK environment is successful.

Ok, verify that your NDK configuration is successful as shown above. So easy.

HelloWorld goes into the world of C/C++ together

From now on, let’s enter HelloWorld together. Let’s create a Native C++ project through AS. The main steps are as follows:

  • Step 1: File –> New –> New Project slide to the bottom of the box, select Native C++, and click next.
  • Step 2: Select a name and press Next until Finish is complete.

Simple and easy to understand? Ok, project created, run, look, Hello World, project created.

How to call C/C++ code in Android?

From the new project above we see a CPP directory, under which we write our C/C++ code. You’ll find a file called native-lib. CPP, which is where C/C++ assigns Hello World.

Android: C/C++ library

  • Step 1: Introduce the C library name through System.loadLibrary.
  • Step 2: Write C/C++ code in natice-lib.cpp.
  • Step 2: call the corresponding implementation method in C/C++ file.

Hello World Demo code:

The Android code:

public class MainActivity extends AppCompatActivity {

    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);
        tv.setText(stringFromJNI());
    }

    public native String stringFromJNI();
}

Copy the code

Natice – lib. CPP code:

#include <jni.h>
#include <string>

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

Ok, so now we call it ok, but we want to generate the object instance in JNI, call the corresponding method, and operate the corresponding property, what should we do? OK, the next section will answer these questions and let’s take a look at the APIS in JNI/NDK.

JNI/the NDK apis

In C/C++ native code to access java-side code, a common use is to get class properties and call class methods. To represent properties and methods in C/C++, JNI defines jfieldID and jmethodID types in the jni.h header file to represent Java side properties and methods, respectively. When accessing or setting Java properties, it is necessary to obtain the jfeldID representing the Java properties in the local code first, and then the Java properties can be operated in the local code. Similarly, when calling java-side methods, it is also necessary to obtain the jmethodID representing the method before the Java method can be called.

Next, let’s try calling Java methods in Native. Let’s start with two common types:

JNIEnv and jobject

In native-lib. CPP above, we see that the getCarName method has two parameters, JNIEnv *env and jobjet instance. A few words about what these two types do.

JNIEnv type

The JNIEnv type actually represents the Java environment, and you can manipulate java-side code through a JNIEnv* pointer. For example, we can use JNIEnv to create objects in Java classes, call methods on Java objects, and get properties from Java objects.

There are many functions available in the JNIEnv class, as follows:

  • NewObject: Creates an object in a Java class.
  • NewString: Creates a String object in a Java class.
  • NewArray: Creates an array object of Type Type.
  • GetField: Gets the field of Type Type.
  • SetField: Sets the value of a field of Type Type.
  • GetStaticField: Obtains a static field of Type Type.
  • SetStaticField: Sets the value of a static field of Type Type.
  • CallMethod: Calls a method whose return Type is Type.
  • CallStaticMethod: Calls a static method that returns a value of Type Type. Of course, there are more functions that can be used in addition to these common function methods, which can be viewed in the jni.h file, Or reference https://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html link to query the relevant methods, above all say so clearly.

Ok, JNIEnv out of the way, let’s move on to the second jobject.

Jobject type

Jobject can be thought of as a reference to a class instance in Java. Of course, different circumstances mean different things.

If a native method is not static, obj represents a class instance of the native method.

If a native method is static, obj represents the class instance of the native method’s class.

A simple example would be to create a static method testStaticCallMethod and a non-static method testCallMethod in a TestJNIBean.

TestJNIBean code:

public class TestJNIBean{
    public static final String LOGO = "learn android with aserbao";
    static {
        System.loadLibrary("native-lib");
    }
    public native String testCallMethod(); // Public static native StringtestStaticCallMethod(); // Static public Stringdescribe() {return LOGO + "Non-static method";
    }
    
    public static String staticDescribe() {return LOGO + "Static method"; }}Copy the code

CPP file implementation:

extern "C"JNIEXPORT jstring JNICALL Java_com_example_androidndk_TestJNIBean_testCallMethod(JNIEnv *env, jobject instance) { jclass a_class = env->GetObjectClass(instance); JmethodID a_method = env->GetMethodID(a_class,"describe"."()Ljava/lang/String;"); Jobj = env->AllocObject(a_class); New jString pring= (jString)(env)->CallObjectMethod(jobj,a_method); // The class calls the method in the class char *print=(char*)(env)->GetStringUTFChars(pring,0); // Convert format output.return env->NewStringUTF(print);
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_androidndk_TestJNIBean_testStaticCallMethod(JNIEnv *env, jclass type) {
    jmethodID  a_method = env->GetMethodID(type."describe"."()Ljava/lang/String;"); Jobj = env->AllocObject(type); New jString pring= (jString)(env)->CallObjectMethod(jobj,a_method); // The class calls the method in the class char *print=(char*)(env)->GetStringUTFChars(pring,0); // Convert format output.return env->NewStringUTF(print);
}
Copy the code

The main difference between the two methods above is that static methods pass in jClass directly, so we can skip getting the jClass, whereas non-static methods pass in the current class

Ok, let’s briefly talk about the mapping between Java types and Native types.

The mapping between Java types and types in Native

Java type The local type An alias defined by JNI
int long jint/jsize
short short jshort
long _int64 jlong
float float jfloat
byte signed char jbyte
double double jdouble
boolean unsigned char jboolean
Object _jobject* jobject
char unsigned short jchar

And we’ll talk about that when we use it. Ok, so much for the basics, and basic calls to C/C++ libraries in Android. Convenient and quick. Call native methods directly. But most of the time, we need to do something with Java code in C/C++ code for our encryption or method call purposes. What to do at this point? Now let’s see how to call Java code in C/C++.

How do I get classes in Java and generate objects

There are several methods in the JNIEnv class to get Java classes:

  • Jclass FindClass(const char* name) finds a class by its name, the full class name

It is important to note that the FindClass method parameter name is the full path to a class. For example, if we want to call the getTime method of the Java Date class, we can do this:

extern "C"
JNIEXPORT jlong JNICALL
Java_com_example_androidndk_TestJNIBean_testNewJavaDate(JNIEnv *env, jobject instance) {
    jclass  class_date = env->FindClass("java/util/Date"); Illegal class name jmethodID a_method = env->GetMethodID(class_date, class_date)"<init>"."()V");
    jobject  a_date_obj = env->NewObject(class_date,a_method);
    jmethodID  date_get_time = env->GetMethodID(class_date,"getTime"."()J");
    jlong get_time = env->CallLongMethod(a_date_obj,date_get_time);
    return get_time;
}
Copy the code
  • Jclass GetObjectClass(Jobject obj) retrieves the class of an object

This is a little bit easier to understand, because based on what we said above, depending on the type of jobject, when we write a method in JNI, if it’s non-static, we pass a Jobject object. We can get the class of the current object based on what is passed in. The code is as follows:

extern "C"JNIEXPORT jstring JNICALL Java_com_example_androidndk_TestJNIBean_testCallMethod(JNIEnv *env, jobject instance) { jclass a_class = env->GetObjectClass(instance); // get a_class from instance... }Copy the code
  • Jclass GetSuperClass(jClass obj) gets an object passed in to get the jClass of its parent class.

Now that we know how to get Java classes from JNIEnv, let’s learn how to get and call Java methods.

How to call Java methods in C/C++?

In the JNIEnv environment, there are two ways to get methods and attributes:

  • GetMethodID: Gets the ID of a non-static method.
  • GetStaticMethodID: Gets the ID of a static method. To obtain the corresponding jmethodID.

GetMethodID:

  jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
Copy the code

Parameter description of the method:

  • Clazz: This method depends on the class object of the class object.
  • Name: The name of this field.
  • Sign: The signature of this field (each variable, each method has a signature).

For a small example, let’s say we want to call the describe method in TestJNIBean in JNI.

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_androidndk_TestJNIBean_testStaticCallMethod(JNIEnv *env, jclass type) {
    jmethodID  a_method = env->GetMethodID(type."describe"."()Ljava/lang/String;"); Jobj = env->AllocObject(type); New jString pring= (jString)(env)->CallObjectMethod(jobj,a_method); // The class calls the method in the class char *print=(char*)(env)->GetStringUTFChars(pring,0); // Convert format output.return env->NewStringUTF(print);
}
Copy the code

GetStaticMethodID has the same method as GetMoehodID, except that it is used to get the ID of the static method. Again, we call the staticDescribe method in TestJNiBean from the CPP file, as follows:


extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_androidndk_TestJNIBean_testStaticCallStaticMethod(JNIEnv *env, jclass type) {
    jmethodID  a_method = env->GetStaticMethodID(type."staticDescribe"."()Ljava/lang/String;"); Jstring pring= (jstring)(env)->CallStaticObjectMethod(type,a_method); // The class calls the method in the class char *print=(char*)(env)->GetStringUTFChars(pring,0); // Convert format output.return env->NewStringUTF(print);
}
Copy the code

The above call is actually quite different, and it is the same as we usually use in Java, when static methods just need to pass a JClass object to call static methods, non-static methods need to be instantiated and then called.

How to call a parent class method in C/C++?

How do we call exactly the method we want in the polymorphic case? For example, I have a Father class that has a toString method, and then Child inherits Father and overwrites toString. How do we call Father and Child toString separately in JNIEnv?

The code implementation is as follows:

public class Father {
    public String toString() {return "Method in the parent class called";
    }
}

public class Child extends Father {
    @Override
    public String toString() {return "Methods in calling subclasses";
    }
}


public class TestJNIBean{
    static {
        System.loadLibrary("native-lib");
    }
    public Father father = new Child();
    public native String testCallFatherMethod(); // Call the parent toString method public native StringtestCallChildMethod(); // Call subclass toString}Copy the code

CPP code implementation:

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_androidndk_TestJNIBean_testCallFatherMethod(JNIEnv *env, jobject instance) {
    jclass clazz = env -> GetObjectClass(instance);
    jfieldID  father_field = env -> GetFieldID(clazz,"father"."Lcom/example/androidndk/Father;");
    jobject  mFather = env -> GetObjectField(instance,father_field);
    jclass  clazz_father = env -> FindClass("com/example/androidndk/Father");
    jmethodID  use_call_non_virtual = env -> GetMethodID(clazz_father,"toString"."()Ljava/lang/String;"); CallNonvirtual***Method jString result = (jstring) env->CallNonvirtualObjectMethod(mFather,clazz_father,use_call_non_virtual);return result;
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_androidndk_TestJNIBean_testCallChildMethod(JNIEnv *env, jobject instance) {
    jclass clazz = env -> GetObjectClass(instance);
    jfieldID  father_field = env -> GetFieldID(clazz,"father"."Lcom/example/androidndk/Father;");
    jobject  mFather = env -> GetObjectField(instance,father_field);
    jclass  clazz_father = env -> FindClass("com/example/androidndk/Father");
    jmethodID  use_call_non_virtual = env -> GetMethodID(clazz_father,"toString"."()Ljava/lang/String;"); Call***Method jstring result = (jstring) env->CallObjectMethod(mFather,use_call_non_virtual);return result;
}
Copy the code

TestCallFatherMethod and testCallChildMethod are called and run respectively.

A method in a subclass of a method in the parent class of a callCopy the code

As we can see from the above example, the only difference between calling a parent and subclass Method in JNIEnv is that a parent Method is called with a CallNonvirtual Method, while a subclass Method is called with a Call.

Ok, so now we’ve figured out how to use polymorphism in JNIEnv. Now let’s look at how to modify Java variables.

How to modify Java variables in C/C++?

Changing the corresponding variable idea in Java is actually quite simple.

  • Find the corresponding class object.
  • Find the attributes in the class that need to be modified
  • Reassign values to attributes in a class

The code is as follows:

public class TestJNIBean{
    static {
        System.loadLibrary("native-lib"); } public int modelNumber = 1; /** * Modify the modelNumber attribute */ public native voidtestChangeField(); } /* * Modifies the property */ extern"C"JNIEXPORT void JNICALL Java_com_example_androidndk_TestJNIBean_testChangeField(JNIEnv *env, jobject instance) { jclass a_class = env->GetObjectClass(instance); JfieldID a_field = env->GetFieldID(a_class,"modelNumber"."I"); Env ->SetIntField(instance,a_field,100); // Reassign attributes}Copy the code

After calling the testChangeField() method, the modelNumber in the TestJNIBean will be changed to 100.

How to manipulate Java strings in C/C++?

  1. The difference between strings in Java and character creation in C/C++ is that strings in Java are Unicode characters, whether they are Chinese characters, letters, or punctuation marks.

Some methods for getting strings in JNIEnv:

  • Jstring NewString(const jchar* unicodeChars, jsize len): Generates jStrings, replacing (Unicode)char arrays with jStrings. Like this:
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_androidndk_TestJNIBean_testNewString(JNIEnv *env, jclass type) {
    jchar* data = new jchar[7];
    data[0] = 'a';
    data[1] = 's';
    data[2] = 'e';
    data[3] = 'r';
    data[4] = 'b';
    data[5] = 'a';
    data[6] = '0';
    return env->NewString(data, 5);
}
Copy the code
  • Jstring NewStringUTF(const char* bytes): Generates and returns a Java String from the (UTF-8)char array. The operation is as follows:
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_androidndk_TestJNIBean_testNewStringUTF(JNIEnv *env, jclass type) {
    std::string learn="learn android from aserbao";
    returnenv->NewStringUTF(learn.c_str()); //c_str() returns a pointer to a regular C string, the same content as the string.Copy the code
  • Jsize GetStringLength(jString JMSG): Gets the length of a string (Unicode).
  • Jsize GetStringUTFLength(jString String): Gets the length of the string ((utF-8)).
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_androidndk_TestJNIBean_testStringLength(JNIEnv *env, jclass type,
                                                         jstring inputString_) {
    jint result = env -> GetStringLength(inputString_);
    jint resultUTF = env -> GetStringUTFLength(inputString_);
    return result;
}
Copy the code
  • Void GetStringRegion(jString STR, jsize start, jsize len, jchar* buf): Copy the Java string and pass in the UTF-8 encoding of JSTR.
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_androidndk_TestJNIBean_testGetStringRegion(JNIEnv *env, jclass type,
                                                            jstring inputString_) {
    jint length = env -> GetStringUTFLength(inputString_);
    jint half = length /2;
    jchar* chars = new jchar[half];
    env -> GetStringRegion(inputString_,0,length/2,chars);
    return env->NewString(chars,half);
}

Copy the code
  • Void GetStringUTFRegion(jString STR, jsize start, jsize len, char* buf): Copy the Java string and pass in the UTF-16 encoding JSTR
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_androidndk_TestJNIBean_testGetStringUTFRegion(JNIEnv *env, jclass type,
                                                               jstring inputString_) {
    jint length = env -> GetStringUTFLength(inputString_);
    jint half = length /2;
    char* chars = new char[half];
    env -> GetStringUTFRegion(inputString_,0,length/2,chars);
    return env->NewStringUTF(chars);
}
Copy the code
  • Jchar * GetStringChars(jString String, jBoolean * isCopy): Converts a JString object into a pointer to a jchar string. The jchar returned by this method is a utF-16 encoded wide string.

    Note: The returned pointer can either point to a Java String or to a copy in JNI. The isCopy argument returns whether the Java String is copied or not. If isCopy is set to NUll, the Java String is not copied. The return value is const, so the retrieved (Unicode)char array cannot be changed; ReleaseStringChars(jString string, const jchar* chars) ¶

  • Char * GetStringUTFChars(jString String, jBoolean * isCopy): Converts a JString object into a pointer to a jchar string. The jchar returned by the utF-8 method is a UTF-8 encoded string.

    The return pointer may also point to a Java String. Depends on the value of isCopy. The return value is a const modifier and cannot be modified. ReleaseStringUTFChars(jString string, const char* utf) ¶

  • Const jchar* GetStringCritical(jString string, jboolean* isCopy): Converts jString to const jchar*. The difference between he and GetStringChars/GetStringUTF GetStringCritical tend to get Java String pointer, rather than a copy.

    ReleaseStringCritical(jString string, const jchar* carray) In particular, there is a critical zone between the GetStringCritical call and ReleaseStringCritical release method calls, and no other JNI functions can be called. Otherwise, the garbage collector will stop working during critical area code execution, any threads that trigger the garbage collector will also be suspended, and any other threads that trigger the garbage collector can’t move forward until the current thread ends and the garbage collector is activated. This means that there should be no interrupts in critical areas, or any new objects allocated in the JVM; Otherwise, a JVM deadlock will occur.

How to manipulate Java arrays in C/C++?

  • JType * GetArrayElements((Array Array, jBoolean * isCopy)) : This method converts Java primitives into C/C++ arrays. When isCopy is true, it indicates that a copy of the data is made, and the pointer to the returned data is the pointer to the copy. If false, no copy is made and Pointers to Java data are used. IsCopy can be passed NULL or 0 if not applicable.
  • Void ReleaseArrayElements(jTypeArray array, j* elems, Jint mode) The corresponding ReleaseArrayElements method must be called once, because this removes JNI local references that might prevent garbage collection. Note here that the last parameter to this method, mode, is used to avoid unnecessary impact on the Java heap when processing replica data. If isCopy in GetArrayElements is true, then we need to set mode. If isCopy in GetArrayElements is false, then we can set mode to 0. Mode has three values:
    • 0: Updates the data on the Java heap and frees up space occupied by the copy.
    • JNI_COMMIT: Commits to update data on the Java heap without freeing space used by the copy.
    • JNI_ABORT: Abort, does not update data on the Java heap, freeing up space occupied by copies.
  • Void * GetPrimitiveArrayCritical (jarray array, jboolean * isCopy) : role similar to GetArrayElements. This method might return a pointer to the original array through the VM. Be careful to avoid deadlocks when using this method.
  • Void ReleasePrimitiveArrayCritical (jarray array, void * carray, jint mode) : the above method corresponding to release. Be careful not to call any JNI function methods between the two methods. This may cause the current thread to block.
  • Void GetArrayRegion(JNIEnv *env, ArrayType array,jsize start, jsize len, Type *buf) Create a buffer in C/C++ and copy the original Java array into the buffer.
  • Void SetArrayRegion(JNIEnv *env, ArrayType array,jsize start, jsize len, const Type *buf) Sets part of the buffer’s data back into the Java raw array.
  • Jsize GetArrayLength(JNIEnv *env, jarray array): Obtains the array length.
  • JobjectArray NewObjectArray(JNIEnv *env, jsize Length, jClass elementClass, jobject initialElement): Creates an array of specified length.

To use the above methods, use a method like this:

extern "C"JNIEXPORT void JNICALL Java_com_example_androidndk_TestJNIBean_testGetTArrayElement(JNIEnv *env, jobject instance) { jclass jclazz = env -> GetObjectClass(instance); JfieldID fid_Arrays = env-> GetFieldID(jclazz,"testArrays"."[I"); JintArray jint_arr = (jintArray) env->GetObjectField(instance, fid_arrays); Jint * int_arr = env->GetIntArrayElements(jint_arr, NULL); Jsize len = env->GetArrayLength(jint_arr); LOGD("The raw data obtained at --------------- is ---------------");
    for(int i = 0; i < len; i++){
        LOGD("len %d",int_arr[i]); } // create a new jintArray object jintArray jint_arr_temp = env->NewIntArray (len); Jint * int_arr_temp = env->GetIntArrayElements (jint_arr_temp, NULL); // Count jint count = 0; LOGD("--------------- prints an odd number ---------------"); // The odd digits are stored in int__arr_ temp memoryfor(jsize j=0; j<len; j++) { jint result = int_arr[j];if(result % 2 ! = 0) { int_arr_temp[count++] = result; }} // Prints an array of int_ _arr_ temp memoryfor(int k = 0; k < count; k++){
        LOGD("len %d",int_arr_temp[k]);
    }

    LOGD("--------------- Print the first two ---------------"); Jint * buffer = new jint[len]; Env ->GetIntArrayRegion(jint_arr,0,2,buffer);for(int z=0; z<2; z++){ LOGD("len %d",buffer[ z]);
    }

    LOGD("--------------- reassign to print ---------------"); // Create a new int array jint* buffers = new jint[3]; jint start = 100;for(int n = start; n < 3+start ; ++n) { buffers[n-start] = n+1; } env -> SetIntArrayRegion(jint_arr,1,3 buffers); Int_arr = env -> GetIntArrayElements(jint_arr,NULL);for (int i = 0; i < len; ++i) {
        LOGD("The result of the reassignment is %d",int_arr[i]);
    }

    LOGD("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- order -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");

    std::sort(int_arr,int_arr+len);
    for (int i = 0; i < len; ++i) {
        LOGD("Sort result is %d",int_arr[i]);
    }

    LOGD("--------------- Data processing completed ---------------");

}
Copy the code

Running results:

D/learn JNI: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- to get the raw data for the -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- D/learn JNI: len 1 D/learn JNI: len 2 D/learn JNI: len 3 D/learn JNI: len 4 D/learn JNI: len 5 D/learn JNI: len 8 D/learn JNI: len 6 D/learn JNI: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- print which is odd -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- D/learn JNI: len 1 D/learn JNI: len 3 D/learn JNI: len 5 D/learn JNI: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- before printing two -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- D/learn JNI: len 1 D/learn JNI: len 2 D/learn JNI: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- to assign a value to print -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- D/learn JNI: assignment again after the results of 1 D/learn JNI: assignment again after the results of 101 D/learn JNI: 102d /learn JNI: 103d /learn JNI: 103d /learn JNI: 103d /learn JNI: 103d /learn JNI: 103d /learn JNI: 103d /learn JNI: 103d /learn JNI: 103d /learn JNI: 103d /learn JNI: 103d /learn JNI: 103d /learn JNI: 103d /learn JNI: 103d /learn JNI To the result of the assignment again after 6 D/learn JNI: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- order -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- D/learn JNI: sorting the results of 1 D/learn JNI: sorting result for 5 D/learn JNI: 10 D/learn JNI: 10 D/learn JNI: 10 D/learn JNI: 10 D/learn JNI: 10 D/learn JNI: 10 D/learn JNI: 10 D/learn JNI: 10 D/learn JNI: 10 D/learn JNI: 10 D/learn JNI: 10 D/learn JNI: 10 D/learn JNI: 10 D/learn JNI: 10 D/learn JNI: 10 D/learn JNI: --------------- Data processing completed ---------------Copy the code

What are the differences between several references in JNI?

References are generated when objects created from the JVM are passed to C/C++ code and are not collected as long as references exist due to Java’s garbage collection mechanism. So we need to be careful when using references in both C/C++ and Java. C/C++ references:

Global references

Global references can span multiple threads and be valid in multiple functions. Global references need to be manually created using the NewGlobalRef method, and the corresponding method for releasing global references is DeleteGlobalRef

Local reference

Local references are common, and almost all return references retrieved from JNI functions are local references. Local references are valid only in a single function. Local references are automatically released when the function returns, although we can also release them manually using the DeleteLocalRef method.

A weak reference

Weak references also need to be created manually and have the same effect as global references, except that weak references do not prevent the garbage collector from collecting the referenced object. We can create a weak reference by using the NewWeakGlobalRef method, or release the corresponding weak reference by DeleteWeakGlobalRef method.

tip

How to print logs in C/C++?

Printing logs at the C/C++ layer in Jni is an important step to help us debug our code. There are three simple steps:

  • Step 1: Import the Android log function in the header of the log file to be printed.
#include <android/log.h>
Copy the code
  • Step 2: Customize LOGD tags. (May be omitted)
#define TAG "learn JNI" #define TAG "learn JNI
#define LOGD(...) Android_log_print (ANDROID_LOG_DEBUG,TAG,__VA_ARGS__) // Defines the LOGD type
#define LOGI(...) Android_log_print (ANDROID_LOG_INFO,TAG,__VA_ARGS__) // Defines the LOGI type
#define LOGW(...) Android_log_print (ANDROID_LOG_WARN,TAG,__VA_ARGS__) // Defines the LOGW type
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__) // Defines LOGE type
#define LOGF(...) Android_log_print (ANDROID_LOG_FATAL,TAG,__VA_ARGS__) // Defines the LOGF type
Copy the code
  • Step 3: Print the log.
LOGE("my name is %s\n"."aserbao"); // android_log_print(ANDROID_LOG_INFO,"android"."my name is %s\n"."aserbao"); // If the second step is omitted, you can print the log directly.Copy the code

Above are the CPP directories and.cpp files automatically created for our new project. What if you want to write your own? Listen to me:

How do I generate *.cpp from *.java?

For example, if I create a tool class Car and want to write a native method called getCarName(), how can we quickly get the corresponding.cpp file? The method is also very simple, we just need to run a few commands step by step on the line. The steps are as follows:

  • Step 1: Create a new utility class Car and write a local static method getCarName().
public class Car {
    static {
        System.loadLibrary("native-lib");
    }
    public native String getCarName();
}
Copy the code
  • Step 2: CD to the Car directory in Terimal and run the javac -h.car. Java command to obtain the corresponding. H file in the current directory.
aserbao:androidndk aserbao$ cd /Users/aserbao/aserbao/code/code/framework/AndroidNDK/app/src/main/java/com/example/androidndk
aserbao:androidndk aserbao$ javac -h . Car.java
Copy the code
  • Step 3: Change the. H to natice-lib. CPP and put it in the CPP directory.
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_androidndk_Car_getCarName(JNIEnv *env, jobject instance) {
    std::string hello = "This is a beautiful car";
    return env->NewStringUTF(hello.c_str());
}
Copy the code

I changed the return to “This is a beautiful car”, so after running we can see that hello world C++ becomes “This is a beautiful car”. And you’re done.

How do I get a signature for a method in Java?

Before we look at C/C++ calling Java code, let’s start with a quick note. The signature of a method in Java. I don’t know if you’ve seen this, but every method in Java has a signature. We will use method signatures several times in the following calls.

How do I get method signatures first? Very simple, like the Car object above, where we write a toString method. We can generate the. Class file by using javac command first, and then obtain the corresponding method signature by using javap command. The method and result are as follows:

javap -s **.class
Copy the code

The corresponding signature types are as follows:

type Corresponding signature
boolean Z
float F
byte B
double D
char C
void V
short S
object L Split the package’s full class name with /; Ljava/lang/String;
int I
Array [signature [I [Ljava/lang/Object;
long L
Method (Parameter type signature..) Return value type signature

Now that we have the method signature, we can start calling Java code in C/C++. Let’s learn how to call Java code in C/C++.

  • .java generated.class
javac *.java 
Copy the code
  • *. Java generated *. H
javac -h . *.java
Copy the code
  • Look at methods and signatures in *.class
javap -s -p *.class
Copy the code

How to handle exceptions in C/C++?

Exception handling is usually divided into two steps: catching and throwing exceptions. Implementing these two steps in C/C++ is also fairly simple. Let’s start with a few functions:

  • ExceptionCheck: Checks whether there is an exception. JNI_TRUE is returned if there is an exception. FALSE is returned otherwise.
  • ExceptionOccurred: Checks whether there is an exception. Exception is returned but NULL is not returned.
  • ExceptionClear: Clears abnormal stack information.
  • Throw: Throws the current exception.
  • ThrowNew: Create a new exception and customize the exception information.
  • FatalError: FatalError and terminates the current VM.

Example code:

Public class TestJNIBean{static {system.loadLibrary ();"native-lib");
    }
    public native void testThrowException();
    private void throwException() throws NullPointerException{
        throw new NullPointerException("this is an NullPointerException"); }} //JNI code extern"C"
JNIEXPORT void JNICALL
Java_com_example_androidndk_TestJNIBean_testThrowException(JNIEnv *env, jobject instance) {

    jclass jclazz = env -> GetObjectClass(instance);
    jmethodID  throwExc = env -> GetMethodID(jclazz,"throwException"."()V");
    if (throwExc == NULL) return;
    env -> CallVoidMethod(instance,throwExc);
    jthrowable excOcc = env -> ExceptionOccurred();
    if(excOcc){ jclass newExcCls ; env -> ExceptionDescribe(); Env -> ExceptionClear(); jclass newExcClazz = env -> FindClass("java/lang/IllegalArgumentException");
        if (newExcClazz == NULL) return;
        env -> ThrowNew(newExcClazz,"this is a IllegalArgumentException"); }}Copy the code

Running results:

12-05 15:20:27. 547, 8077-8077 / com. Example. Androidndk E/AndroidRuntime: FATAL EXCEPTION: the main Process: com.example.androidndk, PID: 8077 java.lang.IllegalArgumentException: this is a IllegalArgumentException at com.example.androidndk.TestJNIBean.testThrowException(Native Method) at com.example.androidndk.MainActivity.itemClickBack(MainActivity.java:90) at com.example.androidndk.base.viewHolder.BaseClickViewHolderThe $1.onClick(BaseClickViewHolder.java:32)
        at android.view.View.performClick(View.java:5198)
        at android.view.View$PerformClick.run(View.java:21147)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5417)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
    --------- beginning of system
Copy the code

The project address

Originally wanted to put this project into AserbaoAndroid inside, and then lazy, a new project, the whole article source code storage address :github.com/aserbao/And…

See articles and links

  • Getting started with the NDK
  • The Java Native Interface(JNI)
  • Java Native Interface Specification Contents

The article summarizes

This article from to start to final completion is almost off and on for more than a month of time, and it’s almost New Year’s day in an instant, visual this is the last a years ago, had planned to think about the related knowledge of so also writes the article inside, behind owing to consider a change of heart, relevant knowledge about so will back out of an article in detail.

This article is about learning some necessary things in JNI, I hope it is useful to you, later have time to publish a second article about C/C++ library access and use it.

Finally, as always, if you encounter any of the problems I wrote about in Android, please leave a comment on my account “Aserbaocool” and join the Android Community.