向现有项目添加 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 方法签名
- 首先 rebuild project,生成对应的 build 文件
- 打开 AS 的 Terminal,进入 project/app/build/intermediates/classes/debug 文件夹
cd app/build/intermediates/classes/debug
- 执行 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
- 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 )
- 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;
}