在 flutter 上使用 c 代码 - (二) 无源码的项目
文章目录
写在前面, 对于无源码的项目, 理论上必须有头文件,不然你不知道里面都定义了什么鬼东西.
本篇虽然是写无源码的项目, 但实际上还是会有源码部分, 只是通过 cmake,clang,xcodebuild,ndk 等工具编译成 so/framework 以供 android/ios 引入
生成动态库
整体的目录结构是这样的, 如果你只是要引入库, 可以跳过这步, 这步的主要做源码生成库的步骤
1$ tree -L 3 cpp-source
2tree -L 3 cpp-source
3cpp-source
4├── android
5│ ├── CMakeLists.txt
6│ ├── build_android.sh
7│ └── cmd
8│ └── android.sh
9├── ios
10│ ├── CMakeLists.txt
11│ ├── build_ios.sh
12│ ├── cmd
13│ │ └── ios_abi_build.sh
14│ └── ios.toolchain.cmake
15└── src
16 ├── some.cpp
17 └── some.h
src 为源码
some.cpp
1#include "some.h"
2#include <stdint.h>
3
4extern "C" __attribute__((visibility("default"))) __attribute__((used)) int32_t
5native_add(int32_t x, int32_t y) {
6 return x + y;
7}
android ios 分别对应平台的 Cmake 配置文件和打包脚本
打包 android
使用 Cmake 配置, 然后通过 ndk 完成这个步骤
CmakeLists.txt:
1cmake_minimum_required (VERSION 2.6) # cmake version
2
3project(SOME) # project name
4
5set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../../android/libs/$ENV{ABI}) # set output path
6
7aux_source_directory(${PROJECT_SOURCE_DIR}/../src SRC_FILES) # scan source code files
8
9add_library(some SHARED ${SRC_FILES}) # add source code files to library, and set build type to dynamic library.
然后有两个打包脚本
- 主脚本, 负责循环 abi, 调用副脚本, 并且完成生成的步骤
- 副脚本, 根据 abi 执行 cmake, 并且完成 make 的过程
主脚本build_android.sh
1rm -rf ./build
2a="armeabi-v7a arm64-v8a x86 x86_64"
3for abi in $a;
4do
5export ABI=$abi
6sh cmd/android.sh
7done
副脚本cmd/android.sh
1export NDK_HOME=$(which adb)/../../ndk-bundle # or set to your ndk home
2
3export MAKE_PATH=build/make-cache
4
5export TARGET_ABI=$ABI
6
7create_makefile() {
8 cmake \
9 -DANDROID_ABI=$TARGET_ABI \
10 -DANDROID_PLATFORM=android-16 \
11 -DCMAKE_BUILD_TYPE=release \
12 -DANDROID_NDK=$NDK_HOME \
13 -DCMAKE_TOOLCHAIN_FILE=$NDK_HOME/build/cmake/android.toolchain.cmake \
14 -DANDROID_TOOLCHAIN=clang -B $MAKE_PATH -S .
15}
16
17create_makefile
18
19cd $MAKE_PATH
20
21make clean
22make
只需要执行
1cd cpp-source/android
2./build_android.sh
就会生成对应的 so 文件
这里直接生成到插件的 libs 目录内了, 后续只需要引入即可, 引入的过程请看后面的引入篇
打包 ios
主要使用项目提供的脚本,配合cmake
和 xcodebuild
来完成打包这个步骤
两个脚本
build_ios.sh:
负责 cmake 和提供当前的 sdk 版本号, 调用副脚本完成打包 framework 的过程
并且将不同 abi 的二进制文件使用lipo
进行合并操作
1rm -fr build
2mkdir build
3
4cd build
5
6cmake .. -G Xcode -DCMAKE_TOOLCHAIN_FILE=../ios.toolchain.cmake -DPLATFORM=OS64COMBINED
7cmake --build . --config Release --target install
8
9cd ..
10
11DEVICE=$(xcodebuild -showsdks|grep "iphoneos"| awk '{print $4}')
12ABI=$DEVICE sh cmd/ios_abi_build.sh
13
14SIMU=$(xcodebuild -showsdks|grep "iphonesimulator"| awk '{print $6}')
15ABI=$SIMU sh cmd/ios_abi_build.sh
16
17cd build/output
18cp -rf $DEVICE fat
19lipo -create $DEVICE/Release/some.framework/some $SIMU/Release/some.framework/some -output fat/Release/some.framework/some
20
21cd ../..
22
23cp -rf build/output/fat/Release/some.framework ../../ios
cmd/ios_abi_build.sh
:
负责调用xcodebuild
完成构建 framework 的过程
1echo "build ios $ABI"
2cd build
3
4cmake .. -G Xcode -DCMAKE_TOOLCHAIN_FILE=../ios.toolchain.cmake -DPLATFORM=OS64COMBINED
5cmake --build . --config Release --target install
6
7# xcodebuild -project SOME.xcodeproj -configuration Release -sdk $ABI -alltargets clean build
8xcodebuild OTHER_CFLAGS="-fembed-bitcode" -project SOME.xcodeproj -configuration Release -sdk $ABI -alltargets clean build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
只需要执行下面的命令即可打出一个 fat 的包给模拟器和真机同时可用
1cd cpp-source/ios
2./build_ios.sh
插件项目
现在开始, 假装没有源码, 只有 so 和 framework
android
因为 so 是分 abi 在 libs 目录下的, 而 so 库的默认目录应该是 jniLibs 目录
所以需要修改 gradle 以引入 so 库
android/build.gradle
1android {
2 // ...
3 sourceSets{
4 // ...
5 main.jniLibs.srcDirs = ['libs']
6 }
7}
这样就完成了 so 库引入的过程
ios
修改flutter_no_cpp_src.podspec
1s.vendored_frameworks = 'some.framework'
这样就完成了 ios 库的引入
dart
1import 'dart:async';
2
3import 'package:flutter/services.dart';
4import 'dart:ffi'; // For FFI
5import 'dart:io'; // For Platform.isX
6
7final DynamicLibrary nativeAddLib = Platform.isAndroid
8 ? DynamicLibrary.open("libsome.so")
9 : DynamicLibrary.open("some.framework/some");
10
11final int Function(int x, int y) nativeAdd = nativeAddLib
12 .lookup<NativeFunction<Int32 Function(Int32, Int32)>>("native_add")
13 .asFunction();
这样nativeAdd
方法就是调用前面的c++
方法来完成 a+b 的过程
静态库的问题
因为 android 只支持 so 动态库, 不支持静态库, 所以略过不表
而 ios 则同时支持静态和动态库, 这里暂时使用的是 framework 形式的动态库, 静态库的话, 可以搜索下如何转化成动态库并打包成 framework 即可
后记
以上