Flutter 移動和桌面應用可以使用 dart:ffi 庫呼叫原生 C API。FFI外部函式介面 的縮寫。其他類似的術語包括原生介面語言繫結

在您的庫或程式可以使用 FFI 庫繫結到原生程式碼之前,您必須確保原生程式碼已載入且其符號對 Dart 可見。本頁重點介紹在 Flutter 外掛或應用中編譯、打包和載入 Android 原生程式碼。

本教程演示瞭如何在 Flutter 外掛中捆綁 C/C++ 原始檔,並使用 Dart FFI 庫在 Android 和 iOS 上將它們繫結起來。在此過程中,您將建立一個 C 函式來實現 32 位加法,然後透過一個名為“native_add”的 Dart 外掛將其公開。

動態連結與靜態連結

#

原生庫可以動態或靜態地連結到應用中。靜態連結庫嵌入到應用的 executables 映像中,並在應用啟動時載入。

可以透過 DynamicLibrary.executableDynamicLibrary.process 來載入靜態連結庫的符號。

相比之下,動態連結庫以單獨的檔案或資料夾的形式分發在應用內,並按需載入。在 Android 上,動態連結庫以一組 .so(ELF)檔案形式分發,每個架構一個。

可以透過 DynamicLibrary.open 將動態連結庫載入到 Dart 中。

API 文件可從 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):

    dart
    DynamicLibrary.open('libGLES_v3.so');

如果文件有指示,您可能需要更新應用或外掛的 Android manifest 檔案。

第一方庫

#

無論是一個應用還是一個外掛,將原生程式碼包含在原始碼或二進位制形式中的過程是相同的。

開源第三方

#

請按照 Android 文件中的 將 C 和 C++ 程式碼新增到您的專案 說明來新增原生程式碼和對原生程式碼工具鏈(CMake 或 ndk-build)的支援。

閉源第三方庫

#

要建立一個包含 Dart 原始碼的 Flutter 外掛,但以二進位制形式分發 C/C++ 庫,請使用以下說明:

  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 中按如下方式覆蓋該設定。

xml
<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. -->

    <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 互操作性的資訊,請檢視以下影片