在Flutter开发中,我们经常会遇到需要调用原生代码的场景。无论是为了使用现有C/C++库,还是为了获得更好的性能,SO库(动态链接库)都是一个非常实用的解决方案。
今天我就来手把手教你如何在Flutter中使用SO动态库,从环境搭建到实际调用,让你一次就能学会!
为什么要用SO库?
在Flutter项目中,使用SO库有以下几个重要场景:
场景1:调用现有C/C++库 公司有个用了10年的图像处理库,用C++写的。总不能为了Flutter重写一遍吧?直接调用SO库是最快捷的方案。
场景2:跨平台复用核心逻辑 游戏的核心算法、加密算法这些,用C写一次,Android、iOS、Web都能用,大大减少开发和维护成本。
场景3:利用硬件加速 一些需要底层硬件支持的功能,比如音视频处理、传感器数据处理等,用C/C++可以更好地利用硬件特性。
场景4:保护核心算法 将核心算法放在SO库中,可以一定程度上保护代码不被反编译,提高安全性。
Flutter中使用SO库的两种方式
Flutter提供了两种方式来使用SO库,各有各的优缺点,根据你的需求选择合适的方式:
方式一:FFI直接调用(推荐新手)
这种方式最直接,Dart代码直接调用C函数,中间没有Kotlin/Java这一层。
优点:
- 代码简单,学习成本低
- 跨平台,Android、iOS、Linux都能用
- 不需要编写额外的原生代码
缺点:
- 只能调用C函数,不能直接用Android API
- 复杂的系统功能(权限、通知等)不好处理
适用场景:
- 纯计算任务(数学运算、加密算法)
- 需要跨平台
- 调用现有C/C++库
方式二:Android插件方式(推荐进阶)
这种方式通过MethodChannel,Dart调用Kotlin,再由Kotlin调用JNI函数。
优点:
- 可以充分利用Android原生API
- 适合复杂原生交互
- 错误处理和日志更方便
缺点:
- 多了一层Kotlin,代码量更多
- 仅限Android平台
- 需要维护两套代码
适用场景:
- 需要调用Android系统API(蓝牙、相机、文件系统)
- 需要处理权限、生命周期
- 不需要跨平台
实战:从零开始使用SO库
我准备了一个完整的示例项目,两种方式都有,方便大家学习。
项目结构说明:
flutter-so-example/
├── lib/ # Dart代码目录
│ ├── calculate.dart # FFI调用封装
│ ├── main.dart # 主界面
│ └── plugin_wrapper.dart # 插件方式调用封装
├── android/ # Android原生代码
│ └── app/
│ └── src/
│ └── main/
│ ├── jniLibs/ # SO库存放目录
│ │ ├── arm64-v8a/
│ │ ├── armeabi-v7a/
│ │ ├── x86/
│ │ └── x86_64/
│ └── kotlin/ # Kotlin代码
├── native/ # C/C++源代码
│ ├── native_lib.c # FFI方式C代码
│ └── plugin_lib.c # 插件方式C代码
└── pubspec.yaml # 项目配置文件依赖
第一步:准备C代码
先写一个简单的C函数,比如加法、减法这些基本运算:
// native_lib.h
#ifndef NATIVE_LIB_H
#define NATIVE_LIB_H
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
int divide(int a, int b);
#endif
// native_lib.c
#include "native_lib.h"
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
int divide(int a, int b) {
return a / b;
}
第二步:编译SO库
这里有个重要的点:Android设备CPU架构不一样,得为每个架构都编译一遍。
编译命令:
# arm64-v8a(64位ARM,大多数现代手机)
aarch64-linux-android21-clang -shared -fPIC native_lib.c -o libcalculate.so
# armeabi-v7a(32位ARM,老手机)
armv7a-linux-androideabi21-clang -shared -fPIC native_lib.c -o libcalculate.so
# x86_64(64位模拟器)
x86_64-linux-android21-clang -shared -fPIC native_lib.c -o libcalculate.so
# x86(32位模拟器)
i686-linux-android21-clang -shared -fPIC native_lib.c -o libcalculate.so
编译后放置位置:
编译出来的.so文件要放到android/app/src/main/jniLibs/对应架构目录下:
android/app/src/main/jniLibs/
├── arm64-v8a/
│ └── libcalculate.so
├── armeabi-v7a/
│ └── libcalculate.so
├── x86/
│ └── libcalculate.so
└── x86_64/
└── libcalculate.so
编译流程说明:
-
准备NDK环境:确保已安装Android NDK(建议使用r21及以上版本)
-
选择编译器:根据目标架构选择对应的编译器
- arm64-v8a:aarch64-linux-android21-clang
- armeabi-v7a:armv7a-linux-androideabi21-clang
- x86_64:x86_64-linux-android21-clang
- x86:i686-linux-android21-clang
-
编译参数说明:
-shared:生成动态链接库(.so文件)-fPIC:生成位置无关代码,动态库必需-o:指定输出文件名
-
编译命令示例:
aarch64-linux-android21-clang -shared -fPIC native_lib.c -o libcalculate.so -
验证编译结果:使用
file命令检查生成的.so文件架构file libcalculate.so # 输出示例:libcalculate.so: ELF 64-bit LSB shared object, ARM aarch64
第三步:FFI方式调用
在Dart中用FFI调用C函数,这是最简单直接的方式:
1. 添加依赖
在pubspec.yaml中添加ffi依赖:
dependencies:
flutter:
sdk: flutter
ffi: ^2.1.0
2. 定义FFI绑定
创建一个calculate.dart文件:
import 'dart:ffi';
import 'package:ffi/ffi.dart';
// 定义函数类型
typedef AddFunc = Int32 Function(Int32 a, Int32 b);
typedef Add = int Function(int a, int b);
typedef SubtractFunc = Int32 Function(Int32 a, Int32 b);
typedef Subtract = int Function(int a, int b);
typedef MultiplyFunc = Int32 Function(Int32 a, Int32 b);
typedef Multiply = int Function(int a, int b);
typedef DivideFunc = Int32 Function(Int32 a, Int32 b);
typedef Divide = int Function(int a, int b);
class Calculate {
late final DynamicLibrary _lib;
late final Add _add;
late final Subtract _subtract;
late final Multiply _multiply;
late final Divide _divide;
Calculate() {
// 加载SO库
_lib = DynamicLibrary.open('libcalculate.so');
// 查找函数并绑定
_add = _lib.lookup<NativeFunction<AddFunc>>('add').asFunction();
_subtract = _lib.lookup<NativeFunction<SubtractFunc>>('subtract').asFunction();
_multiply = _lib.lookup<NativeFunction<MultiplyFunc>>('multiply').asFunction();
_divide = _lib.lookup<NativeFunction<DivideFunc>>('divide').asFunction();
}
// 对外暴露方法
int add(int a, int b) => _add(a, b);
int subtract(int a, int b) => _subtract(a, b);
int multiply(int a, int b) => _multiply(a, b);
int divide(int a, int b) => _divide(a, b);
}
3. 在Flutter中调用
// 创建实例
final calculate = Calculate();
// 调用函数
final sum = calculate.add(10, 5); // 返回15
final difference = calculate.subtract(10, 5); // 返回5
final product = calculate.multiply(10, 5); // 返回50
final quotient = calculate.divide(10, 5); // 返回2
就是这么简单!Dart直接调用C函数,代码清晰易懂。
第四步:Android插件方式调用
如果需要调用Android系统API,或者需要更复杂的原生交互,可以使用Android插件方式。
1. 创建Flutter插件
flutter create --template=plugin --platforms=android load_so_plugin
2. 写JNI函数
C函数要按照JNI规范命名,创建plugin_lib.c:
#include <jni.h>
// 格式:Java_包名_类名_方法名
// 包名中的点(.)替换为下划线(_)
JNIEXPORT jint JNICALL
Java_com_example_load_1so_1plugin_LoadSoPlugin_add(JNIEnv *env, jobject thiz, jint a, jint b) {
return a + b;
}
JNIEXPORT jint JNICALL
Java_com_example_load_1so_1plugin_LoadSoPlugin_subtract(JNIEnv *env, jobject thiz, jint a, jint b) {
return a - b;
}
3. 编译JNI库
编译成libplugin_lib.so,放到插件的android/src/main/jniLibs/目录。
4. Kotlin加载SO库
在LoadSoPlugin.kt中:
class LoadSoPlugin : FlutterPlugin, MethodCallHandler {
init {
// 加载SO库
System.loadLibrary("plugin_lib")
}
// 声明native方法
private external fun add(a: Int, b: Int): Int
private external fun subtract(a: Int, b: Int): Int
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"add" -> {
val a = call.argument<Int>("a")
val b = call.argument<Int>("b")
result.success(add(a!!, b!!))
}
"subtract" -> {
val a = call.argument<Int>("a")
val b = call.argument<Int>("b")
result.success(subtract(a!!, b!!))
}
else -> result.notImplemented()
}
}
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
val channel = MethodChannel(flutterPluginBinding.binaryMessenger, "load_so_plugin")
channel.setMethodCallHandler(this)
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
}
}
5. Dart通过MethodChannel调用
在load_so_plugin.dart中:
class LoadSoPlugin {
static const MethodChannel _channel = MethodChannel('load_so_plugin');
static Future<int> add(int a, int b) async {
final result = await _channel.invokeMethod<int>('add', {
'a': a,
'b': b,
});
return result!;
}
static Future<int> subtract(int a, int b) async {
final result = await _channel.invokeMethod<int>('subtract', {
'a': a,
'b': b,
});
return result!;
}
}
6. 在Flutter中使用
// 调用插件方法
final sum = await LoadSoPlugin.add(10, 5);
final difference = await LoadSoPlugin.subtract(10, 5);
常见问题和解决方案
问题1:找不到SO库
错误信息:
UnsatisfiedLinkError: dlopen failed: library "libcalculate.so" not found
解决方案:
- 确认SO库放在
android/app/src/main/jniLibs/对应架构目录 - 检查设备架构:
adb shell getprop ro.product.cpu.abi - 确保编译时用的API级别和设备匹配
问题2:函数签名不匹配
错误信息:
Invalid argument(s): 'add'
解决方案: 仔细检查函数类型定义,确保Dart和C函数的参数类型一致:
// C函数:int add(int a, int b)
// Dart定义:
typedef AddFunc = Int32 Function(Int32 a, Int32 b); // 必须匹配
问题3:JNI函数命名错误
错误信息:
java.lang.UnsatisfiedLinkError: No implementation found for int add
解决方案:
严格按照JNI规范命名函数:Java_包名_类名_方法名
- 包名中的点(.)要替换成下划线(_)
- 包名中的下划线(_)要替换成_1
问题4:64位和32位架构兼容
解决方案: 为所有支持的架构都编译SO库,包括:
- arm64-v8a(64位ARM)
- armeabi-v7a(32位ARM)
- x86(32位模拟器)
- x86_64(64位模拟器)
项目结构最佳实践
一个好的SO库项目结构应该是这样的:
flutter-so-example/
├── lib/
│ ├── calculate.dart # FFI调用
│ ├── main.dart # 主界面
│ └── performance_test.dart # 性能测试(可选)
├── android/
│ ├── app/
│ │ └── src/
│ │ └── main/
│ │ ├── jniLibs/ # SO库存放目录
│ │ └── kotlin/
│ └── build.gradle
├── plugins/
│ ├── calculate/ # FFI库
│ │ ├── native_lib.c
│ │ └── build.bat # 编译脚本
│ └── plugin_lib/ # 插件库
│ ├── plugin_lib.c
│ └── build.bat # 编译脚本
└── pubspec.yaml
编译脚本推荐
为了方便编译SO库,我准备了一个批处理脚本build.bat:
@echo off
:: 设置NDK路径(根据你的实际安装位置修改)
set NDK_PATH=D:\Android\Sdk\ndk\25.2.9519653
:: 编译目录
set OUTPUT_DIR=..\..\android\app\src\main\jniLibs
:: 创建输出目录
mkdir %OUTPUT_DIR%\arm64-v8a 2>nul
mkdir %OUTPUT_DIR%\armeabi-v7a 2>nul
mkdir %OUTPUT_DIR%\x86 2>nul
mkdir %OUTPUT_DIR%\x86_64 2>nul
:: 编译arm64-v8a
echo 编译 arm64-v8a...
%NDK_PATH%\toolchains\llvm\prebuilt\windows-x86_64\bin\aarch64-linux-android21-clang -shared -fPIC native_lib.c -o %OUTPUT_DIR%\arm64-v8a\libcalculate.so
:: 编译armeabi-v7a
echo 编译 armeabi-v7a...
%NDK_PATH%\toolchains\llvm\prebuilt\windows-x86_64\bin\armv7a-linux-androideabi21-clang -shared -fPIC native_lib.c -o %OUTPUT_DIR%\armeabi-v7a\libcalculate.so
:: 编译x86
echo 编译 x86...
%NDK_PATH%\toolchains\llvm\prebuilt\windows-x86_64\bin\i686-linux-android21-clang -shared -fPIC native_lib.c -o %OUTPUT_DIR%\x86\libcalculate.so
:: 编译x86_64
echo 编译 x86_64...
%NDK_PATH%\toolchains\llvm\prebuilt\windows-x86_64\bin\x86_64-linux-android21-clang -shared -fPIC native_lib.c -o %OUTPUT_DIR%\x86_64\libcalculate.so
echo 编译完成!
pause
使用方法:
- 修改脚本中的NDK路径为你的实际安装位置
- 双击运行
build.bat - 脚本会自动编译所有架构的SO库并复制到正确位置
实际应用场景
学会了SO库的使用,你可以:
1. 集成OpenCV做图像处理
- 图像滤镜、边缘检测
- 人脸识别、目标跟踪
- 图像分割、特征提取
2. 集成FFmpeg做音视频处理
- 视频解码、编码
- 音频处理、混音
- 视频剪辑、特效
3. 集成TensorFlow Lite做AI推理
- 图像分类、目标检测
- 语音识别、自然语言处理
- 实时AI应用
4. 集成加密算法库
- 安全通信、数据加密
- 哈希计算、数字签名
- 密码学应用
总结
使用SO库是Flutter开发中的一个重要技能,它让我们能够:
- 复用现有代码:直接使用成熟的C/C++库
- 跨平台开发:一套代码多平台使用
- 访问底层功能:调用系统API和硬件特性
- 保护核心算法:提高代码安全性
选择建议:
- 新手:从FFI开始,简单直接
- 需要原生API:使用Android插件方式
- 跨平台需求:优先使用FFI
- 复杂原生交互:使用插件方式
后续学习资源
- 官方文档:Dart FFI
- NDK文档:Android NDK
- 示例项目:GitHub链接
如果这篇文章对你有帮助,欢迎点赞收藏!有问题评论区见!