preface

Everybody is good! I again come, this is going to set out to write a JNI development series, after all, now the JNI development is becoming more and more important in companies, after all, if the project is big, may involves more modules, such as you, as the development of the application layer, hard to avoid can’t avoid the need to use some libraries, some of the cryptographic operations, etc., usually in a local method, safer, They throw you so files or static A files.. It’s embarrassing you don’t know how to use it. Online information is more miscellaneous, and very messy, mostly still in the use of. Mk method.

This series is based on CMake form, hope to be able to bring some hope to learn JNI development partners together to learn JNI development ~ than heart ❤

Zero base takes you to eat JNI Family Bucket (2)

Zero base takes you to eat JNI Family bucket (3)

Start with a chestnut

CMakeLists.txt

# Defines the minimum version number supported by cmakeCmake_minimum_required (VERSION 3.4.1 track) add_library (Set the name of the file where the so library is generated. For example, the name of the library generated here should be: libnative-lib.so
             native-lib

             Set the type of the generated so library. There are only two types:
             # STATIC: a STATIC library that is an archive of the target file and is used when linking to other targets
             # SHARED: a dynamic library that is dynamically linked and loaded at run time
             SHARED

             # Set the location of the source file. There can be many source files, all of which must be added. Pay attention to relative locations
             src/main/cpp/native-lib.cpp )

Find dependencies from the system, can add more than one
find_library( For example, find the system log library liblog.so
              log-lib

              The libnative-lib.so library is named log, just as the libnative-lib.so library is named native-lib
              log )
# Configure the link of the target library, i.e. the interdependency
target_link_libraries( # Target library (the resulting library)
                       native-lib

                        ${} (${}, ${}, ${});
                        If it is a third-party library, you can refer to the library name directly, for example:
                        Libthird. A: libthird. Note that when referencing, only one library can be referenced per line
                       ${log-lib} )
Copy the code

Here I had cut the notes, marking the Chinese annotation, more detailed, don’t understand every role may have a look, and, of course, a lot of API can be used, the subsequent detail again, also can look at the official documentation, poke me poking me [] (developer. The android. Google. Cn/the NDK/guides /…).

Let’s create a new Helper class to write native methods

public class NativeHelper  {
    static {
        System.loadLibrary("native-lib");
    }
    public  native String stringFromJNI();
    public  native int add(int a,int b);
 
}
Copy the code

Open up our MainActivity

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); // Example of a call to a native method TextView tv = (TextView) findViewById(R.id.sample_text); tv.setText(stringFromJNI()); }}Copy the code

As you can see at the top, the static code block references the native-lib library and then directly calls native methods to display the string returned by C++. And then how does C++ implement it

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

extern "C" JNIEXPORT jstring

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

The code is simple. It references two header files and defines a method that returns the string “Hello from C++”. Why does calling stringFromJNI () directly map to C++ methods? The e C++ method name is long and familiar. Isn’t this a string of Java package names plus method names, which is called static registration, so you can find the C++ method by mapping it.

Some friends have to say, so long method name is too much trouble, although can be automatically generated, but not beautiful, not elegant. Is a drop! If you have static registration, you have dynamic registration. If you have static registration, you have dynamic registration.

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

#define TAG "JNI_"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__)
JNICALL
jstring backStringToJava(JNIEnv *env, jobject /* this */) {
    std::string hello = "Hello from C++";
    returnenv->NewStringUTF(hello.c_str()); } // registerMethod(JNIEnv *env) {jclass CLZ = env->FindClass();"com/example/taolin/jni_project/NativeHelper");
    if (clz == NULL) {
        LOGD("con't find class: com/example/taolin/jni_project/NativeHelper");
    }
    JNINativeMethod jniNativeMethod[] = {{"stringFromJNI"."()Ljava/lang/String;", (void *) backStringToJava}};
    return env->RegisterNatives(clz,jniNativeMethod, sizeof(jniNativeMethod)/ sizeof(jniNativeMethod[0]));
}
jint JNI_OnLoad(JavaVM *vm, void *reserved){
    JNIEnv * env = NULL;
    if(vm->GetEnv((void**)&env,JNI_VERSION_1_6)! =JNI_OK){return JNI_ERR;
    }
    jint result = registerMethod(env);
    LOGD("RegisterNatives result: %d", result);
    return JNI_VERSION_1_6;
}
Copy the code

In this case, in order to print logs in C++, we need to introduce the log.h header file, and then we change the name of our method to backStringToJava, and since we don’t have static registration rules, the use of Java layer calls can’t find our corresponding method, so we define a dynamic registration method, Dynamically bind Java methods to C++ methods:

  • MainActivity’s class object is obtained from the env pointer, which is described in more detail later
  • Define a series of method objects, each taking three arguments, the first being the method name in Java, the second the corresponding signature of the method, and the third the method name in C++
  • In the JNI_OnLoad method, the dynamic registration binding method is called to bind

Method signature is a standard for the uniqueness of a method. ()Ljava/lang/String; StringFromJNI’s signature, the first parenthesis is the signature of the arguments, because there are no arguments, so empty, followed by the return worth signature, the rule is, if it’s a basic data type, it’s the corresponding basic data type, if it’s not a basic data type, then it’s L+ object package name + “;” Note that the semicolon here cannot be omitted! According to this rule, the signature of the following method is (II)I, and so on. It doesn’t matter if you don’t understand it.

public native String stringFromJNI(); // signature :()Ljava/lang/String; Public native int add(int a int b)"(II)I"
Copy the code

Then we can run the APP directly and see the “Hello from C++” string displayed on the page and see where the so file we generated is:

OK! Finished, our first step is completed, successfully completed the Java call C++ method, but don’t be happy too early, this is only the first step, well, see this reward yourself with spicy bar ~, slipped slipped.