需求背景
近期接到需求,需要做一个游戏登录器,即类似于wegame的一个桌面应用,可以通过登录器来实现游戏的管理。对比了目前流行的跨平台桌面应用框架 electron 、nwjs 、tauri,决定尝试一下tauri,毕竟是真的轻量,还能借机学习一下rust,很完美。
Tauri简介
Tauri是基于rust开发的跨平台应用框架,使用系统自带的webview渲染ui。Tauri 在 Windows 上使用 Microsoft Edge WebView2,在 macOS 上使用 WKWebView,在 Linux 上使用 webkitgtk。目前新的alpha版本已经支持了移动端。
Tauri的优点:
- 轻量,打包快且体积小
- 高性能,内存占用少
- 安全
Tauri的缺点:
- 依赖系统的webview版本,可能存在兼容问题(目前还没遇到)
- 调试页面时,mac上是Safari的界面,个人不是很习惯..
- 跨平台编译流程繁琐,目前只提供了github action,代码不在github上就很...只有win打win的包,mac打mac的包
- 学习成本微高,需要学习新语言rust,rust的学习曲线又是出了名的陡峭。(但是可以学学rust,毕竟在工程化领域很吃香)
- 中文资料较少,遇到问题要花更长的时间解决(如果有chatgpt辅助会好些)
!!!请不要盲目使用Tauri,要根据项目的实际情况选择,重要紧急的项目不推荐使用
窗口设置
tauri的配置文件是/src-tauri/tauri.conf.json,配置文件的详细说明参见官网tauri.app/zh-cn/v1/ap…。窗口相关的配置在tauri.window中,如宽、高、是否可拖动、是否全屏等。decorations设置为false时,窗口不会展示最大化、最小化、关闭等工具栏按钮,如果需要这些功能,需要手动调用@tauri-apps/api/window中的setMinSize``setMaxSize方法。
!!注意
decorations为false时- 窗口会自动偏移,需要设置
center为true - 窗口会无法拖动,如果需要能够拖动,需要在外层的dom节点上添加
data-tauri-drag-region属性
- 窗口会自动偏移,需要设置
setMinSize``setMaxSize等所有js api在使用时,需要先在tauri.conf.json``tauri.allowlist中进行声明。
通信
前端调用rust
tauri提供了command系统,用于从 web 应用程序调用 Rust 函数。 command可以接受参数并返回结果, 也可以返回错误。如何使用command?
- 在rust的函数上添加
#[tauri::command]注释,函数名即为command的名称 - 在
main函数中使用invoke_handler注入command,多个command传递数组 - 在js代码中,从
@tauri-apps/api/tauri中导入invoke函数,invoke('command_name')调用
!!注意
- command传递参数时,如果变量使用了驼峰命名,在rust中接收到的变量会被转化为snake形势,如图,invokeMessage被转化为了invoke_message。
invoke方法是异步的,可以使用await方式调用
事件
可以通过触发和监听事件,从rust向js或者反过来传递消息。事件可以是全局事件,也可以指定窗口,没什么特殊用法,参见官网tauri.app/zh-cn/v1/gu…。
文件下载
游戏相关的资源都存放在oss上,下载或更新时需要使用http库拉取文件,这里使用了reqwest,crates.io/就像是npm一样,我们可以在上面搜索需要使用的第三方包,然后通过cargo安装。下载功能的实现参考了FishLauncher和douyin-downloader。文件下载主要有两个点需要考虑:
进度条
当前进度 = 当前已下载的大小 / 总文件大小
构造一个client实例后,很容就能得到当前下载文件的总大小和当前已下载的大小,触发progress事件,然后在js中监听,获取事件参数,计算进度并展示,部分代码如下:
let client = reqwest::Client::new();
let response = client
.get(&url)
.headers(headers)
.send()
.await
.or(Err(format!("Failed to GET from '{}'", &url)))?;
// 文件总大小
let source_size = response
.content_length()
.ok_or(format!("Failed to get content length from '{}'", &url))?;
// 已经下载的大小
let mut download_size: u64 = 0;
// 下载流
let mut stream = response.bytes_stream();
while let Some(item) = stream.next().await {
let chunk = item.or(Err(format!("Error while downloading file")))?;
download_size += chunk.len() as u64;
// 触发事件
window
.emit(
"progress",
Payload {
total: source_size,
done: download_size,
},
)
.unwrap();
file
.write(&chunk)
.or(Err(format!("Error while writing to file")))?;
}
断点续传
游戏文件一般都超过500M,需要考虑中断下载后继续下载的情况,即断点续传。阿里云oss支持这个功能,所以使用起来很简单,在http请求中添加header字段range,服务器就知道该返回哪一块的数据回来。具体操作如下:
- 保存已经下载的文件大小 download_bytes
- 每次开始下载前,检查download_bytes的值是否大于0
- 大于0,进入断点续传逻辑
- 打开原来的文件,而不是创建新文件,要使用追加的模式
- 在header中添加
range字段
- 否则正常下载
- 大于0,进入断点续传逻辑
- 下载完成后,重置download_bytes的值
部分代码如下:
// 根据download_bytes判断创建文件还是打开原有文件
let mut file = if download_bytes > 0 {
OpenOptions::new().append(true).open(&folder).or(Err(format!("Failed to open file '{}'", folder)))?
} else {
File::create(&folder).or(Err(format!("Failed to create file '{}'", folder)))?
};
let mut headers = reqwest::header::HeaderMap::new();
headers.append(USER_AGENT, user_agent.parse().unwrap());
// 添加RANGE
if download_bytes > 0 {
headers.append(RANGE, format!("bytes={}-", bytes).parse().unwrap());
}
// 要保证进度条的准确
let source_size = response
.content_length()
.ok_or(format!("Failed to get content length from '{}'", &url))? + download_bytes;
let mut download_size: u64 = download_bytes;
!!注意:
- 多文件同时下载时,要注意进度条的变化,最好先获取多个文件的总大小,计算总的进度。否则会0-100%乱闪。
- 断点续传时,由于使用了range,reqwest获取到的文件总大小并不是实际的文件总大小。
自更新
打包
参见文档 tauri.app/zh-cn/v1/gu…
如果我们需要校验更新是否安全,需要在tauri.conf.json中设置_Public-key,_ 打包时会生成对应包的签名,需要配置在更新文件中_。参见 tauri.app/zh-cn/v1/gu…。
运行下方的命令,项目根目录下,会生成_Public-key和Private keytauri signer generate -w ~/.tauri/myapp.key
需要注意的是,配置_Public-key_后,需要设置一个环境变量,TAURI_PRIVATE_KEY ,值为 _Private-key,_否则打包会失败。另外,在window上,最好在cmd命令行中运行打包命令。
更新json配置
tauri更新的服务配置也在tauri.conf.json配置文件中
"updater": {
"active": true,
"endpoints": [
"https://releases.myapp.com/{{target}}/{{current_version}}",
"https://alioss/update.json"
],
"dialog": true,
"pubkey": "YOUR_UPDATER_SIGNATURE_PUBKEY_HERE",
"windows": {
"installMode": "passive"
}
}
endpoints就是tauri请求更新的地址,可以是接口,也可以是cdn上的json文件。如果是接口,需要按照tauri的要求返回对应的内容,参见文档 tauri.app/zh-cn/v1/gu…。我直接使用了cdn的方式,简单方便。
dialog设置为true时,检测到新版本会使用系统内置的更新弹窗。如果想要保持多平台样式一致,需要设置为true,在代码中检测和监听更新,自定义操作和行为,参见文档 tauri.app/zh-cn/v1/gu…
!!注意:
windows.installMode这个值,代表更新后如何安装新包,默认是passive能看到进度条,quiet表示静默安装。如果设置为quiet,则程序需要**以管理员身份打开,**否则在更新时会死循环,一直安装一直重启。
其他
- 在windows上,使用winreg在运行时修改注册表
- 使用runas在运行时获取管理员权限
- 在界面上点击鼠标右键,会出现浏览器的右键菜单,需要视情况禁用或自定义