android使用CMake進行jni編寫遇到的一些問題
阿新 • • 發佈:2019-01-28
前言
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庫如果你是放在jniLibs資料夾sourceSets.main { jniLibs.srcDirs = ['libs'] jni.srcDirs = [] }
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
這個錯誤的意思是你生成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庫的依賴。2、還是依賴第三方庫的問題,如果你出現了missing and no known rule to make it這個error,但是按照上面的方法並沒有解決,如下錯誤
看起來,好像跟上面的錯誤很相似,也是在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 “某某函式”,但是又不是上面的那個原因。我們的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的依賴第三個庫和引入標頭檔案都沒有問題的話,記得檢查一下是否是編譯器的宣告問題。