使用flutter开发移动端项目时,接入跨平台的c++ sdk是很常见的。
一般我们会在项目里新建一个插件来封装引入的sdk,里面定义对应的数据结构、调用逻辑,对外暴露出dart接口供项目使用。
1、创建原生插件
flutter create --template=plugin hello
2、引入sdk
虽然sdk代码是c++写的,但是不同平台的硬件资源、底层的api基本上是不同的,所以一般是android一个so文件,ios一个framework,需要分别操作。
2.1、android平台引入
以opencv为例,从opencv官网下载解压后,放到hello/android/src/main/jniLibs/(需要用的arm架构 如:armeabi-v7a)/libopencv_java4.so。
添加CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(hello)
#头文件路径
include_directories(../include)
#引入多个so文件
add_library(lib_opencv SHARED IMPORTED)
add_library(lib_xxx SHARED IMPORTED)
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libopencv_java4.so)
set_target_properties(lib_xxx PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libXxx.so)
# find_library(log-lib log)
#里面封装调用sdk的cpp文件
add_library(hell SHARED ../ios/Classes/hello.cpp)
target_link_libraries(
hello
lib_opencv
lib_xxx
GLESv2
EGL
log
)
修改build.gradle
android {
...
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
main.jniLibs.srcDirs = ["libs"]
}
defaultConfig {
minSdkVersion 21
externalNativeBuild {
cmake {
cppFlags '-frtti -fexceptions -std=c++11'
arguments "-DANDROID_STL=c++_shared"
}
}
ndk {
abiFilters 'armeabi-v7a'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
...
}
2.2、ios平台引入
ios平台其实是以模块私有化的形式来处理,把sdk放到hello/ios/(opencv2.framework 、xxx.framework)。
修改hello.podspec
# telling CocoaPods not to remove framework
s.preserve_paths = 'opencv2.framework', 'xxx.framework'
# telling linker to include opencv2 framework
s.xcconfig = {
'OTHER_LDFLAGS' => '-framework opencv2 -framework xxx',
}
# including OpenCV framework
s.vendored_frameworks = 'opencv2.framework', 'xxx.framework'
# s.vendored_libraries = 'path/name.a'
s.frameworks = 'AVFoundation'
s.library = 'c++'
新建hello.cpp
#include <opencv2/opencv.hpp>
#include <chrono>
#include <iostream>
#if defined(__ANDROID__)
#include <android/log.h>
#include "xxx.h"
#endif
#if defined(TARGET_OS_IPHONE)
#include <xxx/xxx.h>
#endif
#if defined(__GNUC__)
// Attributes to prevent 'unused' function from being removed and to make it visible
#define FUNCTION_ATTRIBUTE __attribute__((visibility("default"))) __attribute__((used))
#elif defined(_MSC_VER)
// Marking a function for export
#define FUNCTION_ATTRIBUTE __declspec(dllexport)
#endif
using namespace std;
using namespace cv;
long long int get_now() {
return chrono::duration_cast<std::chrono::milliseconds>(
chrono::system_clock::now().time_since_epoch()
).count();
}
void platform_log(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
#ifdef __ANDROID__
__android_log_vprint(ANDROID_LOG_VERBOSE, "ndk", fmt, args);
#else
vprintf(fmt, args);
#endif
va_end(args);
}
//对应sdk里的数据结构
typedef struct MyPoint
{
float x;
float y;
}MyPoint;
//对应sdk里的数据结构
typedef struct MySize
{
float width;
float height;
}MySize;
extern "C" {
FUNCTION_ATTRIBUTE
const char* version() {
std::cout << "version func" << std::endl;
return CV_VERSION;
}
FUNCTION_ATTRIBUTE
void test(int *a) {
std::cout << a << std::endl;
}
FUNCTION_ATTRIBUTE
void process_image(char* inputImagePath, char* outputImagePath) {
long long start = get_now();
Mat input = imread(inputImagePath, IMREAD_GRAYSCALE);
Mat threshed, withContours;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
adaptiveThreshold(input, threshed, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 77, 6);
findContours(threshed, contours, hierarchy, RETR_TREE, CHAIN_APPROX_TC89_L1);
cvtColor(threshed, withContours, COLOR_GRAY2BGR);
drawContours(withContours, contours, -1, Scalar(0, 255, 0), 4);
imwrite(outputImagePath, withContours);
int evalInMillis = static_cast<int>(get_now() - start);
std::cout << "Processing done in" << evalInMillis << std::endl;
}
FUNCTION_ATTRIBUTE
void bytes2Mat(uint8_t* imgData, int h, int w, int channels, cv::Mat *mat){
if (channels == 1)
{
*mat = cv::Mat(h, w, CV_8UC1, imgData);
}
else if (channels == 3)
{
*mat = cv::Mat(h, w, CV_8UC3, imgData);
}
else if (channels == 4)
{
*mat = cv::Mat(h, w, CV_8UC4, imgData);
}
}
....
....
}
修改hello/lib/hello.dart
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
//-----对应的结构体
class MyPoint_dart extends Struct {
@Float()
external double x;
@Float()
external double y;
}
class MySize_dart extends Struct {
@Float()
external double width;
@Float()
external double height;
}
//如果用指针,对应的c内存就要自己管理
//-----------
final DynamicLibrary helloLib = Platform.isAndroid
? DynamicLibrary.open("libHello.so")
: DynamicLibrary.process();
//-----
//c 方法签名
typedef _CVersionFunc = Pointer<Utf8> Function();
typedef _CProcessImageFunc = Void Function(Pointer<Utf8>, Pointer<Utf8>);
typedef _CTestFunc = Void Function(Pointer<Int32>);
//dart 方法签名
typedef _VersionFunc = Pointer<Utf8> Function();
typedef _ProcessImageFunc = void Function(Pointer<Utf8>, Pointer<Utf8>);
typedef _TestFunc = void Function(Pointer<Int32>);
//找对应的方法
final _VersionFunc _version = helloLib
.lookup<NativeFunction<_CVersionFunc>>("version").asFunction();
final _ProcessImageFunc _processImage = helloLib
.lookup<NativeFunction<_CProcessImageFunc>>("process_image").asFunction();
final _TestFunc test = helloLib
.lookup<NativeFunction<_CTestFunc>>("test").asFunction();
//-------
class ImageArguments {
final String inPath;
final String outPath;
ImageArguments(this.inPath, this.outPath);
}
void ProcessImage(ImageArguments args) {
_processImage(args.inPath.toNativeUtf8(), args.outPath.toNativeUtf8());
}
...
...
项目中引入对应的插件
#pubspec.yaml
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
...
hello:
path: ./hello
...
到这里,基本的配置已经完成,接下来就是引入插件里的dart文件常规使用。
2.3、引入类似sdk的配置json等资源
在ios里最简单的就是直接放到项目里,编入到ipa文件里,然后根据mainbundle路径去加载。
针对android,踩的坑比较多,比较合理的办法是先把资源文件放到'项目'/android/app/src/main/assets/xxx.json,然后在项目的pubspec.yaml中引入这些资源。
#pubspec.yaml
...
assets:
- android/app/src/main/assets/
...
在使用的时候还是先从’android/app/src/main/assets/***‘加载对应的资源数据,写到外部存储目录后再使用。
c++ sdk接入过程对我来说并不流畅,特此记录下,希望能帮助有需要的人。
网友评论