JNI笔记
最近项目涉及到JNI开发,因此以下内容做一个总结分享。
为了方便描述,首先新建一个项目,新建的时候带上可选的JNI示例。
首先事先说明下关键的工具与文件
- NDK(Native Development Kit) : 原生开发工具包,即帮助开发原生代码的一系列工具,包括但不限于编译工具、一些公共库、开发IDE等。
- cmake: CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。
- cpp与.h文件: native层的源码文件,与正常的C++文件一样,特殊的在于开放给Java的接口方法,后续会详细介绍
- cmakelist.txt: CMakeLists.txt 用于配置jni项目属性,主要用于声明CMake版本 so库名称 C/Cpp文件路径等信息。
demo目录结构如图:
其中native-lib.cpp就是native的代码文件:
#cmake版本号设置
cmake_minimum_required(VERSION 3.4.1)
#设置C++的编译选项,这里设置C++环境为C++11
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Werror")
#####################################################################
#添加引用的lib,audiobase库
add_library(audiobase
SHARED
IMPORTED)
set_target_properties(audiobase
PROPERTIES IMPORTED_LOCATION
#${CMAKE_SOURCE_DIR}代表当前cmakelist文件的路径,系统会自动获取该文件路径,只需要将引用的动态链接库路径配置好就OK
#这里需要注意的是如果当前引用的so有相应的头文件,需要将头文件路径有所配置,只需要在后续的INCLUDE_DIRECTORIES中即可
${CMAKE_SOURCE_DIR}/../../../musicplayer/libs/armeabi/libaudiobase.so)
######################################################################
#添加引用的lib,andrafp-shared
add_library(andrafp-shared
SHARED
IMPORTED)
set_target_properties(andrafp-shared
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/../../../libs/armeabi/libandrafp-shared.so)
#头文件路径
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include)
######################################################################
#配置库信息,库的名字,动态库或静态库,依赖的源文件
add_library(echo SHARED
#STATIC:静态库,是目标文件的归档文件,在链接其它目标的时候使用。
#SHARED:动态库,会被动态链接,在运行时被加载。
#MODULE:模块库,是不会被链接到其它目标中的插件,但是可能会在运行时使用dlopen-系列的函数动态链接。
audio_common.cpp
audio_main.cpp
audio_player.cpp
udp_send.cpp
data_trans.cpp)
# include libraries needed for echo lib
#添加链接库,相同于指定-l参数
target_link_libraries(echo
android
atomic
log
OpenSLES
audiobase
andrafp-shared)
这个是我们自己的cmakelist,引用的lib较多,但是引用还是很好理解,其中注释有介绍其配置的详细内容以及功能。
以上引入的libaudiobase.so以及libandrafp-shared.so是再native层直接引用,与java层并无交互,若是与java层有交互,可以参考网上很多此类文章。
gradle的相关配置如下:
defaultConfig {
....
ndk {
//平台配置
abiFilters 'armeabi'
}
externalNativeBuild {
cmake {
// 共享库的引用配置
arguments '-DANDROID_TOOLCHAIN=clang',
'-DANDROID_PLATFORM=android-21'
}
}
....
}
externalNativeBuild {
cmake {
path 'src/echo/CMakeLists.txt' //cmakelist文件路径
}
}
其中解释下ABI:ABI(Application binary interface)应用程序二进制接口。不同的CPU 与指令集的每种组合都有定义的 ABI (应用程序二进制接口),一段程序只有遵循这个接口规范才能在该 CPU 上运行,所以同样的程序代码为了兼容多个不同的CPU,需要为不同的 ABI 构建不同的库文件。当然对于CPU来说,不同的架构并不意味着一定互不兼容。
- 如果项目只包含了 armeabi,那么在所有android设备都可以运行;
- 如果项目只包含了 armeabi-v7a,除armeabi架构的设备外都可以运行; (armeabi64-v8a与armeabi-v7a 类似,但是高了一个段位,armeabi64-v8a 是64位,armeabi 与armeabi-v7a 是32位,这三者的关系是向上兼容)
- 如果项目只包含了 x86,那么armeabi架构和armeabi-v7a的Android设备是无法运行的; 如果同时包含了 armeabi,
- armeabi-v7a和x86,所有设备都可以运行,程序在运行的时候去加载不同平台对应的so,这是较为完美的一种解决方案,同时也会导致包变大。
- 以上的内容中介绍了一种native引入so的方式,就是cmakelist中引用。还有一种直接调用dlopen接口引入动态链接库的方法。
代码如下:
//动态链接库路径
#define LIB_CACULATE_PATH "/data/app/com.tencent.karaoketv-1/lib/arm/libandrafp-shared.so"
函数指针
typedef int (*CAC_FUNC)(void **mHowlingDetect);
typedef int (*CAC_FUNC2)(void *mHowlingDetect,int samplerate);
typedef int (*CAC_FUNC3)(void *mHowlingDetect, short *input,int inLen);
/**
* 初始化音频引擎
*/
JNIEXPORT void JNICALL
Java_com_tencent_qqmusic_socket_business_UdpJNIConnection_createSLEngine(
JNIEnv *env, jobject type, jint sampleRate, jint framesPerBuf) {
.....
void *handle;
//打开动态链接库
handle = dlopen(LIB_CACULATE_PATH, RTLD_LAZY);
if (handle) {
MLOGD("dlopen success!!!");
}
CAC_FUNC fun_detect_create = NULL;
CAC_FUNC2 fun_detect_init = NULL;
CAC_FUNC3 fun_detect_begin = NULL;
fun_detect_create = (CAC_FUNC)dlsym(handle, "HowlingDetectCreate_API");
if (fun_detect_create != NULL) {
MLOGD("dlsym1 success!!!");
} else{
MLOGD("dlsym1 failed!!!");
}
fun_detect_init = (CAC_FUNC2)dlsym(handle, "HowlingDetectInit_API");
if (fun_detect_init != NULL) {
MLOGD("dlsym2 success!!!");
}else{
MLOGD("dlsym2 failed!!!");
}
fun_detect_begin = (CAC_FUNC3)dlsym(handle, "HowlingDetectRun_API");
if (fun_detect_begin != NULL) {
MLOGD("dlsym3 success!!!");
}else{
MLOGD("dlsym3 failed!!!");
}
//啸叫检测初始化
// int ret = 1;
void **mHowlingDetect1 = NULL;
fun_detect_create(mHowlingDetect1);
fun_detect_init(&mHowlingDetect1,engine.fastPathSampleRate_);
}
步骤如下:
- 获取需要引入的so路径,因为apk中so在安装之后从apk/lib/下会被复制到/data/app/com.tencent.karaoketv-1/lib/ABI下,ABI代表当前机器使用的CPU平台,通用的是arm路径。
- 定义相应的符号表的指针
- 定义句柄,并调用dlopen打开so库
- 根据方法名获取到相应的符号表指针
- 利用获取到的符号表指针使用对应方法