寫在前面, 對於無源碼的項目, 理論上必須有頭文件,不然你不知道里面都定義了什麼鬼東西.
本篇雖然是寫無源碼的項目, 但實際上還是會有源碼部分, 只是通過 cmake,clang,xcodebuild,ndk 等工具編譯成 so/framework 以供 android/ios 引入
生成動態庫
整體的目錄結構是這樣的, 如果你只是要引入庫, 可以跳過這步, 這步的主要做源碼生成庫的步驟
$ tree -L 3 cpp-source
tree -L 3 cpp-source
cpp-source
├── android
│ ├── CMakeLists.txt
│ ├── build_android.sh
│ └── cmd
│ └── android.sh
├── ios
│ ├── CMakeLists.txt
│ ├── build_ios.sh
│ ├── cmd
│ │ └── ios_abi_build.sh
│ └── ios.toolchain.cmake
└── src
├── some.cpp
└── some.h
src 爲源碼
some.cpp
#include "some.h"
#include <stdint.h>
extern "C" __attribute__((visibility("default"))) __attribute__((used)) int32_t
native_add(int32_t x, int32_t y) {
return x + y;
}
android ios 分別對應平臺的 Cmake 配置文件和打包腳本
打包 android
使用 Cmake 配置, 然後通過 ndk 完成這個步驟
CmakeLists.txt:
cmake_minimum_required (VERSION 2.6) # cmake version
project(SOME) # project name
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../../android/libs/$ENV{ABI}) # set output path
aux_source_directory(${PROJECT_SOURCE_DIR}/../src SRC_FILES) # scan source code files
add_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
rm -rf ./build
a="armeabi-v7a arm64-v8a x86 x86_64"
for abi in $a;
do
export ABI=$abi
sh cmd/android.sh
done
副腳本cmd/android.sh
export NDK_HOME=$(which adb)/../../ndk-bundle # or set to your ndk home
export MAKE_PATH=build/make-cache
export TARGET_ABI=$ABI
create_makefile() {
cmake \
-DANDROID_ABI=$TARGET_ABI \
-DANDROID_PLATFORM=android-16 \
-DCMAKE_BUILD_TYPE=release \
-DANDROID_NDK=$NDK_HOME \
-DCMAKE_TOOLCHAIN_FILE=$NDK_HOME/build/cmake/android.toolchain.cmake \
-DANDROID_TOOLCHAIN=clang -B $MAKE_PATH -S .
}
create_makefile
cd $MAKE_PATH
make clean
make
只需要執行
cd cpp-source/android
./build_android.sh
就會生成對應的 so 文件
這裏直接生成到插件的 libs 目錄內了, 後續只需要引入即可, 引入的過程請看後面的引入篇
打包 ios
主要使用ios-cmake
項目提供的腳本,配合cmake
和 xcodebuild
來完成打包這個步驟
兩個腳本
build_ios.sh:
負責 cmake 和提供當前的 sdk 版本號, 調用副腳本完成打包 framework 的過程
並且將不同 abi 的二進制文件使用lipo
進行合併操作
rm -fr build
mkdir build
cd build
cmake .. -G Xcode -DCMAKE_TOOLCHAIN_FILE=../ios.toolchain.cmake -DPLATFORM=OS64COMBINED
cmake --build . --config Release --target install
cd ..
DEVICE=$(xcodebuild -showsdks|grep "iphoneos"| awk '{print $4}')
ABI=$DEVICE sh cmd/ios_abi_build.sh
SIMU=$(xcodebuild -showsdks|grep "iphonesimulator"| awk '{print $6}')
ABI=$SIMU sh cmd/ios_abi_build.sh
cd build/output
cp -rf $DEVICE fat
lipo -create $DEVICE/Release/some.framework/some $SIMU/Release/some.framework/some -output fat/Release/some.framework/some
cd ../..
cp -rf build/output/fat/Release/some.framework ../../ios
cmd/ios_abi_build.sh
:
負責調用xcodebuild
完成構建 framework 的過程
echo "build ios $ABI"
cd build
cmake .. -G Xcode -DCMAKE_TOOLCHAIN_FILE=../ios.toolchain.cmake -DPLATFORM=OS64COMBINED
cmake --build . --config Release --target install
# xcodebuild -project SOME.xcodeproj -configuration Release -sdk $ABI -alltargets clean build
xcodebuild OTHER_CFLAGS="-fembed-bitcode" -project SOME.xcodeproj -configuration Release -sdk $ABI -alltargets clean build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
只需要執行下面的命令即可打出一個 fat 的包給模擬器和真機同時可用
cd cpp-source/ios
./build_ios.sh
插件項目
現在開始, 假裝沒有源碼, 只有 so 和 framework
android
因爲 so 是分 abi 在 libs 目錄下的, 而 so 庫的默認目錄應該是 jniLibs 目錄
所以需要修改 gradle 以引入 so 庫
android/build.gradle
android {
// ...
sourceSets{
// ...
main.jniLibs.srcDirs = ['libs']
}
}
這樣就完成了 so 庫引入的過程
ios
修改flutter_no_cpp_src.podspec
s.vendored_frameworks = 'some.framework'
這樣就完成了 ios 庫的引入
dart
import 'dart:async';
import 'package:flutter/services.dart';
import 'dart:ffi'; // For FFI
import 'dart:io'; // For Platform.isX
final DynamicLibrary nativeAddLib = Platform.isAndroid
? DynamicLibrary.open("libsome.so")
: DynamicLibrary.open("some.framework/some");
final int Function(int x, int y) nativeAdd = nativeAddLib
.lookup<NativeFunction<Int32 Function(Int32, Int32)>>("native_add")
.asFunction();
這樣nativeAdd
方法就是調用前面的c++
方法來完成 a+b 的過程
靜態庫的問題
因爲 android 只支持 so 動態庫, 不支持靜態庫, 所以略過不表
而 ios 則同時支持靜態和動態庫, 這裏暫時使用的是 framework 形式的動態庫, 靜態庫的話, 可以搜索下如何轉化成動態庫並打包成 framework 即可
後記
以上