Android NDK不得不说的秘密

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

步骤如下:

  1. 获取需要引入的so路径,因为apk中so在安装之后从apk/lib/下会被复制到/data/app/com.tencent.karaoketv-1/lib/ABI下,ABI代表当前机器使用的CPU平台,通用的是arm路径。
  2. 定义相应的符号表的指针
  3. 定义句柄,并调用dlopen打开so库
  4. 根据方法名获取到相应的符号表指针
  5. 利用获取到的符号表指针使用对应方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值