原文链接 - https://robertohuertas.com/2019/10/27/rust-for-android-ios-flutter/
原文作者 - Roberto Huertas

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第1张图片

What if I told you that you could use the same very performant code inAndroid,iOSor even inFlutter. In this article, we’ll see how to achieve this withRust.

But, why would we want something like this?

Imagine that you have a mobile app that needs to process some audio in order to get some insights about the user but you don’t want the audio to be sent to the server to be processed. You want to preserve the privacy of the user. In this kind of scenario, it would make sense to avoid having to write a specific library forAndroidand another one foriOS. Having just a shared library would save us from having to maintain two different codebases and would diminish the chance to get more bugs.

That’s nice, but how could we do something like this? EnterRust. WithRust, not only would you be able to share the same code among multiple platforms, but you could also take advantage of the boost of performance that you will get by using it.

What are we going to do

We are going to write a simple sharedRustlibrary and compile it toAndroidandiOS, and as a bonus, we will also write aFlutterplugin using the very same code.

As you can see, the scope of this article is quite broad so we’ll try to keep everything organized.

You can also read this post while taking a look at theassociated GitHub repository.

Scaffolding our project

Let’s start by creating a folder calledrust-for-android-ios-flutterand create four folders in it (android,ios,flutter&rust):

mkdir rust-for-android-ios-fluttercd rust-for-android-ios-fluttermkdir ios android rust flutter

Once we have it, justcdinto therustfolder and create a newRustlibrary calledrustylib:

cd rustcargo init --name rustylib --lib

This library will only have one function that will get astringas its argument and will return a newstring. Basically, just aHello, World!. But just think of it as a function that could work as the entry point to a more complex process completely written inRust.

Let’s install some targets

In order to compile ourrustyliblibrary toAndroidandiOSwe will need to have sometargetsinstalled in our machine:

# Android targetsrustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android# iOS targetsrustup target add aarch64-apple-ios armv7-apple-ios armv7s-apple-ios x86_64-apple-ios i386-apple-ios

Tools for iOS

ForiOSwe have to be sure that we haveXcodeinstalled in our computer and theXcode build toolsalready set up.

# 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

As you can see, we have also installedcargo-lipoandcbindgen.

Tools for Android

ForAndroid, we have to be sure that we have correctly set up the$ANDROID_HOMEenvironment variable. InmacOSthis is tipically set to~/Library/Android/sdk.

It is also recommended that you installAndroid Studioand theNDK. Once you have everything installed, ensure that the$NDK_HOMEenvironment variable is properly set. InmacOSthis should be tipically set to~/Library/Android/sdk/ndk-bundle.

Finally, we’re just going to installcargo-ndk, which handles finding the correct linkers and converting between the triples used in theRustworld to the triples used in theAndroidworld:

cargo install cargo-ndk

Rust library configuration

The next step is to modify ourCargo.toml. Just make sure it looks similar to this:

[package]name = "rustylib"version = "0.1.0"authors = ["Roberto Huertas "]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 project

Now, let’s create aniOSproject usingXcode.

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第2张图片

In iOS you can use 2 different types of user interface. As we want to show how to use both of them, let’s create two different kind of projects.

Storyboard
We’re choosing Storyboard as our user interface and we’re naming the project rusty-ios-classic.

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第3张图片

Save it in the previously creatediosfolder.

SwiftUI

Let’s create now a newiOSproject. But this time we’re going to selectSwiftUIas ouruser interfaceand name itrusty-ios.

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第4张图片

Save it again in theiosfolder.

Your treeview should look similar to this one:

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第5张图片

Writing our first Rust code

Now, go to theRustproject, open thelib.rsfile and make sure it looks exactly like this:

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);}

The#[no_mangle]attribute is vital here to avoid the compiler from changing the name of the function. We want the name of the function to be exported as it is.

Note also that we’re usingextern "C". This tells the compiler that this function will be called from outsideRustand ensures that it is compiled usingCcalling conventions.

You may be wondering why on Earth we need thishello_releasefunction. The key here is to take a look at thehellofunction. UsingCStringand returning the raw representation keeps the string in memory and prevents it from being released at the end of the function. If the memory were to be released, the pointer provided back to the caller would now be pointing to empty memory or to something else entirely.

In order to avoid a memory leak, because we have now a string that sticks around after the function has finishing executing, we have to provide thehello_releasefunction that takes a pointer to aC stringand frees that memory. It’svery important not to forget calling this functionfrom theiOScode if we don’t want to get into troubles. If you look closely at this function, you’ll notice that it leverages the way memory is managed inRustby using the function’s scope in order to free the pointer.

This code will be the one that we’ll use for ouriOSprojects.

Compiling for iOS

Before we compile the library foriOSwe’re going to generate aC headerthat will work as a_bridge_for ourSwiftcode to be able to call ourRustcode.

We’ll be leveragingcbindgenfor this:

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

This should generate a file calledrustylib.hcontaining the following code:

#include #include #include #include char *hello(const char *to);void hello_release(char *s);

Note thatcbindgenhas automatically generated theC interfacefor us in a very convenient way.

Now, let’s proceed tocompileourRustlibrary so it can be consumed in anyiOSproject:

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

Check out yourtarget/universal/releasefolder and look for a file calledlibrustylib.a. That’s the binary we’re going to use in ouriOSprojects.

Using the iOS binary

First, we’re going to copy ourlibrustylib.aandrustylib.hfiles into theiosfolder:

# we're still in the `rust` folder so...inc=../ios/includelibs=../ios/libsmkdir ${inc}mkdir ${libs}cp rustylib.h ${inc}cp target/universal/release/librustylib.a ${libs}

You should be seeing a treeview like this one, with anincludeand alibsfile:

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第6张图片

As you can imagine, having to manually do this every time you have to compile a new version of yourRustlibrary would be very tedious. Fortunately, you canautomate this processby using a simplebashscript likethis one.

Now, the following is something that you will need to doonly once(twice if you have created two iOS projects as the article described).

Let’s open ourrusty-ios-classicproject inXcodeand let’s do the following:

Add thelibrustylib.afile in theGeneral > Frameworks, Libraries and Embedded Contentsection. Ensure that you see the name of the library there. If it is not shown, try it again. I’m not sure if it’s anXcodebug but most of the times you’ll need to add it twice for it to work correctly.

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第7张图片

After that, go to theBuild Settingstab, search forsearch pathsand add theheaderandlibrarysearch paths. You can use relative paths or use the$(PROJECT_DIR)variable to avoid hardcoding your local path.

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第8张图片

Finally, let’s add theObjective-C Bridging header. Search forbridging headerin theBuild Settingstab:

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第9张图片

Repeat the same for ourrusty-iosproject in case you want to try both types ofiOSprojects.

In our rusty-ios project

If you’re using the project that usesSwiftUIas a user interface, then open theContentView.swiftfile and make it look like this:

import SwiftUIstruct 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}

Run the project inXcode.

In this case, you should be able to seeHello from Rust: Robin the emulator or device you’re using to test the app:rocket:.

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第10张图片

In our rusty-ios-classic project
In case you’re using the project with a Storyboard user interface, open the ViewController.swift file and make it look like this:

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)}

}
Run the project in Xcode.

If everything is fine, you should be able to see Hello from Rust: Rob in the output pane.

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第11张图片

Android project

Let’s openAndroid Studioand let’s create ourAndroidproject:File > New...> New Project > Basic Activity.

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第12张图片

Name itrusty-androidand set thepackagename. We’ll chooseKotlinas our default language andminimum API 22.

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第13张图片

You should end up with a treeview similar to this one:

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第14张图片

JNI

If you remember when we discussed about how to create ouriOSproject, we needed to create aC headerworking as a bridge. InAndroidwe will leverage theJava Native InterfaceorJNIfor short and we will expose our functions through it. The way thatJNIconstructs the name of the function that will be called follows a specific convention:Java___. In our case, that would beJava_com_robertohuertas_rusty_1android_MainActivity_hello(note that_1represents underscores_in the qualified class name).

As you can imagine, if we have to name our functions in such a specific way, this can pose a problem if we want to reuse this very same code in otherAndroidapps. We have several alternatives, though. We can use some sort of proxy class that follows the same specific domain and class naming and include it in every project or we can create anAndroid Libraryand use it everywhere.

In our case, we’re going to create anAndroid Library.

Creating an Android Library

InAndroid Studio,File > New > New Module.... Then chooseAndroid Library.

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第15张图片

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第16张图片

Your Android Studio project pane should look similar to the one below:

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第17张图片

Adding a little bit more Rust

Ok, so let’s create ourAndroidfunctions by leveraging theJNInaming conventions.

Let’scdinto ourrust/srcfolder and create a newandroid.rsfile:

cd rust/srcecho > android.rs

Once you have it, copy this code into it:

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

Wait, what’s going on here?:thinking:

We’d better stop for a moment and explain a little bit the previous code.

First of all, on the top of the file we can see two different directives:

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

The first one will enable this code only when we’re compiling forAndroidand the second one will allow us to name our functions however we want.Rustenforcessnake_casebut we need to opt out of this in order to comply with theJNInaming conventions.

Ok, but then, why have you created two different functions (helloDirectandhello) and not just one?:thinking:

Well, the answer is that I wanted to show you two ways of handling theAndroidpart and let you decide which one is more convenient for your kind of project.

The first function uses thejni cratewithout interacting with thelib.rscode (a.k.a. iOS code) and the second one uses the same code we have in thelib.rsfile.

The difference is clear. The first function is way clearer and more succint than the second one. Plus, in the second one, we have to deal with thehello_releasefunction andunsafewhile in the first one we don’t.

So, what we should do? In my opinion, I would use the first one. This is a super simple example where we’re just building a string and returning it. Ideally, this logic should be also encapsulated in a pureRustlibrary that would be consumed by both theiOSand theAndroidcode. These pieces of code should be only concerned about providing the glue to theiOSandAndroidland viaC headersandJNIand that’s it. So, ideally, in our example, instead of duplicating the logic in theJava_com_robertohuertas_rusty_1android_1lib_RustyKt_helloDirectfunction we would call another library.

Anyway, for the sake of knowing that you have several options, I think it’s good to explore all the approaches.:stuck_out_tongue_winking_eye:

One more important thing. Note that we’re exporting our functions withsysteminstead ofC. This is just to stopcbindgenfrom generating signatures for theseAndroidfunctions.

But wait, this won’t work! We haven’t exposed ourandroidmodule.

Add this to thelib.rsfile:

// add it below the use declarations.#[cfg(target_os = "android")]mod android;

Thecfgattribute will prevent theandroidmodule we just created to be compiled in case we’re not targetingAndroid.

Compiling for Android

Let’s get ready to compile our code forAndroid.

Create ascriptsfolder inside therustfolder and add a file calledandroid_build.shwith the following content:

#!/usr/bin/env bash# set the version to use the librarymin_ver=22# verify before executing this that you have the proper targets installedcargo ndk --target aarch64-linux-android --android-platform ${min_ver} -- build --releasecargo ndk --target armv7-linux-androideabi --android-platform ${min_ver} -- build --releasecargo ndk --target i686-linux-android --android-platform ${min_ver} -- build --releasecargo ndk --target x86_64-linux-android --android-platform ${min_ver} -- build --release# moving libraries to the android projectjniLibs=../android/rusty-android/rusty-android-lib/src/main/jniLibslibName=libdevicers.sorm -rf ${jniLibs}mkdir ${jniLibs}mkdir ${jniLibs}/arm64-v8amkdir ${jniLibs}/armeabi-v7amkdir ${jniLibs}/x86mkdir ${jniLibs}/x86_64cp 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}

Similarly to thepreviously suggested build script for iOS, this script will help us to compile and move the needed files to our previously createdAndroid Library.

If you execute thisbash script, once the compilation process ends you should be able to find a treeview similar to this one, with a newly created folder calledjniLibswith several subfolders in it referencing several architectures:

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第18张图片

Writing the Android Library

Finally, we’re going to write ourAndroid Librarycode and consume it from ourAndroidapplication.

Let’s create a new file underandroid/rusty-android-lib/src/main/java/com/robertohuertas/rusty_android_libcalledrusty.kt. Note that the name must be the same that we used when defining ourJNIfunctions in ourRustlibrary.

Copy the following code in it:

package com.robertohuertas.rusty_android_libexternal fun hello(to: String): Stringexternal fun helloDirect(to: String): Stringfun loadRustyLib() {    System.loadLibrary("rustylib")}

Here, we just declared two signatures mirroring our two Rust functions (remember the name we gave them in our Rust code) and a function that will be called in order to dynamically load the library. Note that we’re not using the name of the library (_librustylib.so_) but the name we gave to the crate.

Compiling the Android Library

If you want to generate an.aarfile ready to be consumed by anyAndroidapp just use theGradletab of yourAndroid Studioand look for a task calledassemble. Right-click on it and selectRun. This will compile your library and you’ll be able to find it atandroid/rusty-android-lib/build/outputs/aar/rusty-android-lib-release.aar.

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第19张图片

Consuming the Android Library

In this case, we don’t need to consume the library as an.aarfile because we have it in the same project. In case you want to know how to consume it like that just take a look at theAndroid Documentation.

In our example, we just need to add the library as a dependency in ourandroid/app/build.gradle:

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

And then, in ourAndroid Studio, chooseFile > Sync project with Gradle files.

Now, open the filecontent_main.xmllocated atandroid/app/src/main/res/layoutand add anidto theTextView, so we can reference it later and programmatically change its value:

android:id="@+id/txt"

After this, we’re going to use ourAndroid Libraryfrom ourMainActivity.ktfile located atandroid/app/src/main/java/com/robertohuertas/rust_android. Open it and write this:

package com.robertohuertas.rusty_androidimport android.os.Bundleimport com.google.android.material.snackbar.Snackbarimport androidx.appcompat.app.AppCompatActivityimport android.view.Menuimport android.view.MenuItemimport android.widget.TextViewimport 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(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)        }    }}

We’re done! Run it in your emulator and you should see a text in the app sayingHello from Rust: Rob. Furthermore, if you click the button below, asnackbarwill showHello from Rust: Rob direct. As you can see, we’re using both functions, the one calling theiOSfunction and the one using only thejni-rscrate.

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第20张图片

Flutter Project

And now… let’s go for the bonus points!

As we already have anAndroid Libraryand a workingiOSproject, making this work in aFlutterproject shouldn’t be very difficult.

The basic idea is to create aFlutter plugin packageso we can share our code in severalFlutterprojects if needed.

So let’s start!:grinning:

# let's use the flutter foldercd flutter# create a plugin project, set its namespace and its nameflutter 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

One of the cool things of theFlutter plugin packagesis that the template comes with anexample project, so we can use it to test that ourpluginworks as expected. No need to create a new project to test ourFlutterplugin.

We’re basically going to use our previousAndroidandiOScode and use it in ourFlutterproject. This should be very straightforward.

Importing the Android library

In order to use theAndroid Librarythat we built before we’re going to useAndroid Studio. Let’s open theflutter/androidproject.

Then, in order to import theAndroid Library, selectFile > New... > New Module.

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第21张图片

Selectimport .JAR/.AAR Package:

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第22张图片

And use the previously generated.AAR Packagepath:

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第23张图片

By doing this, we should see a new folder calledrusty-android-lib-releasefolder with our.aar packageinside along with some other files:

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第24张图片

Finally, open the build.gradle file in the flutter/android folder and add a new dependency:

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')

}
Adding the Android platform code
Open flutter/android/src/main/kotlin/com/robertohuertas/rusty_flutter_lib/RustyFlutterLibPlugin.kt in your favorite IDE and replace the code in it with this one:

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 {

@JvmStaticfun 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("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()}

}
}
Importing the iOS code
Importing the iOS code is fairly easy:

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/
Then open the rusty_flutter_lib.podspec file and add this line:

s.ios.vendored_library = 'libs/librustylib.a'
Adding the iOS plaform code
Open the flutter/ios/Classes/RustyFlutterLibPlugin.swift file and add the following code:

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

}
}
Connect the API and the platform code
Open the flutter/lib/rusty_flutter_lib.dart file and add a new static method in the RustyFlutterLib class:

static Future hello({to: String}) async {
final String greetings = await _channel.invokeMethod('hello', {'to': to});
return greetings;
}
Testing the Flutter example app
Let’s open the flutter/example/lib/main.dart file with your IDE of choice and let’s consume our recently created 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 {
String _platformVersion = 'Unknown';
String _greeting = '';

@override
void initState() {

super.initState();initPlatformState();

}

// Platform messages are asynchronous, so we initialize in an async method.
Future 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: [          Text(            _greeting,            style: TextStyle(fontSize: 20),          ),          Text('Running on: $_platformVersion\n'),        ],      ),    ),  ),);

}
}
After that, go to the example/ios folder and execute this:

pod install
Then, open the example/android/app/build.gradle file and change the minSdkVersion from 16 to 22, as our library was built with this setting, too.

And finally, open the example/pubspec.yaml file and add the line below to avoid this issue when building the iOS version:

version: 1.0.0+1
Congratulations for arriving here!

Let’s run the example app and see if it’s working.

In Android you should see something similar to the screenshot below:

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第25张图片

Similarly, iniOS:

可分享到 Android、iOS 和 Flutter平台的Rust代码编译 [转]_第26张图片

Thanks for finishing this long reading! I hope you found it useful!:blush:

Bibliography

  • Building and Deploying a Rust library on iOS
  • Building and Deploying a Rust library on Android
  • Rust on iOS
  • Rust on Android
  • cargo-ndk
  • jni-rs
  • JNI tips
  • Create an Android library

更多相关文章

  1. Android拍照上传功能示例代码
  2. 举例说明android中Bitmap、ListView以及ImageView的综合使用---
  3. Android DEX反编译后部分代码解析
  4. cocos2d-x打开网页android与ios平台
  5. android: 大量开源项目源代码分享
  6. 有关Android 平台解析XML
  7. android 开发:保存图片到SD卡上
  8. android重启代码

随机推荐

  1. 在Android的webview中定制js的alert,conf
  2. Android应用程序避免Context相关的内存泄
  3. Android中activity之间如何传递Hashmap数
  4. Android自定义控件:Android(安卓)L控件点
  5. 菜单实现Android中的常量 DEFAULT_KEYS_S
  6. Android(安卓)VideoView播放在线视频(2)
  7. android播放器(music player)源码分析5(在线
  8. Android(安卓)1.5 1.6 2.0 2.1 2.2 2.3 3
  9. Android动画开发之Animation动画效果
  10. android 汉化和反编译