Android JNI 调用流程
为啥要用JNI,我个人理解是,Java 代码效率不够高,代码调用底层逻辑隔着一层Java 虚拟机,不能直接操控底层硬件,而C/C++ 可以直接操控硬件设备,对于需要效率更高的操作,就需要通过C/C++ 完成。。
比如说我们公司做的一个项目,VR眼镜,连接主机设备,VR需要上报数据给主机,并且显示出来。眼镜转动很快,,如果数据获取慢,那主机显示很延时很久,所以这里java 就不合适了。
一、项目结构
先简单介绍下目录结构,如果想自己本地调试打开android studio File->New->New Project 选择 native C++ 项目,系统会为我们创建一个demo 。
新建完成build.gradle会指定cmakelist,该文件会用于编译native 库,一般不建议更改
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
然后就是natIve 库的存放位置,系统会新增一个cpp 文件夹,不建议自己修改路径。
这些都是系统自动编译的,我们需要做的是加载native 库,然后调用就可以了
static {
System.loadLibrary("myapplication");
}
public native String stringFromJNI();
流程就是这样,下面介绍下具体实现。
二、CMakelist 参数意义
这个是常用的方式,
一,配置native 库
project("myapplication")
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
native-lib.cpp)
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
android
log)
系统自动生成几个字段,挨个解释下。
//native 库名称,可自己修改,java 端加载需要根据这个来。
project("myapplication")
//这个简单来说就是库文件中,包含的代码。
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
native-lib.cpp)
//简单来说就是native 库中包含哪些代码,如果有多个直接换行就可以了
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
native-lib.cpp
xxxxx.cpp
yyyy.cpp
)
也可以设置目录
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SRC_LIST) add_library(${CMAKE_PROJECT_NAME} SHARED ${SRC_LIST})
//链接一些Android 三方 关联到改native 库中,比如说需要使用到一些android Log 库
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
android
log)
二,链接第三方so
上面说的,都是加载自己的c++ 代码和Android 原生库,那么怎么加载三方so呢。
//设置三方库的路径 set(libs ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs) //别名 add_library(DRIVER_SO SHARED IMPORTED ) //指定头文件位置 include_directories(${CMAKE_CURRENT_SOURCE_DIR}) //目标库 set_target_properties( DRIVER_SO PROPERTIES IMPORTED_LOCATION ${libs}/xxxx.so
//查找原生库
find_library( # Sets the name of the path variable. android-lib # Specifies the name of the NDK library that # you want CMake to locate. android)
//链接到目标库
target_link_libraries( ${CMAKE_PROJECT_NAME} DRIVER_SO android log )
目前用到的就是这些,其他的使用时可以去查询。
三 、调用方式
上面的都是编译语法,下面才是我们的核心逻辑,demo 里面 也已经给了方式。
注意JNI 方法名 java_包名_类名_函数名
Java_com_and_myapplication_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }
首先需要注意JNI的类型
如果回调 Java 方法,也很简单。
/**
找到对应的class
*/
jclass mImuCallBackJ = jniEnv->FindClass(JAVA_CALL_BACK);
jclass strClass = jniEnv->FindClass("java/lang/String");
jmethodID ctorID = jniEnv->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
jbyteArray bytes = jniEnv->NewByteArray(strlen(data));
jniEnv->SetByteArrayRegion(bytes, 0, strlen(data), (jbyte*)data);
jstring encoding = jniEnv->NewStringUTF("utf-8");
jstring callData = (jstring)jniEnv->NewObject(strClass, ctorID, bytes, encoding);
/**
找到对应的method
*/
jmethodID msendImuData = jniEnv->GetStaticMethodID(mImuCallBack, "onIMUEvent", "(Ljava/lang/String;)V");
/**
调用函数
*/
jniEnv->CallStaticVoidMethod(mImuCallBackJ, msendImuData,callData);
上面是基本用法,以下几点需要注意。
1,返回类型和参数,基本类型没啥好说的,主要是复合类型,最后的分号一定要记住“;”,不然会找不到对应的方法。
2,字符串 String类,找的是“java/lang/String” 而不是"Ljava/lang/String",这个是针对返回值和参数的,用错了一些莫名奇妙的问题就产生了。
jclass objClass = env->FindClass("java/lang/String");
3,so 如何调用java
我们知道想调用java ,必须要通过JVM 。so库里面如何获取呢。
C++ 代码通过重写下面代码的方式获取JVM ,JNI 加载so 的时候,自动执行,有JVM 了,我们就可以正常回调java 方法 了
JavaVM* javaVM;
int jni_version = JNI_VERSION_1_4;
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
LOG_D("JNI_OnLoad %d", load_count++);
javaVM = vm;
return JNI_VERSION_1_4;
}
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) {
LOG_D("JNI_OnUnload %d", unload_count++);
}
参考
Android Jni GetMethodID中函数标识的简单解释-CSDN博客
Android-JNI开发系列《八》CMakeLists.txt语法&使用_jni cmakelist-CSDN博客