一次 Rust 并与 Android、iOS 和 Flutter 共享

2025-05-26

一次 Rust 并与 Android、iOS 和 Flutter 共享

如果我告诉你,你可以在AndroidiOS甚至Flutter中使用同样高效的代码,你会怎么想?在本文中,我们将看到如何使用Rust实现这一点。

但是,我们为什么想要这样的东西?

想象一下,你有一个移动应用需要处理一些音频来获取用户信息,但你不想将音频发送到服务器进行处理。你希望保护用户的隐私。在这种情况下,避免为AndroidiOS分别编写一个库是合理的。这样可以避免我们维护两个不同的代码库,并减少出现更多 bug 的可能性。

这很棒,但我们该怎么做呢?那就来试试Rust吧。有了Rust,你不仅可以在多个平台之间共享同一段代码,还能享受到由此带来的性能提升。

我们要做什么

我们将编写一个简单的共享Rust库并将其编译到AndroidiOS,作为奖励,我们还将使用相同的代码编写一个Flutter插件。

如您所见,本文的范围非常广泛,因此我们将尽量使所有内容保持井然有序。

您还可以在查看相关的 GitHub 存储库的同时阅读这篇文章。

搭建项目脚手架

让我们首先创建一个名为 的文件夹rust-for-android-ios-flutter,并在其中创建四个文件夹(androidiosflutter& rust):

mkdir rust-for-android-ios-flutter
cd rust-for-android-ios-flutter
mkdir ios android rust flutter

Enter fullscreen mode Exit fullscreen mode

一旦我们有了它,只需cd进入rust文件夹并创建一个名为的新Rustrustylib

cd rust
cargo init --name rustylib --lib

Enter fullscreen mode Exit fullscreen mode

这个库只有一个函数,它将接收一个string作为参数并返回一个新的string。基本上,只是一个。但你可以把它想象成一个函数,它可以作为一个完全用RustHello, World!编写的更复杂进程的入口点

让我们安装一些目标

为了将我们的rustylib库编译到AndroidiOS,我们需要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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

如您所见,我们还安装了cargo-lipocbindgen

Android 工具

对于Android 系统,我们必须确保正确设置了$ANDROID_HOME环境变量。macOS通常将其设置为~/Library/Android/sdk

还建议您安装Android StudioNDK。安装完成后,请确保$NDK_HOME正确设置环境变量。macOS通常应将其设置为~/Library/Android/sdk/ndk-bundle

最后,我们将安装cargo-ndk ,它负责查找正确的链接器并将Rust世界中使用的三元组转换为Android世界中使用的三元组

cargo install cargo-ndk

Enter fullscreen mode Exit fullscreen mode

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 }

Enter fullscreen mode Exit fullscreen mode

iOS 项目

现在,让我们使用Xcode创建一个iOS项目

iOS 项目

iOS中,您可以使用两种不同类型的user interface。为了演示如何使用它们,让我们创建两种不同类型的项目。

故事板

我们正在选择Storyboarduser interface命名该项目rusty-ios-classic

iOS 项目

将其保存在之前创建的ios文件夹中。

SwiftUI

现在让我们创建一个新iOS项目。但这次我们将选择SwiftUI作为我们的user interface,并将其命名为rusty-ios

iOS 项目

将其再次保存到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);
}

Enter fullscreen mode Exit fullscreen mode

#[no_mangle]属性在这里至关重要,它可以避免编译器更改函数的名称。我们希望函数的名称能够按原样导出。

还要注意,我们正在使用extern "C"。这告诉编译器该函数将从外部调用Rust,并确保使用C调用约定进行编译。

你可能想知道我们到底为什么需要这个hello_release函数。这里的关键在于看一下这个hello函数。使用CString并返回原始表示会将字符串保留在内存中,并防止它在函数结束时被释放。如果内存被释放,返回给调用者的指针现在将指向空内存或其他完全不同的位置。

为了避免内存泄漏,因为现在函数执行完成后,字符串仍然会保留,我们必须提供一个hello_release函数,该函数接受一个指向 a 的指针C string并释放该内存。如果我们不想惹麻烦,务必记住在代码中调用此函数。如果仔细观察这个函数,您会注意到它利用了内存管理的方式,即利用函数的作用域来释放指针。iOSRust

该代码将是我们在项目中使用的代码iOS

为 iOS 编译

在我们为iOS编译库之前,我们将生成一个C header可以作为我们代码桥梁的Swift,以便能够调用我们的Rust代码。

我们将利用cbindgen来实现这一点:

cd rust
cbindgen src/lib.rs -l c > rustylib.h

Enter fullscreen mode Exit fullscreen mode

这应该生成一个名为的文件,rustylib.h其中包含以下代码:

#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

char *hello(const char *to);

void hello_release(char *s);

Enter fullscreen mode Exit fullscreen mode

请注意,cbindgenC interface已经以非常方便的方式为我们自动生成了。

现在,让我们继续编译我们的Rust库,以便它可以在任何iOS项目中使用:

# it's important to not forget the release flag.
cargo lipo --release

Enter fullscreen mode Exit fullscreen mode

检查你的target/universal/release文件夹,找到一个名为 的文件librustylib.a。这就是我们将在iOS项目中使用的二进制文件。

使用 iOS 二进制文件

首先,我们要将librustylib.arustylib.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}

Enter fullscreen mode Exit fullscreen mode

您应该会看到像这样的树状视图,其中包含一个includelibs文件:

复制 iOS 文件

可以想象,每次编译新版本的库时都必须手动执行此操作Rust会非常繁琐。幸运的是,您可以使用像这样的简单脚本来自动化此过程bash

现在,您只需执行一次以下操作(如果您已按照文章所述创建了两个 iOS 项目,则只需执行两次)。

让我们在Xcoderusty-ios-classic中打开我们的项目并执行以下操作:

librustylib.a在 中添加文件General > Frameworks, Libraries and Embedded Content。确保在那里看到库的名称。如果没有显示,请重试。我不确定这是否是个Xcodebug,但大多数情况下,你需要添加两次才能正常工作。

添加库

之后,转到Build Settings选项卡,搜索search paths并添加header搜索library路径。您可以使用相对路径或使用$(PROJECT_DIR)变量来避免对本地路径进行硬编码。

添加头文件和库搜索路径

最后,让我们添加。在选项卡Objective-C Bridging header搜索bridging headerBuild Settings

添加 Objective-C 桥接头

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
}

Enter fullscreen mode Exit fullscreen mode

在Xcode中运行该项目

在这种情况下,您应该能够Hello from Rust: Rob在用于测试应用程序的模拟器或设备中看到🚀。

显示 iOS 结果

在我们的 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)
    }
}

Enter fullscreen mode Exit fullscreen mode

如果一切顺利,您应该能够在输出窗格Hello from Rust: Rob中看到。😉

显示 iOS 经典结果

Android 项目

让我们打开Android Studio并创建我们的Android项目:File > New...> New Project > Basic Activity

创建Android项目

命名rusty-android并设置package name。我们将选择Kotlin作为默认语言和minimum API 22

创建Android项目

您最终应该得到与此类似的树视图:

Android 树形视图

JNI

如果您还记得我们讨论如何创建iOS项目时提到过,我们需要创建一个C header桥梁。在Android中,我们将使用Java Native Interface或(简称),并通过它来暴露我们的函数。构造待调用函数名称的JNI方式遵循特定的约定: 。在我们的例子中,应该是(注意,代表限定类名中的下划线)。JNIJava_<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 库

创建 Android 库

您的Android Studio项目窗格应与下图类似:

创建 Android 库

添加一些 Rust

好的,让我们利用命名约定来创建我们的AndroidJNI功能。

让我们cd进入rust/src文件夹并创建一个新android.rs文件:

cd rust/src
echo > android.rs

Enter fullscreen mode Exit fullscreen mode

获得它后,将此代码复制到其中:

#![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()
}

Enter fullscreen mode Exit fullscreen mode

等等,这是怎么回事?🤔

我们最好停下来一会儿,稍微解释一下前面的代码。

首先,在文件顶部我们可以看到两个不同的指令:

#![cfg(target_os = "android")]
#![allow(non_snake_case)]

Enter fullscreen mode Exit fullscreen mode

第一个仅在我们为Android进行编译时启用此代码,第二个允许我们随意命名我们的函数。Rust强制执行snake_case,但我们需要选择退出以遵守JNI命名约定。

好的,但是,为什么你创建了两个不同的函数(helloDirecthello),而不是一个?🤔

嗯,答案是,我想向您展示处理Android部分的两种方法,让您决定哪一种方法更适合您的项目。

第一个函数使用jni 包而不与代码(又名 iOS 代码)交互lib.rs,第二个函数使用文件中的相同代码lib.rs

区别很明显。第一个函数比第二个函数更清晰简洁。另外,在第二个函数中,我们必须处理hello_release函数本身,而unsafe在第一个函数中则不需要。

那么,我们应该怎么做呢?我认为我会使用第一个。这是一个非常简单的例子,我们只是构建一个字符串并返回它。理想情况下,这个逻辑也应该封装在一个纯库中,供和代码Rust使用。这些代码片段应该只关心通过提供粘合,仅此而已。所以,理想情况下,在我们的例子中,我们不会在函数中重复逻辑,而是调用另一个库。iOSAndroidiOSAndroidC headersJNIJava_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;

Enter fullscreen mode Exit fullscreen mode

如果我们的目标不是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}

Enter fullscreen mode Exit fullscreen mode

与之前建议的 iOS 构建脚本类似,此脚本将帮助我们编译并将所需的文件移动到我们之前创建的Android 库中。

如果您执行此操作bash script,一旦编译过程结束,您应该能够找到类似于此的树状视图,其中有一个名为 的新创建的文件夹,jniLibs其中包含引用多个体系结构的几个子文件夹:

Android 库树视图

编写 Android 库

最后,我们将编写我们的Android 库代码并从我们的Android应用程序中使用它。

让我们在 下创建一个android/rusty-android-lib/src/main/java/com/robertohuertas/rusty_android_lib名为 的新文件。请注意,该名称必须与我们在库rusty.kt定义函数时使用的名称相同JNIRust

复制以下代码进去:

package com.robertohuertas.rusty_android_lib

external fun hello(to: String): String
external fun helloDirect(to: String): String

fun loadRustyLib() {
    System.loadLibrary("rustylib")
}

Enter fullscreen mode Exit fullscreen mode

这里,我们声明了两个与 Rust 函数(记住我们在 Rust 代码中给它们起的名字)对应的签名,以及一个将被调用来动态加载库的函数。注意,我们使用的不是库的名称(librustylib.so),而是我们给 crate 起的名字。

编译Android库

如果您想生成一个.aar可供任何Android应用使用的文件,只需使用Android StudioGradle的标签页,找到名为 的任务。右键单击它并选择。这将编译您的库,您可以在 找到它assembleRunandroid/rusty-android-lib/build/outputs/aar/rusty-android-lib-release.aar

Android 库 aar

使用 Android 库

在这种情况下,我们不需要将库作为.aar文件使用,因为它已经在同一个项目中了。如果您想了解如何使用它,请查看Android 文档

在我们的示例中,我们只需要将库添加为依赖项android/app/build.gradle

dependencies {
    implementation project(':rusty-android-lib')
}

Enter fullscreen mode Exit fullscreen mode

然后,在我们的Android Studio中,选择File > Sync project with Gradle files

content_main.xml现在,打开位于的文件android/app/src/main/res/layout并将添加idTextView,以便我们稍后可以引用它并以编程方式更改其值:

android:id="@+id/txt"

Enter fullscreen mode Exit fullscreen mode

之后,我们将使用位于的文件中的Android 库。打开它并写入以下内容:MainActivity.ktandroid/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)
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

大功告成!在模拟器中运行它,你应该会看到应用中有一条文字Hello from Rust: Rob。此外,如果你点击下面的按钮,snackbar会显示Hello from Rust: Rob direct。如你所见,我们同时使用了两个函数,一个调用了iOS函数,另一个只使用了jni-rscrate。

Android 应用结果

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

Enter fullscreen mode Exit fullscreen mode

Flutter 插件包的一大亮点是模板附带一个示例项目,我们可以用它来测试插件plugin是否按预期运行。无需创建新项目来测试Flutter插件。

我们基本上会使用之前的代码AndroidiOS并将其应用到我们的Flutter项目中。这应该非常简单。

导入Android库

为了使用我们在使用Android Studio之前构建的Android 库,让我们打开项目。flutter/android

然后,为了导入Android 库,请选择File > New... > New Module

导入Android库

选择import .JAR/.AAR Package

导入Android库

并使用之前生成的.AAR Package路径:

导入Android库

通过这样做,我们应该看到一个名为rusty-android-lib-releasefolder 的新文件夹.aar package,里面有一些其他文件:

导入Android库

最后打开文件夹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')
}

Enter fullscreen mode Exit fullscreen mode

添加 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()
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

导入 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/

Enter fullscreen mode Exit fullscreen mode

然后打开rusty_flutter_lib.podspec文件并添加以下行:

s.ios.vendored_library = 'libs/librustylib.a'

Enter fullscreen mode Exit fullscreen mode

添加 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")
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

连接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;
}

Enter fullscreen mode Exit fullscreen mode

测试 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'),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

之后,转到example/ios文件夹并执行以下命令:

pod install

Enter fullscreen mode Exit fullscreen mode

然后,打开example/android/app/build.gradle并将其minSdkVersion从 16 更改为 22,因为我们的库也是使用此设置构建的。

最后,打开example/pubspec.yaml文件并添加以下行以避免在构建版本时出现此问题iOS

version: 1.0.0+1

Enter fullscreen mode Exit fullscreen mode

恭喜你到达这里!👏

让我们运行示例应用程序并查看它是否正常运行。

Android应该会看到类似于下面的屏幕截图的内容:

运行 Flutter Android

同样地,在iOS

运行 Flutter iOS

感谢你读完这篇长文!希望对你有帮助!😊

参考书目

——最初于 2019 年 10 月 27 日
发表于robertohuertas.com 。

文章来源:https://dev.to/robertohuertasm/rust-once-and-share-it-with-android-ios-and-flutter-286o
PREV
如何通过上传制作流畅的 CSS 动画
NEXT
使用别名加速你的 Git 工作流程