android使用CMake进行jni编写遇到的一些问题

前言

          android studio 2.2之后出的CMake 让jni的编写方便了很多,使用CMake让我们不在烦恼函数的定义,以前我们需要通过javah命令生成,jni规定的函数名,现在不需要了。他也让我们可以很方便的编写c/c++代码,自动打成so。总体来说,让我们的jni编写变得更简单。但是网上关于CMake的使用翻来覆去也就是官网的那些。所以我就记录一下自己在使用CMake进行jni编译过程中遇到的问题。
      CMake的使用请见android中使用CMake,这里就不讲使用了,官方的说明里讲的很清楚,我这里就说一下常见问题的出现原因。

      1、如果我们想要在自己的c/c++代码中使用一些第三方库的函数,比如ffmpeg。我们可以通过add_library来添加相关依赖(官网教程里面有详细说明)。但是我们打包好的ffmpeg的so库
   <Image_1>
      应该放在哪里呢? 通常存放so库,我们是放在这两个位置的,那么到底应该放哪里呢?其实两个都是可以的,只不过你需要在gradle和CMakeLists.txt里面说明
    <Image_2>
     不然有可能会出现以下问题
      A、编译能通过,但是安装的时候会报 java.lang.UnSatisfiedLinkError : dlopen failed : library
这种情况,你需要在build.gradle里面添加如下代码
如果你是放在libs文件夹
    sourceSets.main {
        jniLibs.srcDirs = ['libs']
        jni.srcDirs = []
    }
             如果你是放在jniLibs文件夹
    sourceSets.main {
        jniLibs.srcDirs = ['src/main/jniLibs']
        jni.srcDirs = []
    }

        B、编译的时候不通过,报错 error: xxx.so,needed by xxxx.so,missing and no known rule to make it
<Image_3>
       这个错误的意思是你生成 xxxx.so的时候,需要xxx.so库,但是没有找到,其实在这里就是路径的问题,在CMake的使用中,可以通过add_library依赖第三方库
    add_library(avcodec SHARED IMPORTED)
    set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION  ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libavcodec-57.so)
       上面代码avcodec 其实就是依赖库的名字,可以随便取,但是下面set_target_properties的第一个参数一定要和上面统一,具体的参数含义就不在多说了,后面的
    ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libavcodec-57.so
     就是你依赖的so库的存放路径,${CMAKE_SOURCE_DIR} 其实就是CMakeLists.txt文件所在文件夹,${ANDROID_ABI}其实就是你编译的手机的cpu架构,比如armeabi、X86、mips64等等,编译的时候,它会自动去找libs的对应文件夹,如果找不到,就会报这个错误。现在大部分手机都是armeabi架构,模拟器是x86架构,所以如果出现这个错误,需要检查一下自己so库是否是对应版本,以及是否存放在了对应文件夹下。
      如果放在libs下面,就需要如下写
    set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION  ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libavcodec-57.so)
      对应如果在jniLibs下面,就如下
set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavcodec-57.so)
      如果出现这个错误,记得多检查几次,自己的路径以及文件名是否写的正确的。

     上面说了avcodec其实只是你自定义的一个库的名字,但是你的add_library和set_target_properties以及进行link的target_link_libraries里面一定要保持一致如果我们在 target_link_libraries的时候写错了,比如依赖库。
   add_library(avcodec SHARED IMPORTED)
   set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION  ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavcodec-57.so)
      link库,这里我们故意少写了一个c,
    target_link_libraries( # Specifies the target library.
                       native-lib
                        avcode                    
    }
      如果在我们自己的c文件里面用到了libavcodec-57.so里面的函数,那么会出现编译不通过  undefined reference to 'xxxxxx',这是我们在c文件用到了某个so库里面的函数,但是并没有进行对应so库的依赖。
<Image_4>

      2、还是依赖第三方库的问题,如果你出现了missing and no known rule to make it这个error,但是按照上面的方法并没有解决,如下错误
<Image_5>
         看起来,好像跟上面的错误很相似,也是在build  我们自己的libnative-lib.so这个库的时候,他需要依赖第三方库,但是没依赖成功,但是其实注意红色箭头指示的点,mips64??为什么会出现这个文件夹??其实使用jni编译的时候,在他讲我们的c文件打包成so库的时候,如果我们没有指定打包成哪种架构的so库,他默认是会进行所有的打包的(个人猜测),也就是在打mips64这个架构的so库的时候,他去对应地方找依赖的so库,发现没有找到,就会报上面的错误,这个时候,如果我们 只支持armeabi架构,那么需要在build.gradle文件defaultConfig中添加如下代码
    externalNativeBuild {
        cmake {
             cppFlags ""
             abiFilters 'armeabi'//, 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
        }
    }
        其中abiFilters就指定了需要打哪种架构的so库,

      3、同样是依赖第三方库,编译的时候,发现找不到使用的第三方库里面的某个函数,也就是undefined reference to “某某函数”,但是又不是上面的那个原因。
<Image_6>
       我们的cpp代码如下
#include 
#include 
#include 
#include 
extern "C"
{
    jstring
    Java_com_example_lenovo_ffmpegdemo_FFmpegUtils_stringFromFF(JNIEnv  *env, jobject ) {
        char info[10000] = { 0 };
        av_register_all();
        sprintf(info, "%s\n", avcodec_configuration());
        return env -> NewStringUTF(info);
    }
    jstring
    Java_com_example_lenovo_ffmpegdemo_FFmpegUtils_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
}
      看上去没有什么问题。 我们使用了libavformat.so里面的函数av_regist_all(),但是缺报了函数未定义的错误,然而我们明明已经依赖了第三方的so库和相关头文件,这里有一个需要注意的地方就是,我们引入的这个第三方库是ffmpeg,该库需要用c编译器来编译,所以有一个
    extern "C"
      但是,这里include的位置不对,我们需要放在extern "C"的代码块里面,经过如下修改
#include 
#include 
extern "C"
{
#include 
#include 
    jstring
    Java_com_example_lenovo_ffmpegdemo_FFmpegUtils_stringFromFF(JNIEnv  *env, jobject ) {
        char info[10000] = { 0 };
        av_register_all();
        sprintf(info, "%s\n", avcodec_configuration());
        return env -> NewStringUTF(info);
    }
    jstring
    Java_com_example_lenovo_ffmpegdemo_FFmpegUtils_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
     就发现编译通过了,可以正常运行了(这是非常坑的一个点)。所以如果出现了这样的报错,而CMake的依赖第三个库和引入头文件都没有问题的话,记得检查一下是否是编译器的声明问题。




你可能感兴趣的:(android疑难杂症,android,cmake,jni,错误)