1. 程式人生 > >android使用CMake進行jni編寫遇到的一些問題

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 <jni.h>
#include <string>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
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 <jni.h>
#include <string>
extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
    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的依賴第三個庫和引入標頭檔案都沒有問題的話,記得檢查一下是否是編譯器的宣告問題。