AS CMake NDK开发

向现有项目添加 C/C++ 代码(官方文档更新版)

官方指南:
Guide:向您的项目添加 C 和 C++ 代码

用 AS 从头创建一个支持jni的项目,参考上面的文档即可;

向现有项目添加 C/C++ 代码依据上面的文档操作会出现一些 Bug,现对文档做些补充:
如果您希望向现有项目添加原生代码,请执行以下步骤:
1. 创建新的原生源文件并将其添加到您的 AS 项目中。
如果您已经拥有原生代码或想要导入预构建的原生库,则可以跳过此步骤。
2. 创建 CMake 构建脚本,将您的原生源代码构建到库中。如果导入和关联预构建库或平台库,您也需要此构建脚本。
如果您的现有原生库已经拥有 CMakeLists.txt 构建脚本或者使用 ndk-build 并包含 Android.mk 构建脚本,则可以跳过此步骤。
3. 提供一个指向您的 CMake 或 ndk-build 脚本文件的路径,将 Gradle 关联到您的原生库。Gradle 使用构建脚本将源代码导入您的 AS 项目并将原生库(SO 文件)打包到 APK 中。

补充:
1)您可以在模块级 build.gradle 文件的 defaultConfig {} 块中配置另一个 externalNativeBuild {} 块,为 CMake 或 ndk-build 指定可选参数和标志。

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

2)您可以在模块级 build.gradle 文件的 android{} 块中配置另一个 externalNativeBuild {} 块,为 CMake 指定 CMakeLists.txt 文件位置。

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

3) 创建Jni调用Jni的类,在类中创建native接口:

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

4)打开 AS 的 Terninal 工具,进入 java 目录,用 javah 编译, 在目录 /cpp/include 中生成 Java 类 HelloJni 对应的 C/C++ 类的头文件:

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

5)创建 Jni 方法文件

/**
 * 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)因为 C/C++ 文件包含其对应的头文件,因此 CmakeLists.txt 文件中一定要规定头文件位置,否则会出现编译不通过

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

7)在调用 Jni 的方法的文件中加载 libnative-lib.so

public class HelloJni {

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

8)可以通过 HelloJni 来电泳 Jni 方法了

用 javap 打印 Jni 方法签名

  1. 首先 rebuild project,生成对应的 build 文件
  2. 打开 AS 的 Terminal,进入 project/app/build/intermediates/classes/debug 文件夹
 cd app/build/intermediates/classes/debug
  1. 执行 javap -s 命令打印类的方法签名
 javap -s com.example.bg235144.myjni.HelloJni

签名规则:
boolean - Z
byte - B
char C
short - S
int - I
long - J
float - F
double - D
void - V
类,
如:java.util.String - Ljava.util.String
数组,
如:int[] - [I,
如:String[] - [java.util.String

AS 配置打印 log

  1. CMakeLists.txt 中配置 log 库
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++文件(native-lib.cpp )配置 log

1)添加 android/log.h 头文件

#include <android/log.h>

2)宏定义打印 log 的方法 LOGI(),其中 native-activity 过滤条件可以自己指定

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

3)在 C/C++ 文件中调用打印 log 的方法 LOGI()

LOGI("from c log");

JNI 调用静态方法

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调用java实例方法

与 JNI 调用静态方法不同,调用实例方法必须通过类的对象;
调用构造函数->通过构造函数创建对象->通过对象修改变量

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);
}

NDK常见的异常

dos:
查看AS日志

adb logcat

符号表:内存地址与函数名、文件名、行号的映射表。
obj下面的.so包含符号表
用ndk-stack(-sym/-dump)命令就可以反编译带符号表的.so文件,得到Crash信息

将log日志打印到Dos:

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

例如:

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

将log日志打印到文件:

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

例如:

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 调用 Java 层代码可能会导致异常,这时 Java 层到异常处不再执行,而 Jni 会一直执行完毕,会导致因为 Java 层的异常基础上后面执行的 Jni 出现其它异常
处理方法:在 Jni 调用 Java 层代码之后进行 Java 层异常判断,如果 Java 层出现异常,打印出异常信息之后 return, Jni 层代码就不会再执行下去

检测 Java 层是否有异常:
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;
}

点击下载源码

引申阅读:

Jni 官方文档
JNI完全手册
cmake-tutorial
CMake 入门实战

猜你喜欢

转载自blog.csdn.net/amoscxy/article/details/78962505