吃得饱系列-Flutter 调用 Rust 生成的 共享/静态 库

954 阅读3分钟

日常吃得饱

前面写过一两篇水文, 用 Rust 写么点东西, 编译成 shared/static lib 给 Android 或者 iOS 端调用. 这篇水文需要用到之前写得 MD5 的那个 Rust 项目.
吃得饱系列-Android 使用 Rust 生成的动态库
吃得饱系列-Rust 导出静态库给 iOS 端使用

创建 Flutter 项目

这里直接通过下面这条命令创建

flutter create --platforms=android,ios --template=plugin ffi_demo

这里使用的是 plugin 的模板, 如果直接使用 project 模板也是可以的, 现在直接用 Android Studio 打开项目. 我们先来看看项目结构

tree -L 1

.
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android
├── example
├── ffi_demo.iml
├── ios
├── lib
├── pubspec.lock
├── pubspec.yaml
└── test

5 directories, 7 files

处理 Android 端

Android 端的比较好处理, 把开头 Android 的那篇文章的 md5 项目生成的动态库拷贝出来, 放到 android/src/main/jniLibs/arm64-v8a 下面, 事实上这里不需要用到 CMakeLists, 所以直接放到 jniLibs 下面, 不过有个问题, Android 项目并不知道你放这边了, 所以还得配置一下 build.gradle 的 sourceSets

android {
  // ...
  sourceSets {
    // ...
    main.jniLibs.srcDirs = ['src/main/jniLibs']
  }
  // ...
}

这一步完成后, 我们先把 Android 这边的事放一放

处理 iOS 端

由于之前的 md5 那个项目, 没编译 iOS 的静态库, 所以我们先得把 iOS 静态库生成一下. 喜闻乐见的添加对应 target 环节

rustup target add aarch64-apple-ios

如果你想跑在 Intel 设备的 iOS 仿真器上, 需要添加 x86 的 target, 这里只添加了手机 aarch64 的, 如果你想跑在 M1 设备的 iOS 仿真器上, 你需要添加

rustup target add aarch64-apple-ios-sim

接着跑到 md5 的项目中运行构造命令

cargo build --target aarch64-apple-ios --release

构建成功后把 target 目录对应平台的静态库拷贝出来放到 ios/Frameworks 下, 没有 Frameworks 文件夹就自己创建, 文件夹名字随便起, 然后把 ffi_demo.podspec 文件改一下, 总之找到对应的 .podspec 文件, 添加指定静态库的路径

Pod::Spec.new do |s|
  # ...
  s.vendored_libraries = 'Frameworks/libmd5.a'
  # ...
end

处理 Flutter 端

上面的准备工作完成后, 我们可以来暴露接口给 Dart 使用啦! 找到 lib/ffi_demo.dart 文件, 修改里面的代码

import 'dart:io';
import 'dart:ffi';
// third part package
import 'package:ffi/ffi.dart';

typedef LLMD5 = Pointer<Int8> Function(Pointer<Int8>);

class FfiDemo {
  String md5(String str) {
    final DynamicLibrary nativeLib = Platform.isAndroid
        ? DynamicLibrary.open("libmd5.so")
        : DynamicLibrary.process();
    LLMD5 md5 = nativeLib.lookupFunction<LLMD5, LLMD5>("ll_md5");
    var result = md5(str.toNativeUtf8().cast<Int8>());
    return result.cast<Utf8>().toDartString();
  }
}

其实就是把 dart 的字符串转成 C 的字符串传给 md5 函数使用, 返回的结果再转成 dart 的字符串, 我们代码中用到一个第三方包来处理 dart 字符串跟 C 字符串的转换, 直接执行添加第三方包的命令

flutter pub add ffi

执行 example

Android

为了验证我们的插件是否正常工作, 我们把 example 里面的 lib/main.dart 修改一下

import 'package:flutter/material.dart';
import 'package:ffi_demo/ffi_demo.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _md5Str = '';
  final _ffiDemoPlugin = FfiDemo();

  @override
  void initState() {
    super.initState();
    initMD5();
  }

  void initMD5() {
    String str = '';
    try {
      str = _ffiDemoPlugin.md5("foo");
    } on Exception {
      str = 'MD5 failed';
    }
    if (!mounted) return;
    setState(() {
      _md5Str = str;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Text('$_md5Str\n'),
        ),
      ),
    );
  }
}

然后连接对应 ABI 的 Android 手机, 用 Android Studio 执行一下, 可以看到屏幕中间显示了一串经过 md5 运算后的字符串.

image.png

iOS

iOS 这边, 因为我们修改了 podspec 文件, 所以我们需要在 example/ios 目录下执行

pod install

然后就是更新 xcworkspace, 完成后, 就可以在 Pod 项目中找到我们的静态库了 然后用 Android Studio 打开插件项目(直接用 Xcode 执行 Runner 可能会出现找不到符号的问题), 执行到对应的设备(或者仿真器), 就能看到对应的效果

image.png