一次 Rust 并与 Android、iOS 和 Flutter 共享
如果我告诉你,你可以在Android、iOS甚至Flutter中使用同样高效的代码,你会怎么想?在本文中,我们将看到如何使用Rust实现这一点。
但是,我们为什么想要这样的东西?
想象一下,你有一个移动应用需要处理一些音频来获取用户信息,但你不想将音频发送到服务器进行处理。你希望保护用户的隐私。在这种情况下,避免为Android和iOS分别编写一个库是合理的。这样可以避免我们维护两个不同的代码库,并减少出现更多 bug 的可能性。
这很棒,但我们该怎么做呢?那就来试试Rust吧。有了Rust,你不仅可以在多个平台之间共享同一段代码,还能享受到由此带来的性能提升。
我们要做什么
我们将编写一个简单的共享Rust库并将其编译到Android和iOS,作为奖励,我们还将使用相同的代码编写一个Flutter插件。
如您所见,本文的范围非常广泛,因此我们将尽量使所有内容保持井然有序。
您还可以在查看相关的 GitHub 存储库的同时阅读这篇文章。
搭建项目脚手架
让我们首先创建一个名为 的文件夹rust-for-android-ios-flutter
,并在其中创建四个文件夹(android
、ios
、flutter
& rust
):
mkdir rust-for-android-ios-flutter
cd rust-for-android-ios-flutter
mkdir ios android rust flutter
一旦我们有了它,只需cd
进入rust
文件夹并创建一个名为的新Rust库rustylib
:
cd rust
cargo init --name rustylib --lib
这个库只有一个函数,它将接收一个string
作为参数并返回一个新的string
。基本上,只是一个。但你可以把它想象成一个函数,它可以作为一个完全用RustHello, World!
编写的更复杂进程的入口点。
让我们安装一些目标
为了将我们的rustylib
库编译到Android和iOS,我们需要targets
在我们的机器上安装一些:
# Android targets
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
# iOS targets
rustup target add aarch64-apple-ios armv7-apple-ios armv7s-apple-ios x86_64-apple-ios i386-apple-ios
iOS 工具
对于iOS,我们必须确保我们的计算机上安装了XcodeXcode build tools
并且已经设置好。
# install the Xcode build tools.
xcode-select --install
# this cargo subcommand will help you create a universal library for use with iOS.
cargo install cargo-lipo
# this tool will let you automatically create the C/C++11 headers of the library.
cargo install cbindgen
如您所见,我们还安装了cargo-lipo和cbindgen。
Android 工具
对于Android 系统,我们必须确保正确设置了$ANDROID_HOME
环境变量。macOS
通常将其设置为~/Library/Android/sdk
。
还建议您安装Android Studio和NDK。安装完成后,请确保$NDK_HOME
正确设置环境变量。macOS
通常应将其设置为~/Library/Android/sdk/ndk-bundle
。
最后,我们将安装cargo-ndk ,它负责查找正确的链接器并将Rust世界中使用的三元组转换为Android世界中使用的三元组:
cargo install cargo-ndk
Rust 库配置
下一步是修改我们的Cargo.toml
。确保它看起来类似于:
[package]
name = "rustylib"
version = "0.1.0"
authors = ["Roberto Huertas <roberto.huertas@outlook.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "rustylib"
# this is needed to build for iOS and Android.
crate-type = ["staticlib", "cdylib"]
# this dependency is only needed for Android.
[target.'cfg(target_os = "android")'.dependencies]
jni = { version = "0.13.1", default-features = false }
iOS 项目
在iOS中,您可以使用两种不同类型的user interface
。为了演示如何使用它们,让我们创建两种不同类型的项目。
故事板
我们正在选择Storyboard
并user interface
命名该项目rusty-ios-classic
。
将其保存在之前创建的ios
文件夹中。
SwiftUI
现在让我们创建一个新iOS
项目。但这次我们将选择SwiftUI
作为我们的user interface
,并将其命名为rusty-ios
。
将其再次保存到ios
文件夹中。
您的树视图应该类似于这个:
编写我们的第一个 Rust 代码
现在,转到Rust
项目,打开lib.rs
文件并确保它看起来完全像这样:
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
#[no_mangle]
pub unsafe extern "C" fn hello(to: *const c_char) -> *mut c_char {
let c_str = CStr::from_ptr(to);
let recipient = match c_str.to_str() {
Ok(s) => s,
Err(_) => "you",
};
CString::new(format!("Hello from Rust: {}", recipient))
.unwrap()
.into_raw()
}
#[no_mangle]
pub unsafe extern "C" fn hello_release(s: *mut c_char) {
if s.is_null() {
return;
}
CString::from_raw(s);
}
该#[no_mangle]
属性在这里至关重要,它可以避免编译器更改函数的名称。我们希望函数的名称能够按原样导出。
还要注意,我们正在使用extern "C"
。这告诉编译器该函数将从外部调用Rust
,并确保使用C
调用约定进行编译。
你可能想知道我们到底为什么需要这个hello_release
函数。这里的关键在于看一下这个hello
函数。使用CString
并返回原始表示会将字符串保留在内存中,并防止它在函数结束时被释放。如果内存被释放,返回给调用者的指针现在将指向空内存或其他完全不同的位置。
为了避免内存泄漏,因为现在函数执行完成后,字符串仍然会保留,我们必须提供一个hello_release
函数,该函数接受一个指向 a 的指针C string
并释放该内存。如果我们不想惹麻烦,务必记住在代码中调用此函数。如果仔细观察这个函数,您会注意到它利用了内存管理的方式,即利用函数的作用域来释放指针。iOS
Rust
该代码将是我们在项目中使用的代码iOS
。
为 iOS 编译
在我们为iOS编译库之前,我们将生成一个C header
可以作为我们代码桥梁的库Swift
,以便能够调用我们的Rust
代码。
我们将利用cbindgen来实现这一点:
cd rust
cbindgen src/lib.rs -l c > rustylib.h
这应该生成一个名为的文件,rustylib.h
其中包含以下代码:
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
char *hello(const char *to);
void hello_release(char *s);
请注意,cbindgenC interface
已经以非常方便的方式为我们自动生成了。
现在,让我们继续编译我们的Rust
库,以便它可以在任何iOS项目中使用:
# it's important to not forget the release flag.
cargo lipo --release
检查你的target/universal/release
文件夹,找到一个名为 的文件librustylib.a
。这就是我们将在iOS项目中使用的二进制文件。
使用 iOS 二进制文件
首先,我们要将librustylib.a
和rustylib.h
文件复制到ios
文件夹中:
# we're still in the `rust` folder so...
inc=../ios/include
libs=../ios/libs
mkdir ${inc}
mkdir ${libs}
cp rustylib.h ${inc}
cp target/universal/release/librustylib.a ${libs}
您应该会看到像这样的树状视图,其中包含一个include
和libs
文件:
可以想象,每次编译新版本的库时都必须手动执行此操作Rust
会非常繁琐。幸运的是,您可以使用像这样的简单脚本来自动化此过程。bash
现在,您只需执行一次以下操作(如果您已按照文章所述创建了两个 iOS 项目,则只需执行两次)。
让我们在Xcoderusty-ios-classic
中打开我们的项目并执行以下操作:
librustylib.a
在 中添加文件General > Frameworks, Libraries and Embedded Content
。确保在那里看到库的名称。如果没有显示,请重试。我不确定这是否是个Xcode
bug,但大多数情况下,你需要添加两次才能正常工作。
之后,转到Build Settings
选项卡,搜索search paths
并添加header
搜索library
路径。您可以使用相对路径或使用$(PROJECT_DIR)
变量来避免对本地路径进行硬编码。
最后,让我们添加。在选项卡中Objective-C Bridging header
搜索:bridging header
Build Settings
rusty-ios
如果您想尝试两种类型的iOS项目,请对我们的项目重复相同的操作。
在我们的 rusty-ios 项目中
SwiftUI
如果您使用作为用户界面的项目,则打开该ContentView.swift
文件并使其看起来像这样:
import SwiftUI
struct ContentView: View {
let s = getName()
var body: some View {
Text(s)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
func getName() -> String {
let result = hello("Rob")
let sr = String(cString: result!)
// IMPORTANT: once we get the result we have to release the pointer.
hello_release(UnsafeMutablePointer(mutating: result))
return sr
}
在Xcode中运行该项目。
在这种情况下,您应该能够Hello from Rust: Rob
在用于测试应用程序的模拟器或设备中看到🚀。
在我们的 rusty-ios-classic 项目中
如果您使用带有用户界面的项目Storyboard
,请打开该ViewController.swift
文件并使其看起来像这样:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let result = hello("Rob")
let s_result = String(cString: result!)
// IMPORTANT: once we get the result we have to release the pointer.
hello_release(UnsafeMutablePointer(mutating: result))
print(s_result)
}
}
如果一切顺利,您应该能够在输出窗格Hello from Rust: Rob
中看到。😉
Android 项目
让我们打开Android Studio并创建我们的Android项目:File > New...> New Project > Basic Activity
。
命名rusty-android
并设置package name
。我们将选择Kotlin
作为默认语言和minimum API 22
。
您最终应该得到与此类似的树视图:
JNI
如果您还记得我们讨论如何创建iOS项目时提到过,我们需要创建一个C header
桥梁。在Android中,我们将使用Java Native Interface
或(简称),并通过它来暴露我们的函数。构造待调用函数名称的JNI
方式遵循特定的约定: 。在我们的例子中,应该是(注意,代表限定类名中的下划线)。JNI
Java_<domain>_<qualified_classname>_<methodname>
Java_com_robertohuertas_rusty_1android_MainActivity_hello
_1
_
可以想象,如果我们必须以如此特定的方式命名函数,那么如果我们想在其他Android应用中重用相同的代码,就会出现问题。不过,我们还有几种替代方案。我们可以使用某种遵循相同特定域和类命名的代理类,并将其包含在每个项目中,或者我们可以创建一个Android 库并在任何地方使用它。
在我们的例子中,我们将创建一个Android 库。
创建 Android 库
在Android Studio中,File > New > New Module...
然后选择Android Library
。
您的Android Studio项目窗格应与下图类似:
添加一些 Rust
好的,让我们利用命名约定来创建我们的AndroidJNI
功能。
让我们cd
进入rust/src
文件夹并创建一个新android.rs
文件:
cd rust/src
echo > android.rs
获得它后,将此代码复制到其中:
#![cfg(target_os = "android")]
#![allow(non_snake_case)]
use crate::hello;
use jni::objects::{JClass, JString};
use jni::sys::jstring;
use jni::JNIEnv;
use std::ffi::CString;
// NOTE: RustKt references the name rusty.kt, which will be the kotlin file exposing the functions below.
// Remember the JNI naming conventions.
#[no_mangle]
pub extern "system" fn Java_com_robertohuertas_rusty_1android_1lib_RustyKt_helloDirect(
env: JNIEnv,
_: JClass,
input: JString,
) -> jstring {
let input: String = env
.get_string(input)
.expect("Couldn't get Java string!")
.into();
let output = env
.new_string(format!("Hello from Rust: {}", input))
.expect("Couldn't create a Java string!");
output.into_inner()
}
#[allow(clippy::similar_names)]
#[no_mangle]
pub extern "system" fn Java_com_robertohuertas_rusty_1android_1lib_RustyKt_hello(
env: JNIEnv,
_: JClass,
input: JString,
) -> jstring {
let java_str = env.get_string(input).expect("Couldn't get Java string!");
// we call our generic func for iOS
let java_str_ptr = java_str.as_ptr();
let result = unsafe { hello(java_str_ptr) };
// freeing memory from CString in ios function
// if we call hello_release we won't have access to the result
let result_ptr = unsafe { CString::from_raw(result) };
let result_str = result_ptr.to_str().unwrap();
let output = env
.new_string(result_str)
.expect("Couldn't create a Java string!");
output.into_inner()
}
等等,这是怎么回事?🤔
我们最好停下来一会儿,稍微解释一下前面的代码。
首先,在文件顶部我们可以看到两个不同的指令:
#![cfg(target_os = "android")]
#![allow(non_snake_case)]
第一个仅在我们为Android进行编译时启用此代码,第二个允许我们随意命名我们的函数。Rust
强制执行snake_case
,但我们需要选择退出以遵守JNI
命名约定。
好的,但是,为什么你创建了两个不同的函数(helloDirect
和hello
),而不是一个?🤔
嗯,答案是,我想向您展示处理Android部分的两种方法,让您决定哪一种方法更适合您的项目。
第一个函数使用jni 包而不与代码(又名 iOS 代码)交互lib.rs
,第二个函数使用文件中的相同代码lib.rs
。
区别很明显。第一个函数比第二个函数更清晰简洁。另外,在第二个函数中,我们必须处理hello_release
函数本身,而unsafe
在第一个函数中则不需要。
那么,我们应该怎么做呢?我认为我会使用第一个。这是一个非常简单的例子,我们只是构建一个字符串并返回它。理想情况下,这个逻辑也应该封装在一个纯库中,供和代码Rust
使用。这些代码片段应该只关心通过和为和提供粘合,仅此而已。所以,理想情况下,在我们的例子中,我们不会在函数中重复逻辑,而是调用另一个库。iOS
Android
iOS
Android
C headers
JNI
Java_com_robertohuertas_rusty_1android_1lib_RustyKt_helloDirect
无论如何,为了知道您有几种选择,我认为探索所有方法是件好事。😜
还有一件重要的事情。请注意,我们使用system
而不是 来导出函数C
。这只是为了阻止cbindgen为这些Android函数生成签名。
但是等等,这不行!我们还没有暴露我们的android
模块。
将其添加到lib.rs
文件:
// add it below the use declarations.
#[cfg(target_os = "android")]
mod android;
如果我们的目标不是Android ,该cfg
属性将阻止我们刚刚创建的模块进行编译。android
为Android编译
让我们准备为Android编译代码。
scripts
在文件夹内创建一个文件夹rust
,并添加一个名为的文件,android_build.sh
内容如下:
#!/usr/bin/env bash
# set the version to use the library
min_ver=22
# verify before executing this that you have the proper targets installed
cargo ndk --target aarch64-linux-android --android-platform ${min_ver} -- build --release
cargo ndk --target armv7-linux-androideabi --android-platform ${min_ver} -- build --release
cargo ndk --target i686-linux-android --android-platform ${min_ver} -- build --release
cargo ndk --target x86_64-linux-android --android-platform ${min_ver} -- build --release
# moving libraries to the android project
jniLibs=../android/rusty-android/rusty-android-lib/src/main/jniLibs
libName=libdevicers.so
rm -rf ${jniLibs}
mkdir ${jniLibs}
mkdir ${jniLibs}/arm64-v8a
mkdir ${jniLibs}/armeabi-v7a
mkdir ${jniLibs}/x86
mkdir ${jniLibs}/x86_64
cp target/aarch64-linux-android/release/${libName} ${jniLibs}/arm64-v8a/${libName}
cp target/armv7-linux-androideabi/release/${libName} ${jniLibs}/armeabi-v7a/${libName}
cp target/i686-linux-android/release/${libName} ${jniLibs}/x86/${libName}
cp target/x86_64-linux-android/release/${libName} ${jniLibs}/x86_64/${libName}
与之前建议的 iOS 构建脚本类似,此脚本将帮助我们编译并将所需的文件移动到我们之前创建的Android 库中。
如果您执行此操作bash script
,一旦编译过程结束,您应该能够找到类似于此的树状视图,其中有一个名为 的新创建的文件夹,jniLibs
其中包含引用多个体系结构的几个子文件夹:
编写 Android 库
最后,我们将编写我们的Android 库代码并从我们的Android应用程序中使用它。
让我们在 下创建一个android/rusty-android-lib/src/main/java/com/robertohuertas/rusty_android_lib
名为 的新文件。请注意,该名称必须与我们在库中rusty.kt
定义函数时使用的名称相同。JNI
Rust
复制以下代码进去:
package com.robertohuertas.rusty_android_lib
external fun hello(to: String): String
external fun helloDirect(to: String): String
fun loadRustyLib() {
System.loadLibrary("rustylib")
}
这里,我们声明了两个与 Rust 函数(记住我们在 Rust 代码中给它们起的名字)对应的签名,以及一个将被调用来动态加载库的函数。注意,我们使用的不是库的名称(librustylib.so),而是我们给 crate 起的名字。
编译Android库
如果您想生成一个.aar
可供任何Android应用使用的文件,只需使用Android StudioGradle
的标签页,找到名为 的任务。右键单击它并选择。这将编译您的库,您可以在 找到它。assemble
Run
android/rusty-android-lib/build/outputs/aar/rusty-android-lib-release.aar
使用 Android 库
在这种情况下,我们不需要将库作为.aar
文件使用,因为它已经在同一个项目中了。如果您想了解如何使用它,请查看Android 文档。
在我们的示例中,我们只需要将库添加为依赖项android/app/build.gradle
:
dependencies {
implementation project(':rusty-android-lib')
}
然后,在我们的Android Studio中,选择File > Sync project with Gradle files
。
content_main.xml
现在,打开位于的文件android/app/src/main/res/layout
并将添加id
到TextView
,以便我们稍后可以引用它并以编程方式更改其值:
android:id="@+id/txt"
之后,我们将使用位于的文件中的Android 库。打开它并写入以下内容:MainActivity.kt
android/app/src/main/java/com/robertohuertas/rust_android
package com.robertohuertas.rusty_android
import android.os.Bundle
import com.google.android.material.snackbar.Snackbar
import androidx.appcompat.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import android.widget.TextView
import kotlinx.android.synthetic.main.activity_main.*
import com.robertohuertas.rusty_android_lib.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
loadRustyLib()
findViewById<TextView>(R.id.txt).let {
it?.text = hello("Rob")
}
var greeting2 = helloDirect("Rob Direct")
fab.setOnClickListener { view ->
Snackbar.make(view, greeting2, Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
return when (item.itemId) {
R.id.action_settings -> true
else -> super.onOptionsItemSelected(item)
}
}
}
大功告成!在模拟器中运行它,你应该会看到应用中有一条文字Hello from Rust: Rob
。此外,如果你点击下面的按钮,snackbar
会显示Hello from Rust: Rob direct
。如你所见,我们同时使用了两个函数,一个调用了iOS
函数,另一个只使用了jni-rs
crate。
Flutter 项目
现在...让我们来获得奖励积分吧!
由于我们已经有一个Android 库和一个可运行的iOS
项目,因此在项目中完成这项工作Flutter
应该不会很困难。
基本思想是创建一个Flutter 插件包,Flutter
以便我们可以在多个项目中共享我们的代码。
那么,我们开始吧!😀
# let's use the flutter folder
cd flutter
# create a plugin project, set its namespace and its name
flutter create --template=plugin --org com.robertohuertas rusty_flutter_lib
# now you'll have a folder called rusty_flutter_lib inside the flutter folder
# for convenience, we'll move everything to the parent directory (flutter)
# this last step is completely optional.
mv rusty_flutter_lib/{.,}* .
rm -rf rusty_flutter_lib
Flutter 插件包的一大亮点是模板附带一个示例项目,我们可以用它来测试插件plugin
是否按预期运行。无需创建新项目来测试Flutter
插件。
我们基本上会使用之前的代码Android
,iOS
并将其应用到我们的Flutter
项目中。这应该非常简单。
导入Android库
为了使用我们在使用Android Studio之前构建的Android 库,让我们打开项目。flutter/android
然后,为了导入Android 库,请选择File > New... > New Module
。
选择import .JAR/.AAR Package
:
并使用之前生成的.AAR Package
路径:
通过这样做,我们应该看到一个名为rusty-android-lib-release
folder 的新文件夹.aar package
,里面有一些其他文件:
最后打开文件夹build.gradle
中的文件flutter/android
并添加新的依赖项:
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// this is the line to add including the directory holding our .aar package:
implementation fileTree(include: '*.aar', dir: 'rusty-android-lib-release')
}
添加 Android 平台代码
在您最喜欢的 IDE 中打开flutter/android/src/main/kotlin/com/robertohuertas/rusty_flutter_lib/RustyFlutterLibPlugin.kt
并将其中的代码替换为以下代码:
package com.robertohuertas.rusty_flutter_lib
// importing the Android library
import com.robertohuertas.rusty_android_lib.*
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
class RustyFlutterLibPlugin: MethodCallHandler {
companion object {
@JvmStatic
fun registerWith(registrar: Registrar) {
val channel = MethodChannel(registrar.messenger(), "rusty_flutter_lib")
channel.setMethodCallHandler(RustyFlutterLibPlugin())
// dynamically loading the android library
loadRustyLib()
}
}
override fun onMethodCall(call: MethodCall, result: Result) {
when {
call.method == "getPlatformVersion" -> result.success("Android ${android.os.Build.VERSION.RELEASE}")
call.method == "getHello" -> {
val to = call.argument<String>("to")
if (to == null) {
result.success("No to parameter found")
} else {
// we're using the helloDirect function here
// but you could also use the hello function, too.
val res = helloDirect(to)
result.success(res)
}
}
else -> result.notImplemented()
}
}
}
导入 iOS 代码
导入 iOS 代码相当容易:
# copy the header to the Classes folder
cp ../rust/rustylib.h ios/Classes
# create a new libs folder
mkdir ios/libs
# copy the universal library into the libs folder
cp ../rust/target/universal/release/librustylib.a ios/libs/
然后打开rusty_flutter_lib.podspec
文件并添加以下行:
s.ios.vendored_library = 'libs/librustylib.a'
添加 iOS 平台代码
打开flutter/ios/Classes/RustyFlutterLibPlugin.swift
文件并添加以下代码:
import Flutter
import UIKit
public class SwiftRustyFlutterLibPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "rusty_flutter_lib", binaryMessenger: registrar.messenger())
let instance = SwiftRustyFlutterLibPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if (call.method == "getPlatformVersion") {
result("iOS " + UIDevice.current.systemVersion)
} else if (call.method == "hello") {
let res = hello("Rob")
let sr = String(cString: res!)
hello_release(UnsafeMutablePointer(mutating: res))
result(sr)
} else {
result("No method found")
}
}
}
连接API和平台代码
打开flutter/lib/rusty_flutter_lib.dart
文件并在类中添加一个新static
方法RustyFlutterLib
:
static Future<String> hello({to: String}) async {
final String greetings = await _channel.invokeMethod('hello', {'to': to});
return greetings;
}
测试 Flutter 示例应用程序
让我们打开flutter/example/lib/main.dart
文件并使用我们最近创建的Flutter package
:
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:rusty_flutter_lib/rusty_flutter_lib.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
String _greeting = '';
@override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
String greeting;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
platformVersion = await RustyFlutterLib.platformVersion;
greeting = await RustyFlutterLib.hello(to: 'Rob');
} on PlatformException {
platformVersion = 'Failed to get platform version.';
greeting = 'Failed to get hello';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
_greeting = greeting;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
_greeting,
style: TextStyle(fontSize: 20),
),
Text('Running on: $_platformVersion\n'),
],
),
),
),
);
}
}
之后,转到example/ios
文件夹并执行以下命令:
pod install
然后,打开example/android/app/build.gradle
并将其minSdkVersion
从 16 更改为 22,因为我们的库也是使用此设置构建的。
最后,打开example/pubspec.yaml
文件并添加以下行以避免在构建版本时出现此问题iOS
:
version: 1.0.0+1
恭喜你到达这里!👏
让我们运行示例应用程序并查看它是否正常运行。
您Android
应该会看到类似于下面的屏幕截图的内容:
同样地,在iOS
:
感谢你读完这篇长文!希望对你有帮助!😊
参考书目
- 在 iOS 上构建和部署 Rust 库
- 在 Android 上构建和部署 Rust 库
- iOS 上的 Rust
- Android 上的 Rust
- 货物-ndk
- jni-rs
- JNI 技巧
- 创建 Android 库
——最初于 2019 年 10 月 27 日
发表于robertohuertas.com 。