前言
在上一篇中我们讲了,在 Android Studio
中通过二维码使用WIFI连接手机的原理,今天就来使用 Rust
来实现类似的功能。
没有看过的建议看一下,不然这篇文档可能会看不懂。
为什么要用 Rust
呢?
一方面是 Go
已经有了,而刚好我想用 Rust
来练练手,所以就是它了。
回顾使用二维码连接的流程
在开发之前我们先来回顾一下二维码的连接过程,主要有以下这几个步骤
- 创建两个随机字符串,一个用于名字,一个用于密码
- 根据规则组装二维码的内容并生成二维码,等待用户扫码
- 通过
adb
检测 mdns
服务,看看是否有我们需要的
- 使用
adb pair
命令进行配对
- 使用
adb
查看连接列表是否已经连接成功
准备
在开发之前我们来分析一下需要使用那些第三方库
开发
添加依赖
根据上面的分析,我们把依赖加到 Cargo.toml
中
1
2
3
4
5
6
7
8
|
[dependencies]
qrcode = "0.12"
mdns = "3.0.0"
rand = "0.8.5"
async-stream = "0.2.0"
async-std = { version = "1.6.2", features = ["unstable", "attributes"] }
futures-core = "0.3.1"
futures-util = "0.3.1"
|
由于 mdns
使用到了协程,所以我们还需把 async-std
、 asnyc-stream
、 features-core
、 features-util
加上
生成指定个数的字符串
使用 rand
生成随机字符串,在这里我们要指定生成字符串的个数,然后从我们给定的字符集中随机取固定的个数,组成随机字符串。
1
2
3
4
5
6
7
8
9
10
|
const CHAR_SET: &str =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-+*/<>{}";
fn create_random_string(len: u32) -> String {
let mut result = String::new();
let mut rng = thread_rng();
(0..=len).for_each(|_i| {
result.push(CHAR_SET.chars().choose(&mut rng).unwrap());
});
result
}
|
组装二维码内容
有了生成随机的字符串后,我们就需要生成一个10个字符的名字和12个字符串的密码,然后按照 WIFI:T:ADB;S:${name};P:${pwd};;
的格式来生成二维码的内容。
这里要注意名字还有一个 studio
的前缀,其实没有也是可以的。
1
2
3
|
let server_id = format!("studio-{}", create_random_string(10));
let pwd = create_random_string(12);
let pair_string = format!("WIFI:T:ADB;S:{};P:{};;", server_id, pwd);
|
生成二维码
生成二维码也挺简单的,由于我们是在终端中使用,所以只需要使用 Unicode
字符就行了
1
2
3
4
5
6
7
8
9
10
11
|
fn generate_qr_code(content: &str) {
let code = QrCode::new(content).unwrap();
let image = code
.render::<unicode::Dense1x2>()
.dark_color(unicode::Dense1x2::Light)
.light_color(unicode::Dense1x2::Dark)
.build();
println!("{}", image);
}
generate_qr_code(&pair_string);
|
把上一步组装好的内容当做参数传入,生成好之后,打印到控制台中,就能够看到二维码了。
检测配对服务
生成好二维码之后,等待用户扫码,然后我们这里需要检测看看有没有配对的服务。有才能进行配对。
这里有一个需要注意的地方,我们通过 adb mdns services
得到的结果是不带 .local
后缀的,但是我们使用 mdns
去检测的时候要带上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
async fn discovery_pair_services(service: &str) -> Result<Option<SocketAddr>, Error> {
let stream = mdns::discover::all(service, Duration::from_secs(15))?.listen();
pin_mut!(stream);
while let Some(Ok(response)) = stream.next().await {
let host = response.hostname();
let socket_addr = response.socket_address();
if let (Some(host), Some(socket_addr)) = (host, socket_addr) {
println!("host: {}, socket_addr: {}", host, socket_addr);
if host == "_adb-tls-pairing._tcp.local" {
return Ok(Some(socket_addr));
}
}
}
Ok(None)
}
let res = discovery_pair_services("_adb-tls-pairing._tcp.local").await;
|
进行配对
1
2
3
4
5
6
7
8
9
10
11
|
if let Ok(res) = res {
if let Some(res) = res {
let pair_result = Command::new("adb")
.arg("pair")
.arg(res.to_string())
.arg(pwd)
.output()
.expect("failed to execute process");
println!("{}", String::from_utf8(pair_result.stdout).unwrap());
}
}
|
检测到配对服务后,我们就可以拿到它的 IP
和端口,由于返回的是个 SocketAddr
,所以调用它的 to_string
就能够得到 ip:port
的形式。
在进行配对环节时,我们要调用 adb
这个外部程序,所以需要使用 Commond
来执行。
在 Command::new
中传入外部程序的名字,然后通过 arg
来添加一个参数,通过 output
来以子进程的方式执行,并获取输出的结果。
Command
使用起来还是非常方便的,就像平时执行指令一样,只需要对指令和参数进行组装就行了。
查看是否连接成功
配对完成之后,还需要查看是否连接成功,这时候就需要通过把已经连接上的设备打印出来,看看我们的设备是否在其中。执行这条指令要稍微延迟一下,连接有时会慢一点。
1
2
3
4
5
6
|
let devices = Command::new("adb")
.arg("devices")
.arg("-l")
.output()
.expect("failed to execute process");
println!("{}", String::from_utf8(devices.stdout).unwrap());
|
总结
根据之前的分析,我们知道了大致的思路,所以写起来还是比较快的。
100行的代码不到,就实现了一个二维码连接手机的功能。
完整的代码在 devbins/qrcode_pair_rs
参考