AS CMake NDK development

Add C/C++ code to an existing project (updated official documentation)

Official guide:
Guide: Add C and C++ Code to Your Project

Use AS to create a project that supports jni from scratch, just refer to the above documentation;

Adding C/C++ code to an existing project According to the above documentation, there will be some bugs, and now to make some supplements to the documentation:
If you want to add native code to an existing project, please perform the following steps:
1. Create a new native source file and add it to your AS project.
You can skip this step if you already have native code or want to import a prebuilt native library.
2. Create a CMake build script to build your native source code into the library. You also need this build script if importing and linking a prebuilt library or platform library.
You can skip this step if your existing native library already has a CMakeLists.txt build script or uses ndk-build and includes an Android.mk build script.
3. Provide a path to your CMake or ndk-build script file to link Gradle to your native library . Gradle uses build scripts to import source code into your AS project and package native libraries (SO files) into APKs.

Added:
1) You can configure another externalNativeBuild {} block in the defaultConfig {} block of the module-level build.gradle file to specify optional parameters and flags for CMake or ndk-build.

externalNativeBuild {
    cmake {
        cppFlags "-frtti -fexceptions"
    }
}

2) You can configure another externalNativeBuild {} block in the android{} block of the module-level build.gradle file to specify the CMakeLists.txt file location for CMake.

externalNativeBuild {
    cmake {
        path 'CMakeLists.txt'
    }
}

3) Create a class where Jni calls Jni, and create a native interface in the class:

public class HelloJni {
    public static native String sayJni();
}

4) Open the Terninal tool of AS, enter the java directory, compile with javah, and generate the header file of the C/C++ class corresponding to the Java class HelloJni in the directory /cpp/include:

/> cd app/src/main/java
/> javah -d ../cpp/include com.example.bg235144.myjni.HelloJni

5) Create Jni method file

/**
 * C/C++ 类头文件传入 native-lib.cpp 中
 */
#include "include/com_example_bg235144_myjni_HelloJni.h"
#include <jni.h>
#include <iostream>

/**
 * 拷贝头文件中的自动生成的声明,在其对应的方法中进行 jni 操作
 * @param env 
 * @return 
 */
JNIEXPORT jstring JNICALL Java_com_example_bg235144_myjni_HelloJni_sayJni
        (JNIEnv *env, jclass){
    std::string str = "Hello jni";
    return env->NewStringUTF(str.c_str());
}

6) Because the C/C++ file contains its corresponding header file, the location of the header file must be specified in the CmakeLists.txt file, otherwise the compilation will fail

# Specifies a path to native header files.
include_directories(src/main/cpp/include/)

7) Load libnative-lib.so in the file that calls the Jni method

public class HelloJni {

    static {
        System.loadLibrary("native-lib");
    }
    ...
}

8) The Jni method can be electrophoresed through HelloJni

Printing Jni method signatures with javap

  1. First rebuild the project and generate the corresponding build file
  2. Open the Terminal of AS and enter the project/app/build/intermediates/classes/debug folder
 cd app/build/intermediates/classes/debug
  1. Execute the javap -s command to print the method signature of the class
 javap -s com.example.bg235144.myjni.HelloJni

Signature rules:
boolean - Z
byte - B
char C
short - S
int - I
long - J
float - F
double - D
void - V
class,
such as: java.util.String - Ljava.util.String
array,
such as: int[ ] - [I,
eg: String[] - [java.util.String

AS configuration print log

  1. Configure log library in CMakeLists.txt
find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )
  1. C/C++ file (native-lib.cpp ) configuration log

1) Add the android/log.h header file

#include <android/log.h>

2) The macro defines the method LOGI() to print the log, in which the native-activity filter condition can be specified by itself

#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "native-activity", __VA_ARGS__))

3) Call the method LOGI() to print the log in the C/C++ file

LOGI("from c log");

JNI calls static methods

JNIEXPORT void JNICALL Java_com_example_bg235144_myjni_HelloJni_callStaticMethod__I
        (JNIEnv *env, jclass jclass1, jint jint1){
    /**
     * Jni 调用 Java 层的方法,修改 Java 层变量
     */
    // 找到对应的类
    jclass cls_hello = env->FindClass("com.example.bg235144.myjni.HelloJni");
    if(cls_hello==NULL){
        LOGI("cls_hello = null");
        return;
    }
    // 找到对应的方法
    jmethodID mtd_static_method = env->GetStaticMethodID(cls_hello,"staticMethod","(Ljava/lang/String;)V");
    if(mtd_static_method==NULL){
        LOGI("mtd_static_method = null");
        return;
    }

    // 找到对应的变量
    jfieldID fld_name = env->GetStaticFieldID(cls_hello,"name","Ljava/lang/String;");
    if(fld_name==NULL){
        return;
    }
    // 修改 Java 层变量
    jstring name = env->NewStringUTF("Jni native");
    env->SetStaticObjectField(cls_hello,fld_name,name);

    // 调用 Java 层的方法
    jstring data = env->NewStringUTF("call java static method");
    if(data==NULL){
        LOGI("data = null");
        return;
    }
    env->CallStaticVoidMethod(cls_hello,mtd_static_method,data);

    // 删除引用
    env->DeleteLocalRef(cls_hello);
    env->DeleteLocalRef(data);
    env->DeleteLocalRef(name);
}

JNI calls java instance method

Unlike JNI calling static methods, calling instance methods must pass the object of the class;
call the constructor -> create an object through the constructor -> modify variables through the object

JNIEXPORT void JNICALL Java_com_example_bg235144_myjni_HelloJni_callInstanceMethod__I
        (JNIEnv *env, jobject jobject1, jint jint1){
    /**
     * Jni 调用 Java 层实例方法,调用 Java 层实例变量
     */
    // 找到对应的类
    jclass cls_hello_jni = env->FindClass("com.example.bg235144.myjni.HelloJni");
    if(cls_hello_jni==NULL){
        return;
    }
    // 找到要调用的方法
    jmethodID mtd_method = env->GetMethodID(cls_hello_jni,"method","(Ljava/lang/String;)V");
    if(mtd_method==NULL){
        return;
    }
    // 调用 Java 层实例方法
    // 找到对应的构造方法
    jmethodID mtd_construct = env->GetMethodID(cls_hello_jni,"<init>","()V");
    if(mtd_construct==NULL){
        return;
    }
    // 创建相应的对象
    jobject helloJni = env->NewObject(cls_hello_jni,mtd_construct,NULL);
    if(helloJni==NULL){
        return;
    }
    // 获取相应的变量
    jfieldID fld_address = env->GetFieldID(cls_hello_jni,"address","Ljava/lang/String;");
    if(fld_address==NULL){
        return;
    }
    jstring jst_address = env->NewStringUTF("Jni hangzhou");
    if(jst_address==NULL){
        return;
    }
    // 修改变量
    env->SetObjectField(helloJni,fld_address,jst_address);
    // 调用 Java 层方法
    jstring message = env->NewStringUTF("call instance mothod");
    if(message==NULL){
        return;
    }
    env->CallVoidMethod(helloJni,mtd_method,message);
    // 删除引用
    env->DeleteLocalRef(cls_hello_jni);
    env->DeleteLocalRef(helloJni);
    env->DeleteLocalRef(message);
    env->DeleteLocalRef(jst_address);
}

Common exceptions in NDK

dos:
View AS log Symbol table: the mapping table of memory addresses and function names, file names, and line numbers. The .so under obj contains the symbol table Use the ndk-stack (-sym/-dump) command to decompile the .so file with the symbol table to get the Crash information

adb logcat



Print the log log to Dos:

adb logcat | $NDK/ndk-stack -sym $PROJECT_PATH/obj/local/armeabi

E.g:

adb logcat|nak-stack -sym D:\Cloudbackup\workspace\android_dem
o\MyJni\app\build\intermediates\cmake\debug\obj\arm64-v8a

Print the log log to a file:

adb logcat | $NDK/ndk-stack -sym $PROJECT_PATH/obj/local/armeabi > 文件路径

E.g:

adb logcat|ndk-stack -sym D:\Cloudbackup\workspace\android_dem
o\MyJni\app\build\intermediates\cmake\debug\obj\arm64-v8a > D:\Cloudbackup\works
pace\android_demo\MyJni\log.txt

Jni calling the Java layer code may cause an exception. At this time, the Java layer will not execute until the exception, and the Jni will be executed all the time, which will cause other exceptions in the Jni executed later on the basis of the Java layer exception.
Processing method: call in Jni After the Java layer code, the Java layer exception judgment is performed. If an exception occurs in the Java layer, and the exception information is printed out and return, the Jni layer code will not be executed.

Check for exceptions at the Java layer:
env->ExceptionOccurred() / env->ExceptionCheck()

if(env->ExceptionCheck()){
    env->ExceptionDescribe();
    env->ExceptionClear();
    jclass cls_exception = env->FindClass("java/lang/Exception");
    env->ThrowNew(cls_exception,"call java static method ndk error");
    env->DeleteLocalRef(cls_exception);
    return;
}

Click to download the source code

Extended reading:

Jni official document
JNI complete manual
cmake-tutorial
CMake entry combat

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326399031&siteId=291194637