咱们新年好啊,这是 2023 年榜首篇文章,对笔者来说也是一个新的技能测验,看似标题党,但确实是在实话实说 ~

起因

尽管我司从长远打算上是准备运用 Rust 来做底层及逻辑层跨端架构,但这落地时刻会拉的很长,且没有一个适宜的切入点。

现状是年底的时分咱们都了(笔者很嘴硬的没阳,但带薪病假浪费了 (o_ _)ノ),需求上也基本是阻滞状态,那就有空来折腾下 Rust,让自己锈一锈。

学习上从零开始拜读 《Rust 圣经》,但在根底入门篇看了一大半之后,真实是看不下去了…学而不思则惘,尽管搞了解了最要害的一切权和借用,但不到项目里用一下就等于啥也不会[手动狗头]。

刚好有一个由来已久的事务痛点,我司的埋点是否契合预期,这个验证在移动端上是较为杂乱的,原因有以下几点:

  • 埋点传输进程中是加密的,很难经过抓包的形式看到埋点是否正确。
  • 埋点在移动端是有缓存的,并不会实时上报。
  • 埋点在数据入库的时分也是有延迟的,在平台上查询埋点也需求必定时刻本钱。更重要的是,埋点需求验证各种时机是否正确,那需求实时性更高的办法。

而在跨端开发越来越频繁的今日,对立也就越来越凸显,对于 H5 开发的同学或许 Flutter 开发的同学来讲,埋点出现查不到或许埋入过错的问题后,需求消耗许多的时刻和精力来排查原因,所以迫切需求一个能实时查看埋点是否正确的办法。

考虑

最早的想法是让埋点实时显现在 App 上,但这存在以下的问题:

  • App 屏幕真实不大,没有更多的空间能完整显现当时的埋点信息,假如做成需求翻开页面来查看的办法,实时性也不高。
  • 需求额定开发页面,尽管能够用 Flutter 来减少双端开发本钱,但也是需求必定的开发本钱。

那有没有一种办法能够解决呢?

思路换一下,无论是 LookinServer 、 Flipper 等 Debug 利器,仍是 Flutter / Web Debug Tools,都是在电脑上调试 App。那咱们也能够用类似的办法,把实时埋点数据显现在电脑上,不再局限于同一块屏幕。

通讯办法

咱们需求的是一个尽量简单的通讯,所以方针上选定运用 WebSocket,且上述解决方案也大都是这样的挑选。

技能选型

在大方向上,首先排除掉 iOSAndroid 原生开发的办法来完成。

而 Flutter 并不适合做底层及逻辑层,它更适合做页面。

Rust 更契合咱们的需求,能够覆盖了一切终端,功用又高,并且不会加入到线上出产包中,很适合做 Rust 在移动端的榜首步落地。

唯一的问题便是还不会,所以需求花时刻探索下。

当时效果

翻开 Rust 服务端履行程序,等候衔接:

Rust + iOS & Android|未入门也能用来造轮子?

在 App 上输入要衔接的 IP 及 端口号:

Rust + iOS & Android|未入门也能用来造轮子?

服务端履行程序会实时显现收到的埋点信息:

Rust + iOS & Android|未入门也能用来造轮子?

完成进程

笔者在之前现已搭建好了 Rust 的开发环境,也成功的跑通了 “Hello World”,所以装备部分并不在本文的介绍中。

由于是完全陌生的技能领域,所以对笔者来说有2个需求解决的难点:

  • Rust WebSocket 完成。
  • Rust 与 iOS / Android 通讯。

WebSocket

笔者落地方案的时分并没有了解 Rust WebSocket 原理,本着快速落地的办法,先去找了 《Rust 圣经》里边的日常开发三方库精选,里边有这三个推荐库:

Rust + iOS & Android|未入门也能用来造轮子?

库好是好,也都是出产级的库,但笔者 Rust 功力几近于无,要不是 Demo 跑不动,要不是了解不了,改不动 (o_ _)ノ。

后边查到一个完成尤为简单的库,刚好能拿来就用。

[dependencies]
ws = "0.9.2"

传送门(github.com/housleyjk/w…

服务端

服务端的效果便是接收 App 来的音讯并显现,用配套的 ws-rs 库 Rust 服务端代码,很容易完成:

if let Err(error) = listen(address, |out| {
    ...
}) {
    // 告诉用户故障
    println!("创建Websocket失败,原因: {:?}", error);
}

address 是 ip + 端口号,趁便打印出来,让 App 输入连入。

let address = format!("{}:{}", get_ip().unwrap(), get_available_port());
println!("当时地址为:{}", address);

get_ip() 获取本机 ip 地址。

get_available_port() 获取本机可用的端口号。

办法网上搜的,或许不是是最佳的办法:

Rust + iOS & Android|未入门也能用来造轮子?

最终把收到的信息格式化输出即可,用 serde_json 库做 json 解析:

...
        // 处理程序需求获取 out 的一切权,因此咱们运用 move
        move |msg: ws::Message| {
            let text = msg.as_text();
            match text {
                Ok(_t) => {
                    // 格式化
                    let json: Value = serde_json::from_str(_t).unwrap();
                    println!(
                        "触发埋点\n event_id: {}\n event_name: {}\n attributes: {}\n\n",
                        json["event_id"],
                        json["event"],
                        serde_json::to_string(&json["attributes"]).unwrap()
                    );
                    // 运用输出通道发送音讯
                    out.send(msg)
                }
                Err(_) => todo!(),
            }
        }
...

然后履行 cargo run --release 打包成履行程序。

Rust + iOS & Android|未入门也能用来造轮子?

客户端

客户端代码也很简单,衔接服务端的 ip + 端口,并发送音讯即可。

...
    if let Err(error) = connect(c_host, |out| {
        // 将WebSocket翻开时要发送的音讯排队
        if out.send(c_message).is_err() {
            println!("[gaoding-log-view-kit]: 无法初始音讯排队")
        }
        // 封闭衔接
        out.close(CloseCode::Normal)
    })
...

这儿感叹下,Rust 中真的是轮子众多,集成度又高,真的是不明白所以然也能够拆箱即用。

Native 通讯

上面的 websocket 进程很顺利的完成了,但怎么在 App 中发送 ws 音讯?网上包含里有许多的资料,但基本都是同一份来历,看翻译的不如看原文更具体,能少踩坑。

iOS 传送门

Android 传送门

这2篇文章以及 git 源码 会教你 App 怎么让 Rust 打印个 “Hello World”,但里边也有一些遇到的弯弯绕绕需求注意。

Rust + iOS

先来看 iOS 是怎么做的。

前面的装备进程文章讲的很具体,这儿略过。

比较要害的几个环节:

库形式装备

[lib]
crate-type = ["staticlib", "cdylib"]

入口更改为 src/lib.rs,没有就创建一个。(这儿能够不必 lib.rs 做文件名吗?笔者还没具体了解,有了解的同学能够谈论告知下)

露出办法

use std::{ffi::CStr, os::raw::c_char};
...
#[no_mangle]
pub extern "C" fn send_wind_info(message: *const c_char, host: *const c_char) {
...
}

露出 send_wind_info(...) 发送埋点信息办法,且供给2个参数,一个是当时音讯、一个是发送的域名。

理论上,host 参数在 Rust 的内存持有即可,但这还写不出来 … 当然咱们把这个持有放到 Native 来做即能够绕过去。

打包及制造头文件

终端履行 cargo lipo --release 命令就可拿到 .a 文件,Rust 仍是很方便的。

Rust + iOS & Android|未入门也能用来造轮子?

然后咱们需求供给文件来给 iOS 调用:

api.h

#include <stdint.h>
/// 发送埋点信息
void send_wind_info(const char *message, const char *host);

Cocoapods

按文章上说的硬链到项目里,太令人难受了,咱们当然挑选用 pod 本地库引用的形式。

Rust + iOS & Android|未入门也能用来造轮子?

podspec 要害部分:

...
  s.source_files = 'GDLogViewKit/Classes/**/*.{m,h}'
  s.vendored_libraries = 'GDLogViewKit/Library/**/*.a'
  s.public_header_files = 'GDLogViewKit/Classes/*.h'
...

再封装

GDLogViewKit.h / GDLogViewKit.m 供给的便是 api.h 的封装,毕竟外部直接调用 api.h 的 send_wind_info(...) 办法真实不够高雅。

#import "api.h"
static NSString *connectAddress;
...
+ (void)sendWindMessage:(NSString *)message {
  if (!connectAddress || !message) {
    return;
  }
  send_wind_info([message cStringUsingEncoding:NSUTF8StringEncoding], [connectAddress cStringUsingEncoding:NSUTF8StringEncoding]);
}

connectAddress 便是用于保存 ip + 端口的静态变量。


iOS 上仍是很简单的,也或许由于笔者毕竟是一个 iOSer [手动狗头]。整体上便是构造一个 ObjectiveC – C – Rust 通讯进程。

Rust + Android

Android 就崎岖了许多,笔者对 Android 并不算熟,遇到了挺多问题,这儿逐个记录下。

NDK 装备

首先要找到自己 Android SDK 的安装目录,然后找到里边的 NDK 文件夹

Rust + iOS & Android|未入门也能用来造轮子?

然后履行文章中的语句来生成到一个目录下(这个目录依据文章所说生成到项目目录下)。

Rust + iOS & Android|未入门也能用来造轮子?

可是,

依据文章来履行这三句总是失败 – -!

我也测验用 make-standalone-toolchain.sh shell 脚本也不行

Rust + iOS & Android|未入门也能用来造轮子?

ERROR: Failed to create toolchain

Rust + iOS & Android|未入门也能用来造轮子?

这个过错网上搜了下也没有人解释原因,最终想到,我在 python 开发上用的是 python3, 会不会是这个原因?

python3 'xxx/make_standalone_toolchain.py' --api 26 --arch arm64 --install-dir NDK/arm64

成功了 …

再往下照文章履行即可。

JNI

Android 与 iOS 不同的是,多了一层 JNI,相当于 JAVA – JNI – C – Rust,这一部分曾经并没有接触过,一开始靠硬写,后边发现能够经过 javac 来生成。

先在 Cargo.toml 上添加 jni 东西装备,能简洁咱们编写 jni

[target.'cfg(target_os="android")'.dependencies]
jni = { version = "0.20.0", default-features = false }

在 src/lib.rs 添加 Android 胶水代码:

Rust + iOS & Android|未入门也能用来造轮子?

打包 so 库

学文章的办法,添加一个 cargo-config.toml 再履行拷贝命令 cp cargo-config.toml ~/.cargo/config(但这个很古怪,是每个项目都要履行吗?有懂的同学谈论告知下)。

Rust + iOS & Android|未入门也能用来造轮子?

然后调用生成 so 库命令

cargo build --target aarch64-linux-android --release
cargo build --target armv7-linux-androideabi --release
cargo build --target i686-linux-android --release

Gradle

同样,咱们也制造一个独立的 gradle 库

Rust + iOS & Android|未入门也能用来造轮子?

将 so 库放入到 jniLibs 目录下,这儿也有一个坑

Rust + iOS & Android|未入门也能用来造轮子?

文章上是放到这个位置,但咱们项目里需求放入到如下位置才干被读取到

Rust + iOS & Android|未入门也能用来造轮子?

据 Android 开发同学说是不同的项目上依赖包装备不同导致的。

再封装

跟 iOS 一样,封装的东西办法:

GDLogViewKit.java

package com.gaoding.log.view.kit;
/**
 * 日志可视化东西包
 */
public class GDLogViewKit {
    static {
        // 库引用
        System.loadLibrary("gaoding_log_view_kit");
    }
    private static String kAddress = null;
    // native 办法,对应 JNI 
    private static native void sendWindInfo(final String message, final String host);
    ...
    public static void sendWindMessage(String message) {
        if (kAddress == null) {
          return;
        }
        GDLogViewKit.sendWindInfo(message, kAddress);
    }
}

总结

一个简单的埋点实时可视化的轮子就算是做好了,信任它能在项目中起到应有的效果,真的要再感叹 Rust 生态环境的强大,也坚定了后续学习 Rust 的决计。

后续扩展上,能够抛弃 Rust 的服务端,WS 服务上到内部平台上,来把轮子做大。也能够扩展到其他日志信息,乃至能够供给控制指令来操作 App。由于是 Rust,所以能很方便的集成到 Web、桌面等其他终端应用上。

缺乏的点,仍是由于 Rust 没入门,继续啃圣经,先测验把 ip 地址缓存的功用放到 Rust 中,待修炼飞升后,把上述进程加入到跨端东西链全家桶中 …

别的,以上代码已开源 git 传送门,有爱好的能够了解。


感谢阅读,假如对你有用请点个赞 ❤️

Rust + iOS & Android|未入门也能用来造轮子?