一、背景
虽然flutter官方的插件有很多,但是有一部分是比较稀缺的。
例如我们在进行hid设备通信时,发现官方的hid包已经两年没更新了,而flutter的迭代速度很快,基于此我们需要自己开发一个插件。
1.1 是否支持
通过查阅flutter官方文档,我们发现flutter官方提供了一个dart:ffi(Foreign Function Interface: 外部功能接口)库来调用本地的 C API。
二、集成源码
我推荐使用方式二集成c源码,它不需要我们人为的去编写转换文件。
2.1 方法一
生成一个flutter的plugin,然后在这个plugin里对应平台(ios/android)目录下添加C源代码,并根据平台指定的方式进行编译并链接到最终的程序中。
2.1.1 生成plugin
输入以下命令
#其中platplatforms是支持的平台,每个平台会生成对应的目录和相关配置文件等,template是指创建flutter项目的类型,这里我们选择plugin即插件的形式。 flutter create --platforms=android,ios --template=plugin native_add |
2.1.2 添加C/C++源码
作为示例,我们在ios目录下的Classes路径下添加一个native_add.cpp文件(CocoaPods 不允许源码处于比 podspec 文件更高的目录层级,但是 Gradle 允许你指向 ios 文件夹,所以我们偏向于将源代码放到ios目录下)
native_add.cpp 内代码如下:
#include <stdint.h>
extern "C" __attribute__((visibility("default"))) __attribute__((used))
int32_t native_add(int32_t x, int32_t y) {
return x + y;
}
|
一个实现 32 位的加法 C 函数
备注:
FFI库只能与C符号绑定,因此在C++中,这些符号添加 extern C 标记。还应该添加属性来表明符合是需要被Dart引用的,以防止链接器在优化链接时会丢弃符号。
android编译示例
针对android平台的话,你需要创建一个 CMakeLists.txt 文件用来定义如何编译源文件,同时告诉 Gradle 如何去定位它们。
cmake_minimum_required(VERSION 3.4.1) # for example
add_library( native_add
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
../ios/Classes/native_add.cpp )
|
最后,添加一个 externalNativeBuild 到你的 android/build.gradle 文件中。示例如下:
android {
// ...
externalNativeBuild {
// Encapsulates your CMake build configurations.
cmake {
// Provides a relative path to your CMake build script.
path "CMakeLists.txt"
}
}
// ...
}
|
这样最终会在android平台编辑生成一个动态链接库.so文件
2.1.3 使用 FFI 库绑定本地代码
接下来,我们需要在 lib/native_add.dart文件中编写一些代码,将本地源代码转换成Dart代码
首先,你需要创建一个 DynamicLibrary 来处理本地代码。这一步在 iOS 和 Android 之间有所不同:
import 'dart:ffi'; // For FFI
import 'dart:io'; // For Platform.isX
final DynamicLibrary nativeAddLib = Platform.isAndroid
? DynamicLibrary.open('libnative_add.so')
: DynamicLibrary.process();
//静态链接中的符号可以使用 DynamicLibrary.executable 或 DynamicLibrary.process 来加载。
//动态链接库在 Dart 中可以通过 DynamicLibrary.open 加载。
|
在 Android 上,库的名称是定义在 CMakeLists.txt 中的(见上文),但在 iOS 上,它将使用插件的名称(flutter create 命令最后的名字)。
接着,我们通过使用库的句柄来解析native_add 符号,将本地方法转化为dart可以使用的方法。
final int Function(int x, int y) nativeAdd = nativeAddLib
.lookup<NativeFunction<Int32 Function(Int32, Int32)>>('native_add')
.asFunction();
|
2.1.4 调用方法
为了验证集成是否成功,我们可以在plugin项目内的example子项目(这是自动生成的一个app类型的项目)内的lib/main.dart内尝试调用这个方法:
// Inside of _MyAppState.build:
body: Center(
child: Text('1 + 2 == ${nativeAdd(1, 2)}'),
),
|
不过,大部分情况下,我们会把这个plugin引入到一个正常的app项目中:
举个例子,比如我们新建一个flutter app 项目,名字叫flutter_app,它与native_add这个plugin处在同一层级下,我们就可以在flutter_app项目的pubspec.yaml文件添加它对native_add的依赖:
dependencies: native_add: path: ../native_add/ |
flutter_app项目main.dart文件,引入native_add包:
import "package:native_add/native_add.dart"; |
最后,就可以直接调用集成的本地方法了。
2.2 方法二
使用FFI plugin进行c代码调用
关于FFI plugin
FFI plugin是专门为绑定本地源代码而设计出来的,常规plugin虽然也可以支持,3.0之后对C源代码功能的支持ffi plugin会更强大,所以我们如果只是调用C代码,不需要平台SDK API的话,可以考虑使用FFI plugin。
2.2.1 创建项目
输入命令行
flutter create --platforms=android,ios --template=plugin_ffi hello |
2.2.2 添加C/C++源码以及相关编译配置文件
创建完成后,我们观察一下FFI plugin 项目的目录结构,对比常规plugin,主要有以下几点不同:
- 本地的源代码文件和CmakeFile.txt文件现在统一放到项目的src目录下
- ios平台目录Classes下面的源文件存在,只是引入了src下面的源代码
- android平台build.gradle 文件中externalNativeBuild属性中cmake的路径也是指向src中的CmakeFile.txt。
// Relative import to be able to reuse the C sources.information. #include "../../src/hello.c" |
android {
externalNativeBuild {
cmake {
path "../src/CMakeLists.txt"
}
}
}
|
2.2.3 源代码的编译与绑定
项目中的pubspec.yaml 提供了如下配置选项:
plugin:
platforms:
android:
ffiPlugin: true
ios:
ffiPlugin: true
|
意思是利用ffiPlugin去为各个不同的平台编译源代码,并且绑定了二进制文件集成到flutter应用中去,你需要哪些平台都需要体现在这个配置项中。
2.2.4 加载库与转换为dart方法(主要)
ffiPlugin项目为我们提供了一种方式,让我们可以利用源代码根据一定的转化规则自动生成dart的方法,这个是通过ffigen.yaml文件与ffigen命令去完成的:
flutter pub run ffigen --config ffigen.yaml |
ffigen.yaml内容如下:
# Run with `flutter pub run ffigen --config ffigen.yaml`.
name: HelloBindings
description: |
Bindings for `src/hello.h`.
Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`.
output: 'lib/hello_bindings_generated.dart'
headers:
entry-points:
- 'src/hello.h'
include-directives:
- 'src/hello.h'
preamble: |
// ignore_for_file: always_specify_types
// ignore_for_file: camel_case_types
// ignore_for_file: non_constant_identifier_names
comments:
style: any
length: full
|
生成后的文件如下,可以看出,它是根据头文件中定义的本地方法自动生成了dart代码,这个代码文件中有一个HelloBindings类,里面的方法与头文件中的方法存在映射关系。
...
import 'dart:ffi' as ffi;
class HelloBindings {
/// Holds the symbol lookup function.
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
_lookup;
/// The symbols are looked up in [dynamicLibrary].
HelloBindings(ffi.DynamicLibrary dynamicLibrary)
: _lookup = dynamicLibrary.lookup;
int sum(
int a,
int b,
) {
return _sum(
a,
b,
);
}
late final _sumPtr =
_lookup<ffi.NativeFunction<ffi.IntPtr Function(ffi.IntPtr, ffi.IntPtr)>>(
'sum');
late final _sum = _sumPtr.asFunction<int Function(int, int)>();
...
}
|
2.2.5 调用方法
代码示例如下
const String _libName = 'hello';
/// The dynamic library in which the symbols for [HelloBindings] can be found.
final DynamicLibrary _dylib = () {
if (Platform.isMacOS || Platform.isIOS) {
return DynamicLibrary.open('$_libName.framework/$_libName');
}
if (Platform.isAndroid || Platform.isLinux) {
return DynamicLibrary.open('lib$_libName.so');
}
if (Platform.isWindows) {
return DynamicLibrary.open('$_libName.dll');
}
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();
/// The bindings to the native functions in [_dylib].
final HelloBindings _bindings = HelloBindings(_dylib);
int sum(int a, int b) => _bindings.sum(a, b);
|
在这个文件里,我们还是要通过DynamicLibrary来加载本地库文件,再将实例传到类的构造方法中,调用sum方法时在HelloBindings类中实现了具体的转换细节。
三、其他集成
3.1 平台库
要链接到平台库,请按照如下说明:
1. 在 Xcode 中,打开 Runner.xcworkspace。

2. 选择目标设备。

3. 在 Linked Frameworks and Libraries 中点击 +。

4. 选择要链接的系统库。

3.2 已编译的动态库(第三方库)
-
在 Xcode 中打开
yourapp/macos/Runner.xcworkspace。-
拖动您已经预编译的
libyourlibrary.dylib到您的Runner/Frameworks。 -
点击
Runner然后进入Build Phases标签。-
拖动
libyourlibrary.dylib到Copy Bundle Resources列表。 -
在
Embed Libararies下,检查Code Sign on Copy。 -
在
Link Binary With Libraries下,设置状态为Optional。(我们使用动态链接,不需要静态链接)
-
-
点击
Runner然后进入General标签页。-
拖动
libyourlibrary.dylib到 Frameworks, Libararies and Embedded Content 列表中。 -
选择 Embed & Sign。
-
-
点击
Runner然后进入Build Settings标签页。-
在
Search Paths部分,配置Library Search Paths确保libyourlibrary.dylib的路径包括在内。
-
-
-
编辑
lib/main.dart文件。-
使用
DynamicLibrary.open('libyourlibrary.dylib')来动态链接符号表。 -
在 widget 的某个地方调用您的本地代码。
-
-
运行
flutter run然后检查您的本地方法的调用结果。
(1)添加Frameworks

(2)检查Build Phases

(3)设置General

(4)Build Settings

310

被折叠的 条评论
为什么被折叠?



