完整流程 Flutter 集成 Rust 多语言跨端开发基础案例
使用Rust一处编写,横跨中Android & IOS 多端场景使用,相比较 React Native 方案更加高效。并且Rust是一门系统级编程,Rust编写也可以在其它场景下复用。
本文主要介绍了Flutter 集成Rust,所以并不会在Flutter、Rust等开发环境搭建以及Dart、Rust语言上做介绍。
考虑Android & IOS 同时使用,所以本文使用MacOS系统作为演示,如仅仅需做安卓上测试可跳过相关IOS的配置等操作。
本地环境
Flutter 版本
Rust 版本
rustc 1.61.0 (fe5b13d68 2022-05-18)
cargo 1.61.0 (a028ae42f 2022-04-29)
NDK 版本
可通过 Android Studio 安装 NDK 版本 22.1.7171670
测过着23版本之后cargo 构建编译会出问题
配置 NDK 环境变量
此处使用VSCode编辑器编辑
ANDROID_NDK_HOME 指定 NDK 安装目录
code ~/.zshrc
export ANDROID_NDK_HOME=/Users/username/Library/Android/sdk/ndk/22.1.7171670
source ~/.zshrc
生成交叉编译工具
mkdir ~/.NDK
python3 $ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py --api 21 --arch arm64 --install-dir ~/.NDK/arm64
python3 $ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py --api 16 --arch arm --install-dir ~/.NDK/arm
python3 $ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py --api 16 --arch x86 --install-dir ~/.NDK/x86
配置Rust交叉编译工具
code ~/.cargo/config
[target.aarch64-linux-android]
ar = ".NDK/arm64/bin/aarch64-linux-android-ar"
linker = ".NDK/arm64/bin/aarch64-linux-android-clang"
[target.armv7-linux-androideabi]
ar = ".NDK/arm/bin/arm-linux-androideabi-ar"
linker = ".NDK/arm/bin/arm-linux-androideabi-clang"
[target.i686-linux-android]
ar = ".NDK/x86/bin/i686-linux-android-ar"
linker = ".NDK/x86/bin/i686-linux-android-clang"
下载Rust 交叉编译的依赖
Android
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android
IOS
rustup target add aarch64-apple-ios x86_64-apple-ios
安装cargo-lipo以生成iOS通用库
cargo install cargo-lipo
Rust 项目相关
创建项目
cargo init my-app-base --lib
编写功能
使用任意编辑器打开项目,编辑src中lib.rs文件,添加如下代码,其中定义了一个hello输出world字符以及count_add_self每次调用加2
use std::os::raw::c_char;
use std::ffi::CString;
static mut COUNT: u32 = 0;
#[no_mangle]
pub unsafe extern "C" fn count_add_self() -> u32 {
COUNT += 2;
COUNT
}
#[no_mangle]
pub unsafe extern fn hello() -> *const c_char {
let s = CString::new("world").unwrap();
s.into_raw()
}
修改配置
修改 Cargo.toml 文件增加如下内容
Rust 构建出来的二进制库
在 IOS 中是静态链接进最终的程序之中,需要对构建staticlib
的支持
在 Android 是通过动态链接在运行时装在进程序运行空间的,需要对构建cdylib
的支持
[lib]
name = "hello_lib"
crate-type = ["staticlib", "cdylib"]
编译Rust得到静态库
# IOS
cargo lipo --release
# Android
cargo build --target aarch64-linux-android --release
cargo build --target armv7-linux-androideabi --release
cargo build --target i686-linux-android --release
Flutter 项目相关
使用Android Studio、VSCode或者终端创建一个Flutter项目
IOS 引入Rust静态库
-
在Flutter项目ios目录下新建Framework文件夹
-
将Rust项目中target/universal/release/libhello_lib.a 复制到 Framework 文件夹
-
在 ios 目录上右键使用 xcode 打开项目
-
在 Build Phases 中 Link Binary With Libraries 添加 Framework文件夹中 libhello_lib.a 文件
-
在 Build Settings 中 Other Linker Flags 中添加 -all_load 的参数
-
在 Build Settings 中 Architectures 中 Excluded Architectures 添加 arm64 的参数,默认应该有一个 i386 因为Rust编译后的是 arm64 架构
Android 引入Rust静态库
在Flutter项目android/app/src/main目录下新建jniLibs目录,并在新建 arm64-v8a 、 armeabi-v7a 、x86 三个文件夹
将 Rust 项目编译后 target 项目下对应架构so文件复制到 对应 架构文件夹下
添加FFi依赖
在flutter项目pubspec.yaml中添加FFI
ffi: ^2.0.0
测试调用
在 main.dart 中修改后
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'dart:io';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String? hello;
int count = 0;
void _incrementCounter() {
setState(() {
countAdd();
});
}
@override
void initState() {
super.initState();
final dylib = Platform.isAndroid ? DynamicLibrary.open('libhello_lib.so') :DynamicLibrary.process();
var result = dylib.lookupFunction<Pointer<Utf8> Function(),Pointer<Utf8> Function()>('hello');
hello = result().toDartString();
}
void countAdd() {
final dylib = Platform.isAndroid ? DynamicLibrary.open('libhello_lib.so') :DynamicLibrary.process();
var countAddSelf = dylib.lookupFunction<Pointer<Uint32> Function(),Pointer<Uint32> Function()>('count_add_self');
count = countAddSelf().address;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'以下内容为Rust侧结果',
),
Text('$hello'),
Text('$count'),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}