Android逆向技术56——Flutter with Rust会发生什么(By LittleQ)
最近发现Flutter和Rust的讨论也都变得多了起来,然后就像了下,他们两个能否结合一下呢,于是便有了今天这一篇可以水的文章了。
环境准备
因为我们需要结合Flutter和Rust,因此,这两个基础环境的准备是必要的,下面列出一下我用到的版本,其实也就是最新版本。
-
Flutter 3.13.7 -
Cargo 1.73.0 (9c4383fb5 2023-08-26)
然后,这里我们直接采用了flutter_rust_bridge
这一个库,在使用这个库之前呢,需要做一些前期的准备,也就是提前安装一些依赖,具体细节可以参考下官方文档,根据自己需要的平台自行添加相关依赖。
搭建过程
首先,肯定是新建一个Flutter项目,这个过程就不过多描述了,因为这篇文章也不是一个教大家写Flutter应用的文章,相信能看这篇文章的,应该也会自己构建项目了。
依赖添加
在新建完成项目之后,我们需要添加一些依赖,具体如下:
dependencies:
# ... other ...
ffi: ^2.1.0
flutter_rust_bridge: ^1.82.3
dev_dependencies:
# ... other ...
ffigen: ">=8.0.0 <9.0.0"
新建Rust项目
这里,我们直接使用JetBrains公司给开发的最新的Rust的IDE,咱就是用最新的,这不是给IDE打广告蛤,大家也可以根据自己的喜好来。注意,这里需要选择Library,不要选Application,不过选了也不要紧,删一下文件就好了。
添加完成项目之后,依然需要添加一些依赖,具体如下。
[lib]
crate-type = ["cdylib", "staticlib"]
[dependencies]
flutter_rust_bridge = "1.82.3"
编写测试代码
这里,我们简单写一个对外的方法,来测试下环境是不是好的,新建一个api.rs
的文件,具体内容如下,例子参考自参考2,其实随便写点啥都行,但是感觉这个例子还比较有代表性。
pub enum Platform {
Unknown,
Android,
Ios,
Windows,
Unix,
MacIntel,
MacApple,
Wasm,
}
pub fn platform() -> Platform {
if cfg!(windows) {
Platform::Windows
} else if cfg!(target_os = "android") {
Platform::Android
} else if cfg!(target_os = "ios") {
Platform::Ios
} else if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
Platform::MacApple
} else if cfg!(target_os = "macos") {
Platform::MacIntel
} else if cfg!(target_family = "wasm") {
Platform::Wasm
} else if cfg!(unix) {
Platform::Unix
} else {
Platform::Unknown
}
}
其实,如果懒得自己做这些工作,可以直接用参考资料2当中的模板,也挺不错的。
生成桥接文件
这里,工具会帮助我们自动生成代码,来生成对应的一些桥接,首先在项目根目录,添加配置文件,否则自己要输入一坨命令,这显然是难以忍受的,具体配置文件如下。
rust_input:
- native/src/api.rs
dart_output:
- lib/bridge_generated.dart
c_output:
- ios/Runner/bridge_generated.h
extra_c_output_path:
- macos/Runner
dart_decl_output: lib/bridge_definitions.dart
dart_format_line_length: 120
wasm: true
注意,这里需要自己根据需要进行一点点调整,这里吧,我选择开启了wasm项目,所以wasm也一块给加上了,如果不需要,可以不生成。然后运行如下命令,得到自动生成的代码
flutter_rust_bridge_codegen .flutter_rust_bridge.yml
看到下面这个,就说明成功了,否则请自行检测自己有那些依赖没有装。
这里会生成一些文件,不要删掉,不要改动他们。
测试成果
我们选择,在Flutter当中调用一下我们刚才写的Rust的代码,来测试一下成果怎么样,简单编写一个测试的代码。
// ffi.dart
import 'dart:ffi';
import 'bridge_generated.dart';
import 'bridge_definitions.dart';
export 'bridge_definitions.dart';
export 'bridge_generated.dart';
import 'dart:io' as io;
const _base = 'native';
final _dylib = io.Platform.isWindows ? '$_base.dll' : 'lib$_base.so';
final Native api =
NativeImpl(io.Platform.isIOS || io.Platform.isMacOS ? DynamicLibrary.executable() : DynamicLibrary.open(_dylib));
// ffi_web.dart
import 'package:flutter_rust_bridge/flutter_rust_bridge.dart';
import 'bridge_generated.web.dart';
export 'bridge_definitions.dart';
const root = 'pkg/native';
final api = NativeImpl.wasm(
WasmModule.initialize(kind: const Modules.noModules(root: root)),
);
因为这里,我们也加了web的支持,所以说,这也写一下,其实这段代码也是在参考资料2当中借鉴出来的,在这里感谢下参考资料2的作者。接下来,就简单写一个Flutter的应用吧。
import 'dart:async';
import 'package:flutter/material.dart';
import 'ffi.dart' if (dart.library.html) 'ffi_web.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter With Rust',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter With Rust'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late Future<Platform> platform;
@override
void initState() {
super.initState();
platform = api.platform();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: FutureBuilder(
future: platform,
builder: (context, snap) {
final style = Theme.of(context).textTheme.headlineMedium;
if (snap.error != null) {
return Tooltip(
message: snap.error.toString(),
child: Text('Unknown OS', style: style),
);
}
if (snap.hasData) {
final data = snap.data as Platform;
final text = switch (data) {
Platform.Android => 'Android',
Platform.Ios => 'iOS',
Platform.MacApple => 'MacOS with Apple Silicon',
Platform.MacIntel => 'MacOS',
Platform.Windows => 'Windows',
Platform.Unix => 'Unix',
Platform.Wasm => 'the Web',
Platform.Unknown => 'Unknown OS',
};
return Tooltip(
message: text,
child: Text(text, style: style),
);
}
return const Center(child: CircularProgressIndicator());
},
),
);
}
}
Android测试
然后,我们在Android上运行一下来看看,在运行之前,需要安装cargo-ndk
这个依赖,细节版本请参考官方文档,这里我用的最新的,直接装没有啥问题,低版本Rust需要降低版本。
cargo install cargo-ndk
cargo ndk -o ../android/app/src/main/jniLibs build
第二条命令是生成需要的so文件,如果不生成,会出现找不到文件的错误,运行之后,就可以得到下面的结果了,我们就搞成功了,非常的顺利,没有什么坑。
MacOS测试
作为一个跨平台的项目,那么怎么能够只在Android平台测试呢,那么我们多尝试几个平台,接下来搞一下在MacOS上的测试,这里假设大家有MacOS的开发相关环境,并且已知MacOS开发的基础知识。
首先,我们要运行如下命令,来生成xcode项目。
cargo xcode
这里,看到生成这个文件才可以,否则就是失败了,自己检查依赖。
然后,我们需要用xcode打开这个文件,按照如下的操作执行,
接下来,我们需要将我们MacOS的工程,给链接到刚才生成的native文件,首先,我们吧我们刚才的那个项目,也就是 native/native.xcodeproj
作为子工程添加到工程项目。
然后,添加一下项目。
添加完成的效果如下,如果不是这样,那就证明你搞错了,再来一次吧。
然后,选中Runner
,之后添加依赖,链接到我们刚才搞好的库。
这里,选择我们刚才添加的库。
然后,我们需要链接一下,这里选择如下
然后选择添加这个。
这样,库就添加完成了,然后我们需要自己补一下头文件,对于MacOS,我们需要自行处理下,按照如下的操作。
然后呢,我们要在`macos/Runner/AppDelegate.swift
当中添加如下内容,防止优化给我们干掉代码。
import Cocoa
import FlutterMacOS
@NSApplicationMain
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
// 这一行是新添加的
print(dummy_method_to_enforce_bundling())
return true
}
}
之后,我们便可以运行Mac程序了,可以发现,在MacOS上也可以完美的运行。
iOS测试
对于iOS来说,和MacOS的操作类似,只不过要注意,需要选择的库不一样,同样,我们简单来看一下,有关于 native/native.xcodeproj
这个工程,之前已经改好了,因此不用再改了,我们要打开ios/Runner.xcodeproj
这个项目。
然后,还是添加native那个项目作为子项目,这里需要选择 **-staticlib
**这个,而不是之前的。
然后,这里需要添加下头文件,在这个文件当中
// ios/Runner/Runner-Bridging-Header.h
#import "bridge_generated.h"
然后,还是在 ios/Runner/AppDelegate.swift
当中添加内容。
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 新增这一行
print(dummy_method_to_enforce_bundling())
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
这里,需要添加额外的设置,防止xcode给我们抹掉符号,具体如下。
可以发现,这个也是可以的
Web测试
web的话,需要加入wasm的支持,因此按照如下方式安装依赖
rustup toolchain install nightly
rustup +nightly component add rust-src
rustup +nightly target add wasm32-unknown-unknown
# either of these
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
cargo install wasm-pack
然后,这里推荐使用 flutter_rust_bridge
提供的方式运行项目。
dart run flutter_rust_bridge:serve
发现,也是可以的。
有关于window和Linux,这里就不重复尝试了,实测也是没有问题的,到这里本文就结束了,发现Rust结合Flutter确实体验比较丝滑。
参考资料
-
https://github.com/fzyzcjy/flutter_rust_bridge[1] -
https://github.com/Desdaemon/flutter_rust_bridge_template[2] -
https://docs.flutter.dev/development/platform-integration/ios/c-interop#stripping-ios-symbols[3]
Reference
https://github.com/fzyzcjy/flutter_rust_bridge: https://github.com/fzyzcjy/flutter_rust_bridge
[2]https://github.com/Desdaemon/flutter_rust_bridge_template: https://github.com/Desdaemon/flutter_rust_bridge_template
[3]https://docs.flutter.dev/development/platform-integration/ios/c-interop#stripping-ios-symbols: https://docs.flutter.dev/development/platform-integration/ios/c-interop#stripping-ios-symbols
原文始发于微信公众号(安全后厨):Android逆向技术56——Flutter with Rust会发生什么(By LittleQ)
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论