使用JNI 呼叫第三方c++動態庫
昨天晚上到今天一直在折騰這個第三方庫檔案,唉,要哭了,一直就是各種問題。現在詳細說說怎麼做,踩過哪些坑。
現有個第三方的C++動態庫(libModel.so),這個libModel.so是要能被android呼叫的arm庫啊,需要在android中,使用java直接呼叫,那麼一般是兩種方式:
1:libModel.so 符合JNI規範,能夠直接在java層呼叫
2:libModel.so不符合規範,只是普通的c++動態庫,那麼只能是在JNI,寫個c/c++函式,呼叫這個libModel.so庫裡面的函式,然後重新編譯為libhello.so庫,android呼叫這個libModel.so;其實就像給libModel.so再封裝一層。
由於libModel.so不符合JNI規範,我只能採用第二種方式。
首先,建立一個HelloTest的android程式,然後在HelloTest中新建一個jni檔案,在jni檔案中仔建立一個prebuilt資料夾,裡面存放libmodel.so,並且新建一個Android.mk。
Android.mk的內容如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := model
LOCAL_SRC_FILES := libModel.so
include $(PREBUILT_SHARED_LIBRARY )
然後在jni裡面新建Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello
LOCAL_SRC_FILES := hello.cpp
LOCAL_LDLIBS := -llog
LOCAL_SHARED_LIBRARIES := model
LOCAL_ALLOW_UNDEFINED_SYMBOLS := true //避免出現undefined的錯誤
include $(BUILD_SHARED_LIBRARY)
include $(LOCAL_PATH)/prebuilt/Android.mk //包含prebuilt下的Android.mk
因為我jni裡面的具體函式使用到了,所以需要新增一個Application.mk,Application.mk內容如下:
APP_STL := stlport_static
在這裡也遇到了坑,一定要確保你是.cpp檔案,不然Application.mk沒有作用,依然會報:
error :can not find iostream的錯誤,因為這個stlport_static是供c++使用的。
在APK執行時,報瞭如下的錯誤,無法載入庫,那是因為apk沒有找到庫檔案:
01-02 03:50:05.655: E/AndroidRuntime(32314): Caused by: java.lang.UnsatisfiedLinkError: Cannot load library: reloc_library[1285]: 2824 cannot locate '_ZN13CVQASSESSMENTC1Ev'...
我之前嘗試使用如下方式進行打包,不建立prebuilt資料夾 ,直接在jni的Android.mk,加入libModel.so庫,:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libModel
LOCAL_SRC_FILES := libModel.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := modeljni
LOCAL_SRC_FILES := modeTest
LOCAL_SHARED_LIBRARY := libModel
include $(BUILD_SHARED_LIBRARY)
以上面的方式打包,在apk執行的時候,就找不到庫,我也沒有想明白為什麼,還是我的打包有問題,用第一種方式執行成功。
還有在java端呼叫庫的時候,兩個庫都要包含進去,且順序是先libModel.so,在呼叫新生成的。
static
{
System.loadLibrary("Model");
System.loadLibrary("hello");
}
其次就是c++庫需要傳遞結構體引數,我是這樣做的,在JAVA定義一個包含結構體所需引數的類,然後呢,java向jni中的C++ 傳遞 一個物件,c++解析java的物件,再重構第三方庫需要的結構體引數, java 與c 傳參 部落格寫的挺好的,具體的實現是:
首先在java申明native 方法:
public native int getInput(SideInfo sideInfo);
然後,使用javah 生成標頭檔案,將標頭檔案拷貝至jni中, jni中c++具體的實現如下:
//java 向c 傳遞物件
jint Java_com_example_hellotest_MainActivity_getInput
(JNIEnv * env, jobject jthis, jobject sideInfo){
CVQASSESSMENT VqAssessUnit;
jmethodID methodId;
//獲得sideInfo物件的控制代碼
jclass cls_objClass=env->GetObjectClass(sideInfo);
//獲得sideInput物件中特定方法getI_Audio_sample_rate的id
methodId=env->GetMethodID(cls_objClass,"getI_Audio_sample_rate","()I");//第三個是方法的簽名,可以通過 javap -s XX.class的方式檢視方法的簽名。
//呼叫sideInfo物件的特定方法getI_Audio_sample_rate
jint jrate=(jint)env->CallIntMethod(sideInfo,methodId,NULL); //獲取不同的值,有不同的CallXXMethod方法。
return jrate;
}
通過這樣的方法,就可以向c++ 傳遞java物件了。