JNI common development skills

foreword

  Android is now either becoming more and more front-end-various cross-end frameworks, or more and more low-level-framwork/driver development/audio and video. I don't like pure front-end things very much, just what I need for work. I recently started to gradually learn audio and video development (in fact, it is more about audio). To do a good job in deep Android audio and video development, JNI is an essential skill. Next, I will talk about some skills needed for Android JNI development.

1. Basic knowledge

1.1. Create a JNI project

  In fact, there is no difference between creating an ordinary app in AndroidStudio. Go to the interface of creating a project and selecting the project type to find the native c++ project and create it.

image.png

  Compared with ordinary app projects, it has an additional cpp folder, which contains commonly used header files (don't worry about it) and, CMakeList.txtit is a replacement for the previous MK files. Relatively speaking, it is simpler and clearer. As for how to write the official Detailed documentation, with a lot of content, the official CMAKE manual does not go into too much detail, and native-lib.cppthis is a helloworld created by the official for us, which will be described in detail below.

image.png

 There is one more method in its MainActivity stringFromJNI(). This is an interface method to be implemented, which is to be implemented in the Native layer. Note that the identifier in Kotlin is externalin Java.native

image.png

1.2. Data types in JNI

Looking at the jni header file, you will find the Java layer corresponding to the JNI layer 基本数据类型as shown in the figure below. From the name, you can see boolean byte, etc.

image.png


other 对象types. Commonly used objects such as object class string array are implemented by C++ objects, and these object pointers are defined. 

image.png

1.3. Two important structures

1.3.1 JNIEnv

  Most of our operations in the Native layer and the Java layer go through it. It defines the basic data types and object types, type conversion, and method calls of the Java layer in the Native layer of the various Java layers. All its methods are defined in jni.h. struct JNINativeInterfaceFor example, we usually get the String object of the Java layer in the native layer and convert it into a char* of the C language. After completing the string processing, we convert the char* into a jstring that the Java layer can recognize. All methods in the JNIEnv structure are used. The picture below is how I intercepted some of them. 

image.png

1.3.2 JavaVM

        JavaVM is the representative of the Native layer virtual machine stack. It is the only one in the process. Its main function is to coordinate the relationship between JNIEnv and threads. Its main methods are as shown in the figure below, and the specific usage will be pointed out in the fourth section. 

image.png

1.4. Two important methods

1.4.1、JNI_OnLoad

This method is the method that will be called when we load the library, that is, when calling

System.loadLibrary("native-lib")

Here we can do some initialization work, and we can also get the JavaVM and save it

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){
    JNIEnv* jniEnv= NULL;
    int result=(*vm)->GetEnv((vm),(void **)&jniEnv,JNI_VERSION_1_4);
    if (result!=JNI_OK){
        return -1;
    }
    javaVm = vm;//存起来可以后续使用
    return JNI_VERSION_1_4;
}

1.4.2、JNI_OnUnload

When our library is uninstalled, it will be called to do some finishing work. How to destroy it depends on your business

二、Java Call Native

Let's look at the official helloworld

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

  extern "C"It is a C++ code that needs to be marked. It is mainly a compatibility between C and C++. If it is a C code, this identifier does not need to be used. JNIEXPORTand JNICALLis a fixed identifier, jstringwhich is the return parameter type. It needs to be the type of the jni layer. Java_com_example_myapplication3_MainActivity_stringFromJNIIt is the method signature of stringFromJNI in our MainAtivity. AndroidStudio is very humane, as long as we enter stringFromJNI, it will automatically complete the method signature. In fact, you can compare it carefully. Can see how a signature method: Java_包名_类名_方法名.
In this method, define a Hello from C++ string, call the NewStringUTF method of JNIEnv to convert the native character pointer into a jstring type recognized by the Java layer and return it. Here is C++, and NewStringUTF accepts char* type, so the C++ string has to call the c_str() method to convert char*, if you use C language, there is no such step.

三、Native Call Java

3.1. Interface callback

First look at how my Java layer is written:

public interface IProgressListener{
    void onProgress(long recorderMs); 
}
public native void addProgressListener(IProgressListener listener);

In fact, the Java layer is written in the same way as the ordinary interface callback method, combined with native or external identifiers.
Look at how our native layer is written

JNIEXPORT void JNICALL Java_cn_zybwz_binmedia_OpenSLRecorder_addProgressListener(JNIEnv *env, jobject thiz, jobject listener) {                                                 
    jclass listenerClass=(*env)->GetObjectClass(env,listener);           
    progressListenerId=(*env)->GetMethodID(env,listenerClass,"onProgress", "(J)V"); 
    progressListenerObject=(*env)->NewGlobalRef(env,listener);//
    (*env)->CallVoidMethod(env,progressListenerObject,progressListenerId,(jlong)recorder_ms);
    (*env)->DeleteGlobalRef(env,progressListenerObject);
 }

First of all, in terms of process

  The most important thing is how to obtain the MethodID correctly. In fact, it is mainly determined by the two parameters behind GetMethodID. The first is the method name onProgress, followed by the input and output parameter lists. (J)VWe may not be very used to this, but (J)it is the representative of the input parameter type. long, Vis the type of output parameter representing void, the specific comparison table here
you will find two lines of code missing in my process, because these two lines of code can be taken out, this is a special point, otherwise when you call There is a good chance that the program will crash. The entire life cycle we pass in jobjec listeneris only in this method. After this method ends, the listenr object will be destroyed, even if we use external variables to receive it, because the address pointed to by this object has been cleared, so we need to pass NewGlobalRefTo instantiate a global variable, and correspondingly DeleteGlobalRefdestroy the global variable manually. After all, our callbacks are often not in a method, otherwise we might not be as convenient as return.

3.2. Call object method

Here we call the method of the Java layer through the native layer to print a log.
First, let's see how my Java layer is written.

public class C2J {
    public void LogError(String error){
        Log.e("C2J", "LogError: "+error );//简单的打印一条错误信息
    }
}

Look at how to write my native layer

JNIEXPORT jstring JNICALL Java_com_example_jnidemo_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject thiz) {
    jclass clazz=(*env)->FindClass(env,"com/example/jnidemo/C2J");
    jmethodID cmethodId=(*env)->GetMethodID(env,clazz,"<init>", "()V");
    jobject j=(*env)->NewObject(env,clazz,cmethodId);
    jmethodID methodId=(*env)->GetMethodID(env,clazz,"LogError", "(Ljava/lang/String;)V");
    (*env)->CallVoidMethod(env,j,methodId,(*env)->NewStringUTF(env,"err"));
}

Also analyze its process

3.3 A brief summary

In fact, not only CallVoidMethod can call methods, but there are corresponding calling methods according to the input parameter type and return value type of the called method.

  • Call Java non-static method

image.png

  • Call Java static method

image.png

   It can be seen that the interface callback of the Native and Java layers here we have mastered at least two methods, one is to save the interface object and call the method at the right time and place, or you can directly get the static method of the Java layer for notification, the static method We can do a lot inside, such as sending a broadcast, calling an observer's notification method, and so on.

4. Notes on threads in the Native layer

 The focus of this section is not on the knowledge of C/C++ threads, but on the problems encountered when calling JNIEnv in threads and how to avoid them. First of all, in principle, JNIEnv is an object that every JNI method will pass in. It is a context between Java and Native. It is thread-related, or it is attached to the thread, so in different threads It is actually invisible to operate JNIEnv, and it is even possible that the thread does not have a JNIEnv, so it will crash when called. For example, our ordinary C/C++ creates a thread, so how do we get a JNIEnv in the child thread? At this time, we need to use our JavaVM. JavaVM can be seen from its name that it is related to the virtual machine stack. Each process has only one virtual machine stack, which stores thread and JNIEnv information, so we need to pass It takes JNIEnv and attaches it to the current thread to make JNIEnv available on the current thread. The specific method is as follows:

JNIEnv* jniEnv=NULL;
javaVm->GetEnv((void **)jniEnv,JNI_VERSION_1_4);
javaVm->AttachCurrentThread(&jniEnv,NULL);
//使用jniEnv DO some thing
javaVm->DetachCurrentThread();

JNI_OnLoadFirst of all, this javaVm object is the global variable we saved   in one of the two important methods . GetEnv obtains JNIEnv, and AttachCurrentThread binds the current thread to JNIEnv. At this time, JNIEnv can be used normally. Remember to unbind it from the current thread after use.

Five, finally

  Part of the above code comes from an audio project I am preparing to do on GitHub. If you want to see more specific code, you can also read it above. This is also my hands-on project for learning audio and video. It is being built step by step. If you are interested, you can discuss it together.
Project Address
The above is my personal understanding and summary, which is not entirely correct. I hope everyone can criticize and correct me.

Guess you like

Origin blog.csdn.net/qq_37841321/article/details/122306106