Android 上使用 dart:ffi 呼叫本地程式碼
Flutter 移動版可以使用 dart:ffi 函式庫來呼叫本地的 C API。 FFI 代表 外部功能介面。類似功能的其他術語包括本地介面和語言繫結。
你必須首先確保本地程式碼已載入,並且其符號對 Dart 可見,然後才能在函式庫或程式使用 FFI 函式庫繫結本地程式碼。本頁主要介紹如何在 Flutter 外掛或應用程式中編譯、打包和載入本地程式碼。
本教程示範了如何在 Flutter 外掛中捆綁 C/C++ 原始碼,並使用 Android 和 iOS 上的 Dart FFI 函式庫繫結它們。在本範例中,你將建立一個實現 32 位的加法 C 函式,然後透過名為 “native_add” 的 Dart 外掛暴露它。
動態連結 vs 靜態連結
本地函式庫可以動態或靜態地連結到應用程式中。一個靜態連結函式庫會被嵌入到應用程式的可執行映像中,並在應用程式啟動時載入。
靜態連結中的符號可以使用 DynamicLibrary.executable
或 DynamicLibrary.process
來載入。
相比之下,動態連結庫則分佈在應用程式中的單獨的檔案或資料夾中,並按需載入。在 Android 上,動態連結庫作為一組
.so
(ELF 可執行與可連結格式)檔案分發,每個架構各有一個。
動態連結庫在 Dart 中可以透過 DynamicLibrary.open
載入。
Dart dev 頻道中的 API 已經可用: Dart API 參考文件.
在 Android 平台只有動態函式庫可以使用(因為在 JVM 環境無法靜態連結)。
建立 FFI 外掛
如果要建立一個名為 “native_add” 的外掛,你需要這麼做:
$ flutter create --platforms=android,ios,macos,windows,linux --template=plugin_ffi native_add
$ cd native_add
C/C++ 原始碼會被建立至 native_add/src
。這些原始碼在不同平台建立時會生成在不同平台的建立檔案夾。
FFI 函式庫只能繫結 C 語言的符號,所以 C++ 語言的符號會被標記為 extern "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 生成。
其他的用例
平台函式庫
要連結到平台函式庫,請按照如下說明:
-
在 Android 文件的 Android NDK Native APIs 清單中找到所需的函式庫。它列出了穩定的本地 API。
-
使用
DynamicLibrary.open
載入函式庫。範例:載入 OpenGL ES (v3):DynamicLibrary.open('libGLES_v3.so');
如果文件中有說明,你還需要根據說明更新 Android 應用程式或外掛的清單檔案。
第一方函式庫
對於應用程式或外掛,以原始碼或二進位形式包含本機程式碼的過程是相同的。
開源三方函式庫
遵循 Android 文件中的
新增 C 和 C++ 程式碼到專案
來新增本地程式碼和對本地程式碼工具鏈的支援(CMake 或 ndk-build
)。
閉源三方函式庫
要建立包含 Dart 原始碼,但以二進位形式分發 C/C++ 函式庫的 Flutter 外掛,請按照如下說明:
-
開啟你專案的
android/build.gradle
檔案。 -
新增 aar 工件新增為依賴。 不要在你的 Flutter package 中匯入工件。對應的,它需要在一個倉庫中下載,比如 JCenter。
Android APK 尺寸(共享物件壓縮)
Android 指南 通常建議分發未壓縮的本地共享物件,因為這種做法實際上可以節省裝置空間。共享物件可以直接從 APK 載入,而不是將它們解壓到裝置上的臨時位置然後再載入。 APK 是在傳輸過程中額外打包的 - 這就是為什麼你應該檢視下載的檔案尺寸。
Flutter APK 檔案預設情況下不遵循這些指導原則來壓縮 libflutter.so
和 libapp.so
,這會導致 APK 體積更小,但在裝置上體積更大。
來自第三方的共享函式庫可以使用其 AndroidManifest.xml
中的 android:extractNativeLibs="true"
更改此預設設定,來停止壓縮 libflutter.so
、libapp.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">
Other Resources
To learn more about C interoperability, check out these videos: