最近负责一个android项目需要使用到之前公司师兄编写的c++算法库,一开始并不知道c++项目可以移植给android项目使用,竟然天真的打算将c++算法库用java进行重写。。。
通过查询大量资料得知可以通过项目移植的方法将c++项目编译成so库后在android项目上进行调用。网上的资料纷繁复杂,大部分回答提出的一种解决方案是通过在android studio上新建一个c++ native项目,通过在cpp目录下创建cpp文件并实现相应函数后通过在native.cpp文件中实现jni接口将相应需要使用到的c++函数在jni接口中暴露出去给相应的android项目使用,文件结构如下图所示。
笔者不推荐这种方法,这种方法实现的前提是c++项目就是由本人所编写,对于其中的内容与结构已经了然于心,否则在将c++项目直接复制到cpp目录下进行编译打包成so库会出现许多头疼的问题,对于初学者非常不友好。
本文介绍的是一种通过visual studio上的跨平台android进行c++项目的移植打包成so库并给android studio调用的方法。
1:c++项目的预处理
请注意,对于你要进行移植的c++项目你要先了解此项目是基于Windows还是linux下进行编写的。由于android是一种基于Linux内核(不包含GNU组件)的自由及开放源代码的操作系统,如果你的c++项目是基于windows下进行编写那么你可能需要对c++源码进行一定量的修改,例如在windows下引入的库需要替换成linux下的库进行使用,否则android ndk编译器将找不到相应的库。
如上图所示,可以通过ifdef宏定义进行条件判断此时项目所运行在的操作系统从而进行不同的处理操作。
如果你的c++项目非常大,建议不要将整个项目进行移植,移植的项目内容越多,出现问题的概率也就越大,后期积重难返。最好是你的android项目需要调用到哪些算法函数就将相应的cpp文件移植进行使用。
2:环境的搭建
本次移植时在visual studio 2019上安装android ndk-16b进行操作。
在visual studio2019上先打开VS installer
选中C++移动开发
如果VS没有下载NDK和SDK的,需要在VS里面配置
3:创建安卓项目
创建好后如下所示:
重定向到你的NDK版本:
配置项目属性:
4:导入项目
具体导入项目方法可以参考这篇文章:导入项目方法,导入后的项目目录如下图所示:
5:链接opencv库以及项目相应的依赖库
如果你的项目中有使用的opencv库,那么你需要重新链接相应的OpenCV库,下载android版本的opencv,我使用的是opencv-3.4.15-android-sdk。
先简单说下Android库的头文件目录和库文件目录的链接导入:
头文件位于:\opencv-3.4.15-android-sdk\OpenCV-android-sdk\sdk\native\jni\include
库文件位于:
\opencv-3.4.15-android-sdk\OpenCV-android-sdk\sdk\native\3rdparty\libs
\opencv-3.4.15-android-sdk\OpenCV-android-sdk\sdk\native\libs
\opencv-3.4.15-android-sdk\OpenCV-android-sdk\sdk\native\staticlibs
上图中第一个附加库目录是我的c++项目需要依赖的库文件libusb,用于调用usb接口进行相应操作,因此也需要将相应库文件添加到附加库目录。这里根据你需要编译的架构类型链接不同版本的库文件,我这里链接的是armeabi-v7a版本,这个版本是比较通用的,2010之后的android设备大部分都是用这个cpu架构。
最后就是添加附加库:
附加依赖项就是链接上述附加库目录中需要使用的.so动态库与.a静态库文件,注意android项目是无法使用windows下的dll以及lib第三方库文件的,必须全部替换为替换为安卓端的.so和.a文件。
添加附加依赖项的命名规范是libopencv_java3.so添加到附加依赖项则修改为-l+去掉lib后的文件名称,无需添加后缀名,静态库的添加同理。
完整的opencv库添加到附加依赖项列表: -lopencv_java4 -lopencv_calib3d -lopencv_core -lopencv_dnn -lopencv_features2d -lopencv_flann -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc -lopencv_ml -lopencv_objdetect -lopencv_photo -lopencv_stitching -lopencv_video -lopencv_videoio -lcpufeatures -lIlmImf -littnotify -llibjasper -llibjpeg-turbo -llibpng -llibprotobuf -llibtiff -llibwebp -lquirc -ltbb -ltegra_hal -lz -ldl -lm -llog
如果你的c++项目有依赖其他的库请记得一定要找到相应版本的依赖库并按上述方法添加到附加库目录以及附加依赖项中,例如我是在项目中用到了libusb库,要编译一份armeabi-v7a版本的c++项目so库,就要在项目中添加了libusb.h后在附加库目录以及附加依赖项中添加amreabi-v7a版本的libusb的so库,这样才是真正完成了库的链接并可以调用其中的函数。
下图修改你要编译的so库版本,我选择的是ARM版本,即armeabi-v7a版本so库。
一开始编译会遇到一些c++设置的问题,因此需要在配置中进行一些修改操作。
编译后控制台出现如上显示就表明编译成功,如果有遇到其他问题也可以发在评论区,或许可以提供一些帮助。
1:导出函数编写
新建的android项目中自动生成了一个cpp文件,其中的JNIEXPORT jfloat JNICALL Java_com_jniexample_JNIInterface_***(JNIEnv env, jclass type, jfloatArray buf) 方法就是要导出给java进行调用的方法。文件内容如下:
#include "opencvTest2.h" #include#include #include #include #include #include #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "opencvTest2", __VA_ARGS__)) #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "opencvTest2", __VA_ARGS__)) using namespace cv; using namespace std; //这里的extern就是将你需要使用到的c++算法中的函数进行导出进行使用。 extern float TestOpencv(float* buf, int len); extern float TestMath(); extern int ScanIDCardMoveToReadPosition(int timeout); extern "C" { /*此简单函数返回平台 ABI,此动态本地库为此平台 ABI 进行编译。*/ const char * opencvTest2::getPlatformABI() { #if defined(__arm__) #if defined(__ARM_ARCH_7A__) #if defined(__ARM_NEON__) #define ABI "armeabi-v7a/NEON" #else #define ABI "armeabi-v7a" #endif #else #define ABI "armeabi" #endif #elif defined(__i386__) #define ABI "x86" #else #define ABI "unknown" #endif LOGI("This dynamic shared library is compiled with ABI: %s", ABI); return "This native library is compiled with ABI: %s" ABI "."; } //C++导出给Java类使用的命名规范 //Java_packagename_classname_functionname //第一个传参总是JNIEnv* env //第二个传参 如果是static成员函数就是jclass type, // 如果是非static成员函数就是jobject thiz, //第三个传参才是真正的参数 JNIEXPORT jfloat JNICALL Java_com_jniexample_JNIInterface_CVTestSum(JNIEnv* env, jclass type, jfloatArray buf) //这个用来导出给Java使用 { auto len = env->GetArrayLength(buf); jboolean notcopy = JNI_FALSE; float* fptr = env->GetFloatArrayElements(buf, ¬copy);//从Java内存转换到native指针 return TestOpencv(fptr, len); } JNIEXPORT jfloat JNICALL Java_com_jniexample_JNIInterface_TestSum(JNIEnv* env, jclass type, jfloatArray buf)//这个用来导出给Java使用 { auto len = env->GetArrayLength(buf); jboolean notcopy = JNI_FALSE; float* fptr = env->GetFloatArrayElements(buf, ¬copy); float sum = 0; for (size_t i = 0; i < len; i++) { sum += fptr[i]; } return sum; } //测试动态库是否可以使用 JNIEXPORT jfloat JNICALL Java_com_jniexample_JNIInterface_TestMath(JNIEnv* env, jclass type)//这个用来导出给Java使用 { return TestMath(); } //ScanIDCardMoveToReadPosition测试c++项目中的函数能否使用 JNIEXPORT jint JNICALL Java_com_jniexample_JNIInterface_ScanIDCardMoveToReadPosition(JNIEnv* env, jclass type, jint timeout)//这个用来导出给Java使用 { int i= ScanIDCardMoveToReadPosition(timeout); return i; } void opencvTest2() { } opencvTest2::opencvTest2() { } opencvTest2::~opencvTest2() { } }
2:android项目中进行导入
在android根目录下创建libs文件夹,并在动态库下创建相应版本的文件夹用于存放下图中的动态库。
修改android跟目录下的build.gradle:
在上图中蓝色的文件夹java中添加一个Java类,请注意创建此java类的包路径以及类名必须要按照步骤1中编写的JNIEXPORT方法中的Java_com_jniexample_JNIInterface名进行创建,否则无法找到so库的导出方法。
这个类用来对接.so中的导出函数: Java_com_jniexample_JNIInterface_CVTestSum和 Java_com_jniexample_JNIInterface_TestSum。
所以,接下来就要创建这两个函数的导出:
在项目中进行使用:
成功调用并输出结果: