JNI - NDK - cross compile

JNI

1.1 introduction to JNI

Java Nativie interface

Java local interface, Jni is a feature of Java calling local language. Jni enables Java and native languages to call each other

  • For example, java and c/c + + call each other

1.2 implementation steps

    1. Declare Native methods in java

      1. public native String stringFromJNI();
        
    2. The javac command compiles the java source file in 1 to get the class file

    3. The javah -jni command exports the JNI header (. h) file

    4. Use the local code that java needs to interact with to implement the native method declared in java

      1. extern "C"
        JNIEXPORT jstring JNICALL
        Java_com_example_ndktest_NdkManager_stringFromJNI(JNIEnv *env, jobject thiz) {
            std::string hello = "Hello from C++";
            return env->NewStringUTF(hello.c_str());
        }
        
    5. Compile native code into dynamic libraries

      1. Under windows dll, linux is So, MAC system is jnilib
    6. Execute Java program through Java command to realize java calling local code

1.3 what can the native layer do through jni

  • Create and update java objects (including array strings)
  • Call java method
  • Load class and get class information

1.3.1 operation on class

  • GetObjectClass gets the class file. Generally, it gets the class file of the user-defined type passed in the parameter

  • Findclass (full path of class) gets the class file. Generally, get the class file of the class that is not in the parameter list but needs to be used. The parameter is the path of the target class

  • GetMethodID get method name

  • GetFieldID get field

  • See the following example for details

    • extern "C"
      JNIEXPORT jobject JNICALL
      Java_com_example_ndktest_NdkManager_changName(JNIEnv *env, jobject thiz, jobject st, jstring name) {
          //Reflection java method
          //1 get the class file corresponding to java
          jclass stClass = env->GetObjectClass(st);
          // 2 find the method getName to call
          jmethodID setAge = env->GetMethodID(stClass, "setAge", "(I)V");
          jmethodID printInfo = env->GetStaticMethodID(stClass, "printInfo", "(Ljava/lang/String;)V");
          jmethodID getName = env->GetMethodID(stClass, "getName", "()Ljava/lang/String;");
          //3 call getName
          jstring sName = static_cast<jstring> (env->CallObjectMethod(st, getName));
         	// Convert the return value jstring to c available char
          const char *c = env->GetStringUTFChars(sName, 0);
          LOGE("The obtained name is:%s", c);
      
          //Release local reference
          env->ReleaseStringUTFChars(sName, c);
          // Call setAge
          env->CallVoidMethod(st, setAge, 25);
          // Call static method
          jstring str = env->NewStringUTF("printInfo");
          env->CallStaticVoidMethod(stClass, printInfo, str);
          //Release local reference
          env->DeleteLocalRef(str);
          
      	//Reflection class properties
          //Reflection get attribute id
          jfieldID nameId = env->GetFieldID(stClass, "name", "Ljava/lang/String;");
          jfieldID ageId = env->GetFieldID(stClass, "age", "I");
          //Set property id
          //  name
          env->SetObjectField(st, nameId, name);
          //  age
          env->SetIntField(st, ageId, 30);
      
          //Get methodid by passing the method of parameter object
          jmethodID toString=env->GetMethodID(stClass,"toSting", "(Lcom/example/ndktest/Student;)V");
          //create object
          //1 get the class first. We got the Student's class file above, so we won't get it again here
          //2 get construction method
          jmethodID constour= env->GetMethodID(stClass,"<init>", "(Ljava/lang/String;I)V");
          //3 Create Object
          jobject st1=env->NewObject(stClass,constour,env->NewStringUTF("jesse"),20);
          // 4. Call the method whose parameter is Studnet
          env->CallVoidMethod(st,toString,st1);
          // 5 release local references
          env->DeleteLocalRef(st1);
          env->DeleteLocalRef(stClass);
          return st;
      }
      

1.3.2 get parameters passed from java layer

  • The parameter is an array

    • All reference type arrays are jobjectArray

      • java

      • // Reference data type is parameter
            public native int Test(int[] list, String[] str);
        
      • c++

      • extern "C"
        JNIEXPORT jint JNICALL
        Java_com_example_ndktest_NdkManager_Test(JNIEnv *env, jobject thiz, jintArray i_Array,
                                                 jobjectArray str) {
            // Get the address of the first element of the array
            // Second parameter
            // jboolean* isCopy
            // true indicates that the newly requested memory copies a new array
            // false is to use java arrays
            jint *p = env->GetIntArrayElements(i_Array, NULL);
            // If it is a c environment, you need to replace env - > with (* Env) - >
            //jint *p1 =(*env).GetIntArrayElements(i_Array, NULL);
            // Because JNIEnv itself is a pointer in c environment, if it is declared as a pointer again here, it will become a secondary pointer. Therefore, it is necessary to solve a reference and get a primary pointer for operation
            //Gets the length of the array
            jint length = env->GetArrayLength(i_Array);
            for (int i = 0; i < length; i++) {
                if (i == 3) {
                    *(p + i) = 10;
                }
                LOGE("Acquired java Array value is: %d", *(p + i));
            }
            // Release array
            // Parameter 1
            // Parameter 2
            // Parameter 3: mode mode has three values
            // 0 refresh java array and release c/c + + array
            // 1 = JNI_COMMIT: refresh JAVA array only
            // 2 = JNI_ABORT: release only c/c + + arrays
            env->ReleaseIntArrayElements(i_Array, p, 0);
        	//The type array is traversed in the following way. First, get the length of the array, and then press the subscript to get it
            int count = env->GetArrayLength(str);
            for (int i = 0; i < count; i++) {
                // Get jstring
                jstring s1 = static_cast<jstring>(env->GetObjectArrayElement(str, i));
                // Can be converted to C / char in C + +
                const char *c = env->GetStringUTFChars(s1, 0);
                LOGE("Acquired java str Array value is: %s", c);
                // release
                env->ReleaseStringUTFChars(s1, c);
            }
            return count;
        }
        
  • The parameter is a basic data type

    • Can be used directly

      • java

      • // The basic data type is parameter
           public native int Test1(int i);
        
      • c++

      • extern "C"
        JNIEXPORT jint JNICALL
        Java_com_example_ndktest_NdkManager_Test1(JNIEnv *env, jobject thiz, jint i) {
            return i + 1;
        }
        
  • The parameter is a custom type

    • You need to operate through the method of the operation class
    • Please refer to the example in 1.3.1 above

1.4 JNI syntax

  • Declaring native methods in java

    • public native String stringFromJNI();
      
  • Implement the declared native method in native

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

1.4.1 code interpretation in native

1.4.1.1 extern "C"

1.4.1.2 JNIEXPORT

Macro definition: #define jniexport__ attribute__ ((visibility ("default")) in UNIX systems such as Linux/Unix/Mac os/Android, it is defined as__ attribute__ ((visibility ("default")))

  • Indicates that the function is visible to the outside world

  • GCC has a visibility attribute, which means enabling this attribute:

    • When - fvisibility=hidden, the functions in the dynamic library are hidden by default.
    • When - fvisibility=default, the functions in the dynamic library are visible by default.

1.4.1.3 JNICALL

Macro definition: in UNIX systems such as Linux/Unix/Mac os/Android, it is an empty macro definition: #define jnical, so you can delete it on android

1.4.1.4 thiz

  • thiz is the class that declares the native method in java, that is, the class that calls the native method

1.4.1.5JNIEnv

  • JNIEnv type actually represents the Java environment. Through this JNIEnv * pointer, you can operate the code on the Java side:
    • Call Java function
    • Manipulating Java objects
  • The essence of JNIEnv is a thread related structure. There is one JNIEnv for each thread

1.4.1.6 JavaVM

  • JavaVM: JavaVM is the representative of Java virtual machine in JNI layer. There is only one in JNI global
  • JNIEnv: the representative of JavaVM in threads. Each thread has one. There may be many jnienvs in JNI. At the same time, JNIEnv has thread correlation, that is, thread B cannot use the JNIEnv of thread A

1.4.1.7 how to use JNIenv in native threads

  • If you want to use JNIEnv * in the native thread, you need to bind with the AttachCurrentThread method of the JVM. When the child thread exits, you need to call the DetachCurrentThread function of JavaVM to release the corresponding resources, otherwise an error will occur.

    • JavaVM *_vm;
      
      jint JNI_OnLoad(JavaVM* vm, void* reserved){
          _vm = vm;
          return JNI_VERSION_1_6;
       }
      
      void* threadTask(void* args){
          JNIEnv *env;
          // Bind the current thread and assign value to env through javavm
          jint result = _vm->AttachCurrentThread(&env,0);
          if (result != JNI_OK){
              return 0;
          }
      
          // ...
      
          // Don't forget to detach the thread after the task is executed
          _vm->DetachCurrentThread();
      }
      
      extern "C"
      JNIEXPORT void JNICALL
      Java_com_example_ndktest_NdkManager_nativeThreadTest(JNIEnv *env, jobject thiz) {
          pthread_t pid;
          pthread_create(&pid,0,threadTask,0);
      }
      
    • Here through jni_ This method involves the registration method of jni, which will be described in detail later

1.5 JNI registration type

1.5.1 static registration

  • When the Java layer calls the navtie function, it will find the corresponding JNI function according to the function name in the JNI library. If it is not found, an error will be reported. If found, an association relationship will be established between the native function and the JNI function. In fact, it is to save the function pointer of the JNI function. Next time you call the native function, you can directly use this function pointer.

  • grammar

    • JNI function name format (the "." in the package name needs to be changed to "")
    • Java_ + Mainu function name (# jndmu package name) + #com
  • Disadvantages of static registration

    • It is required that the name of JNI function must follow the naming format of JNI specification;
    • The name is long and error prone;
    • The first call will search the corresponding function in JNI according to the function name, which will affect the execution efficiency;
    • All Java classes that declare native functions need to be compiled, and each generated class file needs to generate a header file with javah tool;
  • example

    • Package name: com example. ndktest

    • Class name NdkManager

    • java class

      • package com.example.ndktest;
        
        import java.util.ArrayList;
        import java.util.List;
        
        public class NdkManager {
            static {
                // native is implemented in the ndktest library, so you need to load the library, which will be mentioned later in cmake
                System.loadLibrary("ndktest");
            }
            public native String stringFromJNI();
        }
        
        
    • native implementation

      • #include <jni.h>
        #include <string>
        #include <android/log.h>
        
        // Define log macro output__ VA_ARGS__ Indicates that the variable parameter represents "..." in the preceding brackets
        #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "JNI", __VA_ARGS__);
        
        extern "C"
        JNIEXPORT jstring JNICALL
        Java_com_example_ndktest_NdkManager_stringFromJNI(JNIEnv *env, jobject thiz) {
            std::string hello = "Hello from C++";
            // 1. Get the Class of thiz, that is, the Class information in java
            jclass thisclazz = env->GetObjectClass(thiz);
            // 2. Get the methodID of the getClass method according to the Class. The third parameter is the signature (params)return
            jmethodID mid_getClass = env->GetMethodID(thisclazz, "getClass", "()Ljava/lang/Class;");
            // 3. Execute getClass method to obtain Class object
            jobject clazz_instance = env->CallObjectMethod(thiz, mid_getClass);
            // 4. Get Class instance
            jclass clazz = env->GetObjectClass(clazz_instance);
            // 5. According to the methodID of class
            jmethodID mid_getName = env->GetMethodID(clazz, "getName", "()Ljava/lang/String;");
            // 6. Call getName method
            jstring name = static_cast<jstring>(env->CallObjectMethod(clazz_instance, mid_getName));
            //Print thiz class name
            LOGE("class name:%s", env->GetStringUTFChars(name, 0));
            
            return env->NewStringUTF(hello.c_str());
        }
        

1.5.2 dynamic registration

  • Provide a function mapping table and register it with the JVM virtual machine, so that the JVM can call the corresponding function with the function mapping table, and there is no need to find the function to be called through the function name.

  • Java and JNI establish a function mapping table through the structure of JNI nativemethod, which is in JNI H is defined in the header file, and its structure is as follows:

    • typedef struct {
          const char* name; // Corresponding method name in java
          const char* signature; // Method signature
          void*       fnPtr;// /Function pointer corresponding to the method in interactive cpp (pointing to the corresponding function)
      } JNINativeMethod;
      
  • After creating the mapping table, call the env->RegisterNatives function to register the mapping table to JVM.

    • When the Java layer passes through system Loadlibrary when loading JNI library, JNI will be checked in the library_ Onload function.
    • JNI_OnLoad is the entry function of JNI library. All function mapping, dynamic registration and other initialization work need to be completed here.
  • example

    • Java class name NativeManager package name com example. dynamicndk

      • public class NativeManager {
            // Used to load the 'dynamicndk' library on application startup.
            static {
                System.loadLibrary("dynamicndk");
            }
            // Dynamic registration
            public native String stringFromJNI();
            public native void Test();
            // native thread calls java
            public native void threadTest();
        }
        
    • cpp

      • #include <jni.h>
        #include <string>
        #include <android/log.h>
        
        #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "JNI", __VA_ARGS__);
        
        JavaVM *_vm;//Save the JavaVM declaration as a global variable, which is convenient for extracting JNIEnv or when needed
        
        
        char *mClassName = "com/example/dynamicndk/NativeManager";
        
        void Test(JNIEnv *env, jobject thiz) {
            LOGE("Dynamic registration");
        }
        
        jstring stringFromJNI(JNIEnv *env, jobject thiz) {
            std::string hello = "Hello from C++";
        
            return env->NewStringUTF(hello.c_str());
        }
        // Create the mapping table corresponding to the native function to be bound
        JNINativeMethod jniMethod[] = {
                {"Test",          "()V",                  (void *) Test},
                {"stringFromJNI", "()Ljava/lang/String;", (jstring *) stringFromJNI},
        };
        // System. The first function to execute after loadlibrary
        int JNI_OnLoad(JavaVM *vm, void *unused) {
            LOGE("JniOnLoad");
            //Save as global variable javaVM
            _vm = vm;
            // Save global variable JNIEnv
            JNIEnv *env = nullptr; //nullptr replaces NUll to assign a value to the pointer, or you can write 0 directly
            // Here, the address of the env pointer should be passed in to facilitate the assignment of the pointer pointed to by the address
            int success = vm->GetEnv((void **) &env, JNI_VERSION_1_6);
        	//Judge success
            if (success != JNI_OK) {
                LOGE("SUCCESS %d:", success);
                return -1;
            }
            // Get the class to bind, that is, the corresponding java interaction class
            jclass jcls = env->FindClass(mClassName);
            // Register the native method to associate
            env->RegisterNatives(jcls, jniMethod, sizeof(jniMethod) / sizeof(JNINativeMethod));
            return JNI_VERSION_1_6;
        }
        
  • Step summary

      1. Create function mapping table
      2. Get the java class corresponding to the function
      3. Get JNIEnv and register the function mapping table through JNIEnv
  • advantage

    • More flexible dynamic registration and naming

1.6 data type conversion

1.6.1 basic data type

java typeNative typedescribe
booleanjbooleanunsigned 8 bits integer
bytejbytesigned 8 bits integer
charjcharunsigned 16 bits integer
shortjshortsigned 16 bits integer
intjintsigned 32 bits integer
longjlongsigned 64 bits integer
floatjfloatsigned 32 bits floating point
doublejdoublesigned 64 bits floating point
voidvoidNo shaping

1.6.2 reference data type

javanative
objectjobject
java.lang.Class instancejclass
java.lang.String instancejstring
arrayjarray
Object[]jobjectArray
boolean[]jbooleanArray
byte[]jbyteArray
char[]jcharArray
short[]jshortArray
int[]jintArray
long[]jlongArray
float[]jfloatArray
double[]jdoubleArray
java.lang.Throwable objectsjthrowable

1.6.3 function signature

  • format

    • [parameter 1 type character parameter 2 type character...] Return value type character

    • Note that the reference type should start with L, followed by the full path of the type and start with; ending

      • Example: Ljava/lang/String;
    • parameter

      • If it is multi parameter, there is no need to add type characters directly after the interval

      • example

      • java methodMethod signature
        String getStr(int a,int b)(II)Ljava/lang/String;
        String getS(int a,String b)(ILjava/lang/String;)Ljava/lang/String;
    • If there is no parameter, no content will be written in parentheses. If there is no return value, the return value position is V

      • Example (V)
  • Note that the reference type should start with L, followed by the full path of the type and start with; ending

    • Example: Ljava/lang/String;
  • Comparison table

    • Java typeCorresponding character
      voidV
      booleanZ
      intI
      longJ
      doubleD
      floatF
      byteB
      charC
      shortS
      int[][I (array starts with [followed by corresponding type character)
      StringLjava/lang/String; (reference type starts with L and ends with classpath)
      Object[][Ljava/lang/object;

1.7 JNI references

Local Reference

  • Created inside the function, the declared variables and objects belong to local references

  • When the method call ends, the local reference is automatically released

  • Of course, it can also be released manually

    • DeleteLocalRef()  // Delete is used to create objects
      ReleaseXXX 
      

Global Reference

  • JNI allows you to create global variables from local variables

    • //Declare global references
      jclass  st1Class;
      extern "C"
      JNIEXPORT jobject JNICALL
      Java_com_example_ndktest_NdkManager_changName(JNIEnv *env, jobject thiz, jobject st, jstring name) {
          //Find class
          if(st1Class==NULL){
              jclass  cls = env->FindClass("com/example/ndktest/Student");
              //Declare as global reference
              st1Class= static_cast<jclass>(env->NewGlobalRef(cls));
              env->DeleteLocalRef(cls);//Release local reference
              //env->DeleteGlobalRef(st1Class);// Release global reference
          }
      }
      
  • Can cross methods and threads

  • DeleteGlobalRef needs to be called to release

Weak Global Reference

  • Similar to global references, weak references can be used across methods and threads. Unlike global references, weak references do not prevent the GC from reclaiming objects inside the VM it points to

  • Therefore, when using weak references, you must first check whether the cached weak references point to the active object or to an object that has been GC

  • establish

    • //Declare global references
      jclass  st1Class;
      extern "C"
      JNIEXPORT jobject JNICALL
      Java_com_example_ndktest_NdkManager_changName(JNIEnv *env, jobject thiz, jobject st, jstring name) {
       	//Determine whether to point to the active object
          jboolean isEqual =env->IsSameObject(st1Class,NULL);
          if(st1Class==NULL||isEqual){
              jclass  cls = env->FindClass("com/example/ndktest/Student");
              //Declare as global reference
              st1Class= static_cast<jclass>(env->NewWeakGlobalRef(cls));
              env->DeleteLocalRef(cls);//Release local reference
              //env->DeleteWeakGlobalRef(st1Class);// Release weak global references
          }
      }
      
  • release

    • Call DeleteWeakGlobalRef to release

NDK

brief introduction

Android NDK is a set of tools that allow you to embed C or C + + ("native code") into Android applications. NDK describes a tool set

It is the native code of c/c + + called by jni

List of important structures

  • We can view the directory structure of ndk in SDK / ndk bundle. Here are three important members:
    • NDK build: this Shell script is the starting point of Android NDK building system. Generally, only executing this command in the project can compile the corresponding dynamic link library.
    • platforms: this directory contains header files and library files that support different Android target versions. The NDK construction system will reference header files and library files under the specified platform according to the specific configuration.
    • toolchains: this directory contains cross compilers under different platforms supported by NDK - ARM, X86 and MIPS. At present, ARM is commonly used// todo ndk-depends.cmd

Cross compilation

  • The process of compiling secondary files that can be executed on one platform is called cross compilation
    • For example, compile the available library files of android on windows

Library file format

  • Static library a
    • When compiling the link, all the code of the library file is added to the executable file, so the generated file is relatively large, but the library file is no longer needed at runtime. The suffix in linux is " a”
  • Dynamic library so
    • When compiling the link, the code of the library file is not added to the executable file, but the library is loaded by the runtime link file when the program is executed. The suffix in linux is " so ", gcc uses the dynamic library by default when compiling.

makefile (.mk) compilation

brief introduction

Makefile is "automatic compilation": the source files in a project are not counted, and they are placed in several directories according to type, function and module. Makefile defines a series of rules to specify which files need to be compiled first, which files need to be compiled later, how to link and so on. Android uses Android MK file to configure makefile

1.1 Android.mk

  • # The location of the source file in. The macro function my dir returns the path of the current directory (the directory containing the Android.mk file itself).
    LOCAL_PATH := $(call my-dir)
    
    # Import other makefile files. CLEAR_ The vars variable points to the special GNU Makefile, which can clear many local files for you_ XXX variable
    # Local will not be cleared_ Path variable
    include $(CLEAR_VARS)
    
    # Specify the library name. If the beginning of the module name is lib, the construction system will not attach additional prefix lib; Instead, the module name is adopted as is and added so extension.
    LOCAL_MODULE := hello
    # Contains a list of C and / or C + + source files to build into the module, separated by spaces
    LOCAL_SRC_FILES := hello.c
    # Build dynamic library
    include $(BUILD_SHARED_LIBRARY)
    
    

1.2 settings corresponding to Gradle

  • app/gradle

    • apply plugin: 'com.android.application'
      
      android {
          compileSdkVersion 29
      
          defaultConfig {
            ...
              // The source file should be compiled into several CPUs so
              externalNativeBuild{
                  ndkBuild{
                      abiFilters 'x86','armeabi-v7a'
                  }
              }
              // Several kinds of so need to be packaged into apk
              ndk {
                  abiFilters 'x86','armeabi-v7a'
              }
          }
          // Configure native build script location
          externalNativeBuild{
              ndkBuild{
                  path "src/main/jni/Android.mk"
              }
          }
          // Specify ndk version
          ndkVersion "20.0.5594570"
      
          ...
      }
      
      dependencies {
          implementation fileTree(dir: "libs", include: ["*.jar"])
          ...
      }
      
  • Google recommends developers to use cmake instead of makefile for cross compilation. When introducing a third-party precompiled so, there will be some differences before and after android 6.0. For example, before 6.0, you need to manually system Loadlibrary is a third-party so, which is not required later. There are many configuration parameters about Makefile, which are not explained here. For more reference Official documents.

    • Below 6.0, system Loadlibrary will not automatically load so. The internal dependent so is below 6.0, system Loadlibrary will automatically load the so that is internally dependent on so, so version compatibility is required when using mk

Cmake compilation

  • Is a build tool

1.1 CMakeLists.txt

  • # cmake version
    cmake_minimum_required(VERSION 3.10.2)
    
    #Declare and name items
    project("ndktest")
    
    # Declare and name Libraries
    # Set it to share or not. Here are three values 
    #	SHARED: represents a dynamic library, which can be used in (Java) code Loadlibrary (name) is called dynamically;
    # 	STATIC: represents a STATIC library, which will be called at compile time when integrated into the code;
    #	MODULE: only valid in the system using dyId. If dyId is not supported, it will be treated as SHARED;
    #	EXCLUDE_FROM_ALL: indicates that this library is not built by default, unless it is dependent on other components or built manually;
    # Add the path to the source file in the library
    add_library( # Set the name of the library. For example, ndktest will now be generated so
            ndktest
    
            # Set library as shared library
            SHARED
    
            # Provide relative path of source file
            native-lib.cpp)
    # Search and specify the pre built library and store the path as a variable (log Lib in this case).
    # There are already some pre built libraries (such as log) in the NDK, and the NDK library is already configured as part of the cmake search path
    # You can write directly in the target without writing_ link_ log Libraries
    # For example, we introduced Android / log H is liblog. In the ndk directory of the call The lib in front of so can be omitted and can be found by writing log directly
    find_library( # Set path variable name
            log-lib
    
            # Find the so library path with the specified name from the system path and assign it to the log lib above
            log)
    
    # Specifies the library that CMake should link to the target library. You can link multiple libraries, such as libraries defined in this build script, pre built third-party libraries, or system libraries.
    target_link_libraries( # Specify target library
            ndktest
    
            # The path to link the target library. Here is the path of log, that is, find above_ Log Lib in Library 
            # This is a link to the target library path in the form of variables. Of course, we can also find in the province_ Library this step directly specifies the target library
            # For example, the ndktest here can also be found by writing log directly
            # If you need to introduce a third-party so, you have to specify a directory to find it
            ${log-lib})
    

1.2 configuration in gradle

  • android {
        //......
        defaultConfig {
            applicationId "com.example.ndktest"
            minSdk 21
            targetSdk 31
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
            externalNativeBuild {
                cmake {
                    // Setting the c + + standard
                    cppFlags '-std=c++11'
                    //You need to generate so under several cpu architectures. If you don't write it, it will be generated by default
                    abiFilters "armeabi-v7a","x86"
                }
            }
            // Package apk packages supporting several architectures. For example, apks supporting x86 and armeabi-v7a architectures will be generated here
            ndk {
                abiFilters 'x86','armeabi-v7a'
            }
        }
        // Different packaged apk architectures
         splits {
            abi {
                enable true
                reset()
                include 'armeabi-v7a', 'x86'
                universalApk true
            }
        }
        //......
        // Configure the file path of the native build script
        externalNativeBuild {
            cmake {
                path file('src/main/cpp/CMakeLists.txt')
                version '3.10.2'
            }
        }
        //.....
    }
    
  • externalNativeBuild compiles the guidance source file in defaultConfig and configures the build script path of native outside defaultConfig

Add multiple source files

Reference the third-party dynamic library so

  • If you reference a so - > libtest. Com written in c So contains only one hc file

  • Here cmakelist Txt is moved from the cpp directory to the app directory to facilitate the later splicing path

  • Put the so of the corresponding architecture under the corresponding architecture directory of jinLibs. Here, take armeabi-v7a as an example

Mode 1

  • Add cmake lookup path and directly add a lookup path to cmake, under which external can be found
  • In cmakelist Txt add the following code level and target_link_libraries,find_library of the same level

    • set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_CURRENT_SOURCE_DIR}/jniLibs/${CMAKE_ANDROID_ARCH_ABI}")
      
    • Cmakelist Txt

      • CMAKE_CXX_FLAGS is compiled into variable names in c + + environment using c + +
      • CMAKE_C_FLAGS is compiled into variable names in the C environment using C
    • This variable is passed to the compiler

    • Redefine the variable and specify the target path through - L. you can dynamically identify the directory through the following variables to find the specified so

      • CMAKE_SOURCE_DIR current file path
      • ANDROID_ABI is the abi directory
    • Write test to target_link_libraries indicates that we want to link libtest so

      • When so starts with lib, it is written to target_link_libraries or find_ In library, lib can be omitted and only write the name

      • target_link_libraries( # Specifies the target library.        ndktest		test        # Links the target library to the log library        # included in the NDK.        ${log-lib})
        

Mode II

  • In cmakelist Txt add the following code level and target_link_libraries,find_library of the same level

  • And the so needs to be load ed in systemload

  • This method can still be used below android 6.0, and the problem of wrong directory will occur above 6.0. Therefore, we generally use the first method in cmake

    • # test On behalf of third parties so - libtest.so# SHARED Represents dynamic library, and static library is STATIC;# IMPORTED: Indicates that it is added in the form of import(Precompiled Library)add_library(test SHARED IMPORTED)#set up test Import path for(IMPORTED_LOCATION) attribute,Relative paths cannot be used# CMAKE_SOURCE_DIR: current cmakelists.txt Path of( cmake Tools (built-in)# Built in droid Android_ ABI: the cpu architecture that needs to be compiled at present_ target_ properties(external PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libtest.so)
      
    • Write test to target_link_libraries indicates that we want to link libtest so

      • target_link_libraries( # Specifies the target library.        ndktest		test        # Links the target library to the log library        # included in the NDK.        ${log-lib})
        

Use after introducing so

If libtest test() function exists in so

Condition 1 exists h

  • Direct #include < test h>

    • If the test function is written in c

      • Our environment is also a c + + environment, so we need to mark extern "C" in advance to call normally

        • extern "C" {    extern void test();}
          
      • If it is not a c + + environment, you can call the test function directly

Case 2 does not exist h

  • The function should be declared as extern, that is, the external definition is referenced here

    • extern void test();
      
    • If the test function is written in c

      • Our environment is also a c + + environment, so we need to mark extern "C" in advance to call normally

        • extern "C" {    extern void test();}
          
      • If it is not a c + + environment, you can call the test function directly

Introduce static library a

  • You don't have to put it in the jnilibs directory. You just need to set the search path of the library like referencing so

  • # test1 represents a third party The full name of a file is libtest1 a
    # SHARED Represents dynamic library, and static library is STATIC;# IMPORTED: indicates that it is added in the form of import (precompiled Library)
    add_library(test1 STATIC IMPORTED)
    #Set the imported_location attribute of test. Relative paths cannot be used
    # CMAKE_SOURCE_DIR: current cmakelists Txt path (cmake tool built-in)
    # android cmake built-in Android_ ABI: the cpu architecture AS3.0 that needs to be compiled at present After 2, ${ANDROID_ABI} is changed to ${CMAKE_ANDROID_ARCH_ABI}
    set_target_properties(external PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libtest1.a)
    
  • The usage is the same as the above so usage

Keywords: JNI NDK

Added by Pazuzu156 on Mon, 07 Feb 2022 13:19:46 +0200