Flutter 调用静态库

0 阅读4分钟

有没有一种需求,我写个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 文件)中来实现调用。以下是完整的实现步骤:

image.png

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}'),
            ],
          ),
        ),
      ),
    );
  }
}
关键注意事项
  1. 编译过程解析

image.png

  1. 数据类型转换
Dart 类型      | Java/Kotlin 类型 | C 类型        | 处理方式         |
| ------------ | -------------- | ----------- | ------------ |
| double       | Float          | jfloat      | 直接转换         |
| List<double> | Float[]        | jfloatArray | 通过 JNI 函数转换  |
| 自定义对象        | Java 对象        | jobject     | 手动创建 Java 对象 |
| 结构体          | Java 类         | struct      | 映射为 Java 对象

image.png

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'

目录如下

image.png 我们直接做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;当然中途有会问题的,可以具体问题具体解决

image.png 生成静态库后,将静态库和头文件拷贝到真正需要使用的项目中 放在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'] // 静态库位置
        }
    }
}

image.png

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/
  1. 先清理项目:
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 隐藏符号