使用 dart:ffi 绑定到原生 Android 代码

Flutter 移动和桌面应用程序可以使用 dart:ffi 库来调用原生 C API。FFI 代表 外部函数接口。 类似功能的其他术语包括原生接口语言绑定。

在您的库或程序可以使用 FFI 库绑定到原生代码之前,您必须确保已加载原生代码,并且其符号对 Dart 可见。此页面重点介绍在 Flutter 插件或应用程序中编译、打包和加载 Android 原生代码。

本教程演示如何在 Flutter 插件中捆绑 C/C++ 源代码,并使用 Android 和 iOS 上的 Dart FFI 库绑定到它们。在此演练中,您将创建一个实现 32 位加法的 C 函数,然后通过名为“native_add”的 Dart 插件公开它。

动态链接与静态链接

原生库可以动态或静态链接到应用程序中。静态链接的库嵌入到应用程序的可执行映像中,并在应用程序启动时加载。

可以使用 DynamicLibrary.executableDynamicLibrary.process 加载静态链接库中的符号。

相比之下,动态链接库分布在应用程序中的一个单独文件或文件夹中,并按需加载。在 Android 上,动态链接库以一组 .so(ELF)文件分发,每个架构一个。

可以通过 DynamicLibrary.open 将动态链接库加载到 Dart 中。

API 文档可从 Dart 开发频道获得:Dart API 参考文档

在 Android 上,仅支持动态链接库(因为主可执行文件是 JVM,我们不会静态链接到它)。

创建 FFI 插件

要创建名为“native_add”的 FFI 插件,请执行以下操作

$ flutter create --platforms=android,ios,macos,windows,linux --template=plugin_ffi native_add
$ cd native_add

这将在 native_add/src 中创建一个包含 C/C++ 源的插件。这些源由各种操作系统构建文件夹中的原生构建文件构建。

FFI 库只能绑定到 C 符号,因此在 C++ 中,这些符号标记为 extern "C"

您还应该添加属性以指示符号是从 Dart 引用的,以防止链接器在链接时优化期间丢弃符号。 __attribute__((visibility("default"))) __attribute__((used))

在 Android 上,native_add/android/build.gradle 链接代码。

原生代码在 lib/native_add_bindings_generated.dart 中从 dart 调用。

绑定使用 package:ffigen 生成。

其他用例

平台库

要链接到平台库,请使用以下说明

  1. 在 Android 文档中的 Android NDK 原生 API 列表中找到所需的库。这列出了稳定的原生 API。
  2. 使用 DynamicLibrary.open 加载库。例如,要加载 OpenGL ES (v3)

    DynamicLibrary.open('libGLES_v3.so');
    

如果文档中指示,您可能需要更新应用或插件的 Android 清单文件。

第一方库

将原生代码包含在源代码或二进制形式中的流程对于应用或插件而言是相同的。

开源第三方

按照 Android 文档中的将 C 和 C++ 代码添加到项目说明添加原生代码和对原生代码工具链(CMake 或 ndk-build)的支持。

闭源第三方库

要创建一个包含 Dart 源代码但以二进制形式分发 C/C++ 库的 Flutter 插件,请使用以下说明

  1. 打开项目的 android/build.gradle 文件。
  2. 添加 AAR 工件作为依赖项。不要将工件包含在 Flutter 软件包中。相反,应从 JCenter 等存储库下载工件。

Android APK 大小(共享对象压缩)

Android 指南通常建议以未压缩形式分发原生共享对象,因为这实际上可以节省设备空间。可以从 APK 直接加载共享对象,而不是将它们解包到设备的临时位置,然后加载。此外,APK 会在传输过程中打包,这就是你应该查看下载大小的原因。

默认情况下,Flutter APK 不会遵循这些指南,并且会压缩 libflutter.solibapp.so,这会导致 APK 大小变小,但设备大小变大。

来自第三方的共享对象可以在其 AndroidManifest.xml 中使用 android:extractNativeLibs="true" 更改此默认设置,并停止压缩 libflutter.solibapp.so 以及任何用户添加的共享对象。要重新启用压缩,请按照以下方式覆盖 your_app_name/android/app/src/main/AndroidManifest.xml 中的设置。

@@ -1,5 +1,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.your_app_name">
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.example.your_app_name" >
     <!-- io.flutter.app.FlutterApplication is an android.app.Application that
          calls FlutterMain.startInitialization(this); in its onCreate method.
          In most cases you can leave this as-is, but you if you want to provide
          additional functionality it is fine to subclass or reimplement
          FlutterApplication and put your custom class here. -->
@@ -8,7 +9,9 @@
     <application
         android:name="io.flutter.app.FlutterApplication"
         android:label="your_app_name"
-        android:icon="@mipmap/ic_launcher">
+        android:icon="@mipmap/ic_launcher"
+        android:extractNativeLibs="true"
+        tools:replace="android:extractNativeLibs">

其他资源

要了解有关 C 互操作性的更多信息,请查看以下视频