有没有一种需求,我写个flutter插件供客户使用,但是里面实现内容得封装不可见,做过flutter都知道,外界接入flutter插件是直接源码接入的,所有源码都可以看得到;这样一来反而比不上原生的(iOS和Android端); 那这样怎么处理呢?实现这个需求呢;按照原生原生思路,打包成framework供iOS端使用,打包成so库供Android使用,如果两端都需要使用同样算法,那直接使用c来封装,后面就一样;资深flutter工程师发现,flutter也可以这样做的;因为flutter底层原理就是调用两端原生代码的。 接下来我们来规划步骤:1、写使用C/C++写一个算法方法供外界调用;2、iOS打成静态库,Android对应打成so库;3、创建flutter插件,建立通道,分别调用两端的静态库;4、验证(以上都是基于Mac电脑来写的,Windows有的步骤实现不了)
1、准备开发环境(针对Android端)
安装 Android NDK
# 通过 Android Studio 安装:
# 1. 打开 Android Studio > Preferences > Appearance & Behavior > System Settings > Android SDK
# 2. 切换到 "SDK Tools" 标签
# 3. 勾选 "NDK (Side by side)" 和 "CMake",点击 Apply
# 或使用命令行安装:
sdkmanager --install "ndk;25.1.8937393" # 替换为你的 NDK 版本
设置环境变量
在 ~/.zshrc 或 ~/.bash_profile 中添加:
export ANDROID_NDK_HOME="$HOME/Library/Android/sdk/ndk/25.1.8937393" # 替换为你的 NDK 路径
export PATH="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin:$PATH"
激活配置:
source ~/.zshrc # 或 source ~/.bash_profile
编写 C 代码示例
比如创建mylib.c:
#include <jni.h>
JNIEXPORT jint JNICALL
Java_com_example_app_NativeHelper_add(JNIEnv *env, jobject obj, jint a, jint b) {
return a + b;
}
注意:函数名格式遵循 Java_包名_类名_方法名(包名中的 . 替换为 _)。 验证符合暴露
llvm-nm -g libBleSDK.a | grep ' T '
应该只显示公共函数:
T adjustSleepTemp
T getTemperatureResult
Flutter Android 调用静态库的完整指南
在 Flutter 中,Android 部分不能直接调用 .a 静态库文件,但可以通过将静态库链接到动态库(.so 文件)中来实现调用。以下是完整的实现步骤:
graph TD
A[编译静态库] --> B[创建 JNI 接口]
B --> C[链接为动态库]
C --> D[集成到 Flutter]
D --> E[通过 JNI 调用]
1. 创建 JNI 接口文件
jni_interface.c
#include <jni.h>
#include "BleSDK.h" // 包含静态库的头文件
JNIEXPORT jfloat JNICALL
Java_com_example_app_NativeHelper_adjustSleepTemp(JNIEnv *env, jobject obj, jfloat sleepTemp) {
return adjustSleepTemp(sleepTemp);
}
JNIEXPORT jobject JNICALL
Java_com_example_app_NativeHelper_getTemperatureResult(
JNIEnv *env,
jobject obj,
jfloatArray temperatureArray
) {
jsize length = (*env)->GetArrayLength(env, temperatureArray);
jfloat *array = (*env)->GetFloatArrayElements(env, temperatureArray, NULL);
// 调用静态库中的函数
TemperatureResult result = getTemperatureResult(array, length);
(*env)->ReleaseFloatArrayElements(env, temperatureArray, array, 0);
// 创建Java对象
jclass resultClass = (*env)->FindClass(env, "com/example/app/TemperatureResult");
jmethodID constructor = (*env)->GetMethodID(env, resultClass, "<init>", "(FI)V");
jobject resultObject = (*env)->NewObject(env, resultClass, constructor,
result.temperature, result.temperatureStatus);
return resultObject;
}
2. 创建 CMakeLists.txt 文件
cmake_minimum_required(VERSION 3.10.2)
# 设置静态库路径
set(LIB_DIR ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI})
# 添加预编译的BleSDK静态库
add_library(BleSDK STATIC IMPORTED)
set_target_properties(BleSDK PROPERTIES IMPORTED_LOCATION ${LIB_DIR}/libBleSDK.a)
# 包含头文件
include_directories(${CMAKE_SOURCE_DIR}/include)
# 创建共享库
add_library(jni_ble_sdk SHARED jni_interface.c)
# 链接静态库
target_link_libraries(jni_ble_sdk BleSDK)
3. 配置 Android 项目
目录结构:
android/app/
├── src/main/
│ ├── cpp/
│ │ ├── CMakeLists.txt
│ │ ├── jni_interface.c
│ │ ├── include/
│ │ │ └── BleSDK.h
│ │ └── libs/
│ │ ├── arm64-v8a/
│ │ │ └── libBleSDK.a
│ │ ├── armeabi-v7a/
│ │ │ └── libBleSDK.a
│ │ ├── x86/
│ │ │ └── libBleSDK.a
│ │ └── x86_64/
│ │ └── libBleSDK.a
│ └── java/
│ └── com/example/app/
│ ├── NativeHelper.java
│ └── TemperatureResult.java
app/build.gradle 配置:
android {
defaultConfig {
externalNativeBuild {
cmake {
cppFlags ""
// 指定ABI过滤器
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
}
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
sourceSets {
main {
jniLibs.srcDirs = ['src/main/cpp/libs'] // 静态库位置
}
}
}
4. 创建 Java 封装类
NativeHelper.java
package com.example.app;
public class NativeHelper {
static {
System.loadLibrary("jni_ble_sdk"); // 加载动态库
}
public static native float adjustSleepTemp(float sleepTemp);
public static native TemperatureResult getTemperatureResult(float[] temperatures);
}
TemperatureResult.java
package com.example.app;
public class TemperatureResult {
public final float temperature;
public final int temperatureStatus;
public TemperatureResult(float temperature, int temperatureStatus) {
this.temperature = temperature;
this.temperatureStatus = temperatureStatus;
}
}
5. 在 Flutter 中调用
native_utils.dart
import 'package:flutter/services.dart';
class NativeUtils {
static const _channel = MethodChannel('com.example.app/native_utils');
static Future<double> adjustSleepTemp(double temp) async {
return await _channel.invokeMethod('adjustSleepTemp', temp);
}
static Future<TemperatureResult> getTemperature(List<double> temps) async {
final result = await _channel.invokeMethod('getTemperature', temps);
return TemperatureResult(result['temperature'], result['status']);
}
}
class TemperatureResult {
final double temperature;
final int status;
TemperatureResult(this.temperature, this.status);
}
Android 端实现 (MainActivity.kt)
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.app/native_utils")
channel.setMethodCallHandler { call, result ->
when (call.method) {
"adjustSleepTemp" -> {
try {
val temp = call.arguments as Double
val adjusted = NativeHelper.adjustSleepTemp(temp.toFloat())
result.success(adjusted.toDouble())
} catch (e: Exception) {
result.error("ADJUST_TEMP_ERROR", e.message, null)
}
}
"getTemperature" -> {
try {
val temps = call.arguments as List<Double>
val floatArray = temps.map { it.toFloat() }.toFloatArray()
val res = NativeHelper.getTemperatureResult(floatArray)
val map = mapOf(
"temperature" to res.temperature.toDouble(),
"status" to res.temperatureStatus
)
result.success(map)
} catch (e: Exception) {
result.error("GET_TEMP_ERROR", e.message, null)
}
}
else -> result.notImplemented()
}
}
}
}
6. 在 Dart 中使用
main.dart
import 'package:flutter/material.dart';
import 'native_utils.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
double adjustedTemp = 0.0;
TemperatureResult? tempResult;
@override
void initState() {
super.initState();
testNativeCalls();
}
Future<void> testNativeCalls() async {
// 测试调整睡眠温度
adjustedTemp = await NativeUtils.adjustSleepTemp(20.5);
print('调整后的睡眠温度: $adjustedTemp');
// 测试获取温度结果
tempResult = await NativeUtils.getTemperature([22.5, 23.0, 22.8]);
print('平均温度: ${tempResult?.temperature}, 状态: ${tempResult?.status}');
setState(() {});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Native 库测试')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('调整后的睡眠温度: $adjustedTemp'),
if (tempResult != null)
Text('平均温度: ${tempResult!.temperature}, 状态: ${tempResult!.status}'),
],
),
),
),
);
}
}
关键注意事项
- 编译过程解析
- 数据类型转换
Dart 类型 | Java/Kotlin 类型 | C 类型 | 处理方式 |
| ------------ | -------------- | ----------- | ------------ |
| double | Float | jfloat | 直接转换 |
| List<double> | Float[] | jfloatArray | 通过 JNI 函数转换 |
| 自定义对象 | Java 对象 | jobject | 手动创建 Java 对象 |
| 结构体 | Java 类 | struct | 映射为 Java 对象
2、写使用C/C++写一个算法方法供外界调用
使用Xcode创建.c文件和.h文件;
#BleSDK.h
#include <stdio.h>
#ifndef BleSDK_h
#define BleSDK_h
#ifdef __cplusplus
extern "C" {
#endif// 定义返回结构体替代OC字典
typedef struct {
float temperature;
int temperatureStatus;
} TemperatureResult;
TemperatureResult getTemperatureResult(float* arrayTemperature, int count);
float adjustSleepTemp(float sleepTemp);
#ifdef __cplusplus
}
#endif
#endif /* BleSDK_h */
#BleSDK.c
float adjustSleepTemp(float sleepTemp) {
实现.......
}
TemperatureResult getTemperatureResult(float* arrayTemperature, int count){
实现.......
}
对应iOS打成静态库,对应上面使用Xcode打成静态库比较简单,不多做赘述。
3、iOS打成静态库,Android对应打成so库
iOS端在ios目录下新建Framework目录,将静态库拷贝这这个目录下,在.podspec文件 添加
s.vendored_frameworks = 'Framework/blesdkX6.framework'
目录如下
我们直接做Android方面打成静态库;
使用Android Studio(没有下载的可以直接下载)创建一个Android项目或者是flutter项目(毕竟后面都是需要用到flutter项目);
不过我直接使用创建终端命令flutter项目
flutter create -i objc -a java flutter_example --template=plugin --platforms=android,ios --org=flutter_example --description="Your plugin description" flutter_example
创建一个jni目录,跟Android同一个层级 将之前BleSDK.h和BleSDK.c都拷贝到jni目录下; 在创建一个build_static_lib.sh文件
#!/bin/bash
# build_static_lib.sh - 修正版本
set -e # 遇到错误立即退出
# 1. 手动设置 NDK 路径(替换为您的实际路径)
export ANDROID_NDK_HOME="/Users/xxx/Library/Android/sdk/ndk/28.1.13356709"
# 验证NDK路径
if [ ! -d "$ANDROID_NDK_HOME" ]; then
echo "错误: NDK目录不存在: $ANDROID_NDK_HOME"
exit 1
fi
TOOLCHAIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64"
API=21
# 目标架构
ARCHS=("aarch64-linux-android" "armv7a-linux-androideabi" "x86_64-linux-android" "i686-linux-android")
ARCH_NAMES=("arm64-v8a" "armeabi-v7a" "x86_64" "x86")
# 清理旧构建
rm -rf build dist
mkdir -p build
for i in "${!ARCHS[@]}"; do
ARCH=${ARCHS[$i]}
ARCH_NAME=${ARCH_NAMES[$i]}
echo "构建架构: $ARCH_NAME ($ARCH)"
# 设置编译器路径
CLANG="$TOOLCHAIN/bin/${ARCH}${API}-clang"
AR="$TOOLCHAIN/bin/llvm-ar"
# 创建架构特定目录
mkdir -p "build/$ARCH_NAME"
# 编译核心实现
"$CLANG" \
-c BleSDK.c -o "build/$ARCH_NAME/BleSDK.o" \
-I. \
-fvisibility=hidden
# 创建静态库
"$AR" rcs "build/$ARCH_NAME/libBleSDK.a" "build/$ARCH_NAME/BleSDK.o"
echo "验证 $ARCH_NAME 的符号:"
"$TOOLCHAIN/bin/llvm-nm" -g "build/$ARCH_NAME/libBleSDK.a" | grep ' T '
done
# 创建输出目录
mkdir -p dist/{include,lib}
# 复制头文件
cp BleSDK.h dist/include/
# 复制各架构库文件
for arch in "${ARCH_NAMES[@]}"; do
mkdir -p "dist/lib/$arch"
cp "build/$arch/libBleSDK.a" "dist/lib/$arch/"
done
echo "构建完成!"
echo "头文件: dist/include/BleSDK.h"
echo "静态库: dist/lib/*/libBleSDK.a"
cd到这个目录,执行这个sh脚本文件./build_static_lib.sh;然后会生成build和dist文件目录,dist就是我们所需要的静态文件,重复执行脚本文件,可以将前面文件移除掉 rm -rf build dist 如果没有权限执行,可以执行chmod +x build_static_lib.sh;当然中途有会问题的,可以具体问题具体解决
生成静态库后,将静态库和头文件拷贝到真正需要使用的项目中
放在flutter项目-android-src.main目前下新建一个cpp目录文件(与java文件目录是一个层级);在新建CMakeLists.txt和jni_interface.c这两个是创建共享库和链接静态库作用
CMakeLists.txt
cmake_minimum_required(VERSION 3.22.1)
# 设置静态库路径
set(LIB_DIR ${CMAKE_SOURCE_DIR}/lib/${ANDROID_ABI})
# 添加预编译的静态库
add_library(BleSDK STATIC IMPORTED)
set_target_properties(BleSDK PROPERTIES
IMPORTED_LOCATION ${LIB_DIR}/libBleSDK.a
# 添加这行确保符号可见
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_SOURCE_DIR}/include
)
# 包含头文件
include_directories(${CMAKE_SOURCE_DIR}/include)
# 创建共享库
add_library(jni_ble_sdk SHARED jni_interface.c)
# 链接静态库 - 添加 PUBLIC 或 PRIVATE
target_link_libraries(jni_ble_sdk PRIVATE BleSDK)
# 添加必要的系统库
target_link_libraries(jni_ble_sdk PRIVATE log android)
# jni_interface.c
#include <jni.h>
#include "BleSDK.h" // 包含静态库的头文件
JNIEXPORT jfloat JNICALL
Java_com_jstyle_blesdk2301_ble2301_NativeHelper_adjustSleepTemp(JNIEnv *env, jobject obj, jfloat sleepTemp) {
return adjustSleepTemp(sleepTemp);
}
JNIEXPORT jobject JNICALL
Java_com_jstyle_blesdk2301_ble2301_NativeHelper_getTemperatureResult(
JNIEnv *env,
jclass clazz,
jfloatArray temperatureArray)
{
// 1. 获取数组长度和指针
jsize length = (*env)->GetArrayLength(env, temperatureArray);
jfloat *array = (*env)->GetFloatArrayElements(env, temperatureArray, NULL);
// 2. 调用核心函数
TemperatureResult result = getTemperatureResult(array, length);
// 3. 释放数组
(*env)->ReleaseFloatArrayElements(env, temperatureArray, array, 0);
#xxx/TemperatureResult这个是创建TemperatureResult文件路径
// 4. 创建 Java TemperatureResult 对象
jclass resultClass = (*env)->FindClass(env, "com/xxx/TemperatureResult");
jmethodID constructor = (*env)->GetMethodID(env, resultClass, "<init>", "(FI)V");
jobject resultObject = (*env)->NewObject(env, resultClass, constructor,
result.temperature, result.temperatureStatus);
return resultObject;
}
在java目录下创建 TemperatureResult和NativeHelper
package com.xxx;
public class NativeHelper {
static {
System.loadLibrary("jni_ble_sdk"); // 加载动态库
}
public static native float adjustSleepTemp(float sleepTemp);
public static native TemperatureResult getTemperatureResult(float[] temperatures);
}
package com.xxx;
public class TemperatureResult {
public final float temperature;
public final int temperatureStatus;
public TemperatureResult(float temperature, int temperatureStatus) {
this.temperature = temperature;
this.temperatureStatus = temperatureStatus;
}
}
Android部分需要配置,在android/build.gradle文件android模块新增
defaultConfig {
minSdkVersion 21
externalNativeBuild {
cmake {
cppFlags ""
// 指定ABI过滤器
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
}
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.22.1"
}
}
sourceSets {
main {
jniLibs.srcDirs = ['src/main/cpp/lib'] // 静态库位置
}
}
完整是
android {
compileSdkVersion 31
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion 21
externalNativeBuild {
cmake {
cppFlags ""
// 指定ABI过滤器
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
}
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.22.1"
}
}
sourceSets {
main {
jniLibs.srcDirs = ['src/main/cpp/lib'] // 静态库位置
}
}
}
4、创建flutter插件,建立通道,分别调用两端的静态库
创建flutter插件,直接使用工具或者终端命令创建flutter项目(这个简单,不赘述) 解析是flutter调用,这些静态库; 先做两端 Android端 在方法public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) 执行方法调用静态库方法
// 5. 调用原生方法
TemperatureResult res = NativeHelper.getTemperatureResult(floatArray);
// 6. 构建响应
Map<String, Object> response = new HashMap<>();
response.put("temperature", res.temperature);
response.put("status", res.temperatureStatus);
result.success(response);
在iOS端
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result 方法调用静态库
int count = (int)temperatureArray.count;
float cTemperatures[count];
// 将NSArray转换为C float数组
for (int i = 0; i < count; i++) {
cTemperatures[i] = [temperatureArray[i] floatValue];
}
// 调用C函数(注意:直接调用函数,不是对象方法)
TemperatureResult resultStruct = getTemperatureResult(cTemperatures, count);
// 将结果转换为Flutter可识别的格式
result(@{
@"temperature": @(resultStruct.temperature),
@"status": @(resultStruct.temperatureStatus)
});
接下来是flutter代码调用
// 获取温度数据
static Future<Map<String, dynamic>> getTemperature(List<double> temperatures) async {
try {
final result = await _channel.invokeMethod('getTemperature', {
'temperatures': temperatures.map((t) => t.toDouble()).toList(),
});
print(result);
return Map<String, dynamic>.from(result);//result as Map<String, dynamic>;
} on PlatformException catch (e) {
throw Exception('Failed to get temperature: ${e.message}');
}
}
5、常见问题解决
1. 找不到符号错误
错误信息:undefined reference to 'adjustSleepTemp' 解决方案 1.确保静态库中确实包含该符号:
llvm-nm -g libBleSDK.a | grep adjustSleepTemp
2.检查头文件是否正确定义了函数 3.确认链接顺序正确
2.CMake 版本问题
解决方案
方法 1: 安装指定版本的 CMake(推荐)
打开 Android Studio
- 进入 Preferences > Appearance & Behavior > System Settings > Android SDK
- 切换到 "SDK Tools" 标签页
安装 CMake 3.10.2
- 勾选 "Show Package Details"
- 在 CMake 部分找到 3.10.2 版本
- 勾选并点击 "Apply" 安装
验证安装
安装完成后,CMake 3.10.2 将位于:
~/Library/Android/sdk/cmake/3.10.2.4988404/
- 先清理项目:
flutter clean
rm -rf android/build android/.gradle
2.按照上面安装CMake 3.10.2 3.修改 local.properties
# android/local.properties
cmake.dir=/Users/xxx/Library/Android/sdk/cmake/3.10.2.4988404
4.更新 build.gradle(可选)
// android/app/build.gradle
android {
externalNativeBuild {
cmake {
// 指定版本(确保与安装的一致)
version "3.10.2"
}
}
}
如果使用 NDK,确保版本兼容
在 android/app/build.gradle 中添加:
android {
ndkVersion "25.1.8937393" // 使用与您系统匹配的版本
}
检查 CMakeLists.txt
确保 CMakeLists.txt 中没有硬编码的版本要求:
# 避免这样的严格版本要求
cmake_minimum_required(VERSION 3.10.2)
# 改为更灵活的版本要求
cmake_minimum_required(VERSION 3.10)
验证修复 1.检查 CMake 版本是否正确识别:
cd android
./gradlew --info | grep "Cmake"
应该显示:
Using Cmake from /Users/xxx/Library/Android/sdk/cmake/3.10.2.4988404/bin/cmake
2.检查项目配置:
flutter doctor -v
检查静态库符号
# 检查静态库是否包含所需符号
cd /Users/x'x'x/Documents/android/2301_x6a01/android/src/main/cpp/libs/arm64-v8a
nm -g libBleSDK.a | grep -E 'adjustSleepTemp|getTemperatureResult'
如果没有任何输出,说明静态库编译有问题,需要重新编译静态库并确保:
- 函数没有声明为 static
- 编译时没有使用 -fvisibility=hidden 隐藏符号