在 flutter 上使用 c 代碼 - (二) 無源碼的項目

寫在前面, 對於無源碼的項目, 理論上必須有頭文件,不然你不知道里面都定義了什麼鬼東西.

本篇雖然是寫無源碼的項目, 但實際上還是會有源碼部分, 只是通過 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.

然後有兩個打包腳本

  1. 主腳本, 負責循環 abi, 調用副腳本, 並且完成生成的步驟
  2. 副腳本, 根據 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 文件

20191105112724.png

這裏直接生成到插件的 libs 目錄內了, 後續只需要引入即可, 引入的過程請看後面的引入篇

打包 ios

主要使用ios-cmake項目提供的腳本,配合cmakexcodebuild 來完成打包這個步驟

兩個腳本

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 即可

後記

項目地址

以上

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章