專業(yè)網(wǎng)站推廣的公司網(wǎng)絡(luò)營銷推廣專員
文章目錄
- 前言
- 一、 單實(shí)例應(yīng)用的意義
- 二、 實(shí)現(xiàn)單實(shí)例應(yīng)用的方法
- 1 Windows下的實(shí)現(xiàn)
- 1.1 創(chuàng)建命名Mutex
- 1.2 在Tauri應(yīng)用中集成Mutex檢查
- 2 macOS下的實(shí)現(xiàn)
- 2.1 獲取Bundle Identifier
- 2.2 檢查是否已經(jīng)有實(shí)例在運(yùn)行
- 3 Linux下的實(shí)現(xiàn)
- 3.1 獲取進(jìn)程列表
- 3.2 檢查是否已經(jīng)有實(shí)例在運(yùn)行
- 4 在Tauri應(yīng)用中集成單實(shí)例檢查
- 三、使用Tauri官方提供的插件實(shí)現(xiàn)單例程序
- 1. 安裝準(zhǔn)備
- 2. 自動(dòng)安裝(推薦)
- 3. 手動(dòng)安裝
- 四、配置單例插件
- 1. `init`函數(shù)
- 2. 新打開程序提示例子
前言
隨著跨平臺(tái)應(yīng)用開發(fā)的需求不斷增加,Tauri2.0框架憑借其高性能和跨平臺(tái)的特性,成為了開發(fā)者們的熱門選擇。然而,在開發(fā)桌面應(yīng)用時(shí),如何確保應(yīng)用程序只能運(yùn)行一個(gè)實(shí)例是一個(gè)常見的需求。例如,某些應(yīng)用程序需要獨(dú)占系統(tǒng)資源,或者需要避免用戶誤操作導(dǎo)致的數(shù)據(jù)沖突。今天,我們將探討如何在Tauri2.0框架下,使用Rust語言實(shí)現(xiàn)單實(shí)例應(yīng)用程序的功能。
本文將詳細(xì)介紹在不同操作系統(tǒng)(Windows、macOS、Linux)下實(shí)現(xiàn)單實(shí)例應(yīng)用的方法,并提供完整的代碼示例。通過本文,你將了解到如何在Tauri2.0應(yīng)用啟動(dòng)時(shí)檢查是否已經(jīng)有實(shí)例在運(yùn)行,并采取相應(yīng)的措施,例如提示用戶或?qū)?shù)傳遞給已有的實(shí)例。
最后再為你介紹Tauri官方為我們實(shí)現(xiàn)這種需求提供的一種捷徑,從而不用去管理互斥體,而是簡單的插件配置就能得到相同的結(jié)果,這也是為什么要寫本文的原因。這就是Tauri插件 —— Single Instance
.
一、 單實(shí)例應(yīng)用的意義
在開發(fā)桌面應(yīng)用時(shí),單實(shí)例應(yīng)用的意義主要體現(xiàn)在以下幾個(gè)方面:
-
資源管理:某些應(yīng)用程序需要獨(dú)占特定的系統(tǒng)資源,例如硬件設(shè)備或獨(dú)特的系統(tǒng)服務(wù)。如果允許多個(gè)實(shí)例運(yùn)行,可能會(huì)導(dǎo)致資源爭搶或不可預(yù)測(cè)的行為。
-
數(shù)據(jù)一致性:對(duì)于需要處理共享數(shù)據(jù)的應(yīng)用程序,例如數(shù)據(jù)庫管理工具或配置文件編輯器,防止多個(gè)實(shí)例同時(shí)修改數(shù)據(jù)可以避免數(shù)據(jù)沖突和不一致。
-
用戶體驗(yàn):在某些場(chǎng)景下,用戶可能不小心多次啟動(dòng)應(yīng)用程序,導(dǎo)致多個(gè)實(shí)例運(yùn)行。通過單實(shí)例機(jī)制,可以提供更友好的用戶體驗(yàn),例如自動(dòng)將焦點(diǎn)切換到已有的實(shí)例。
-
安全性:對(duì)于某些需要嚴(yán)格控制的應(yīng)用程序,例如金融類軟件或敏感數(shù)據(jù)處理工具,單實(shí)例機(jī)制可以增強(qiáng)應(yīng)用的安全性,防止惡意的多實(shí)例攻擊。
二、 實(shí)現(xiàn)單實(shí)例應(yīng)用的方法
在Tauri2.0框架下實(shí)現(xiàn)單實(shí)例應(yīng)用,我們需要在應(yīng)用啟動(dòng)時(shí)檢查是否已經(jīng)有一個(gè)實(shí)例在運(yùn)行。如果有,則采取相應(yīng)的措施,例如提示用戶或?qū)?shù)傳遞給已有的實(shí)例。
1 Windows下的實(shí)現(xiàn)
在Windows平臺(tái)下,可以通過創(chuàng)建一個(gè)命名的Mutex(互斥量)來實(shí)現(xiàn)單實(shí)例檢查。Mutex是Windows提供的一種同步機(jī)制,可以用于跨進(jìn)程的同步和互斥控制。
1.1 創(chuàng)建命名Mutex
在Windows下,我們可以通過調(diào)用CreateMutexW
函數(shù)創(chuàng)建一個(gè)命名的Mutex。如果Mutex已經(jīng)存在,則表示已經(jīng)有一個(gè)實(shí)例在運(yùn)行。
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use winapi::shared::minwindef::DWORD;
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::synchapi::CreateMutexW;fn create_mutex(name: &str) -> bool {let name = OsStr::new(name).encode_wide().chain(Some(0)).collect::<Vec<u16>>();unsafe {CreateMutexW(name.as_ptr(), false as DWORD, None) as DWORD} != 0
}fn is_single_instance(name: &str) -> bool {let result = create_mutex(name);if result {// 如果Mutex已經(jīng)存在,則表示已經(jīng)有一個(gè)實(shí)例在運(yùn)行unsafe {if GetLastError() == 183 { // ERROR_ALREADY_EXISTSreturn false;}}}result
}
1.2 在Tauri應(yīng)用中集成Mutex檢查
在Tauri應(yīng)用的主函數(shù)中,我們可以調(diào)用上述函數(shù)來檢查是否已經(jīng)有一個(gè)實(shí)例在運(yùn)行。如果已經(jīng)有實(shí)例運(yùn)行,則可以提示用戶并退出。
fn main() {let instance_name = "MyTauriApp";if !is_single_instance(instance_name) {// 如果已經(jīng)有一個(gè)實(shí)例在運(yùn)行,則提示用戶并退出println!("An instance of {} is already running.", instance_name);std::process::exit(1);}// 啟動(dòng)Tauri應(yīng)用tauri::run();
}
2 macOS下的實(shí)現(xiàn)
在macOS平臺(tái)下,可以通過BUNDLE_IDENTIFIER
來實(shí)現(xiàn)單實(shí)例檢查。macOS提供了LSOpenURLsWithRole
函數(shù),可以用于檢查是否已經(jīng)有一個(gè)應(yīng)用程序在運(yùn)行。
2.1 獲取Bundle Identifier
在macOS下,每個(gè)應(yīng)用程序都有一個(gè)唯一的Bundle Identifier,可以通過Info.plist文件配置。
use std::process::Command;fn get_bundle_identifier() -> String {let output = Command::new("osascript").arg("-e").arg("id of app \"System Events\"").output().expect("failed to execute osascript");String::from_utf8(output.stdout).unwrap()
}
2.2 檢查是否已經(jīng)有實(shí)例在運(yùn)行
通過調(diào)用LSOpenURLsWithRole
函數(shù),我們可以檢查是否已經(jīng)有一個(gè)實(shí)例在運(yùn)行。如果有,則返回true
,否則返回false
。
use std::os::raw::c_char;extern crate libc;fn is_single_instance(bundle_id: &str) -> bool {let mut psi: libc::PROCESSENTRY32 = unsafe { std::mem::zeroed() };let snapshot = unsafe { libc::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };if snapshot == 0 {return false;}psi.dwSize = std::mem::size_of::<libc::PROCESSENTRY32>() as DWORD;while unsafe { Process32Next(snapshot, &mut psi) } != 0 {if let Some(name) = unsafe { CStr::from_ptr(psi.szExeFile.as_ptr() as *const c_char) }.to_str() {if name == bundle_id {return true;}}}unsafe { CloseHandle(snapshot) };false
}
3 Linux下的實(shí)現(xiàn)
在Linux平臺(tái)下,可以通過檢查進(jìn)程名或使用套接字來實(shí)現(xiàn)單實(shí)例檢查。這里我們將演示如何通過檢查進(jìn)程名來實(shí)現(xiàn)單實(shí)例檢查。
3.1 獲取進(jìn)程列表
通過調(diào)用/proc
文件系統(tǒng),我們可以獲取當(dāng)前運(yùn)行的所有進(jìn)程,并檢查是否有相同的進(jìn)程名。
use std::fs;
use std::path::Path;fn get_process_list() -> Vec<String> {let mut processes = Vec::new();for entry in fs::read_dir("/proc").unwrap() {let entry = entry.unwrap();let path = entry.path();if path.is_dir() {if let Some(name) = path.file_name().and_then(|n| n.to_str()) {if name.chars().all(char::is_digit) {processes.push(name.to_string());}}}}processes
}
3.2 檢查是否已經(jīng)有實(shí)例在運(yùn)行
通過遍歷所有進(jìn)程,并檢查是否有相同的進(jìn)程名來判斷是否已經(jīng)有實(shí)例在運(yùn)行。
fn is_single_instance(process_name: &str) -> bool {let processes = get_process_list();for pid in processes {let exe_path = format!("/proc/{}/exe", pid);let exe_link = Path::new(&exe_path);if exe_link.exists() {if let Some(exe_path) = exe_link.canonicalize().ok() {if exe_path.file_name().and_then(|n| n.to_str()) == Some(process_name) {return true;}}}}false
}
4 在Tauri應(yīng)用中集成單實(shí)例檢查
在Tauri應(yīng)用的主函數(shù)中,我們可以根據(jù)不同的平臺(tái)調(diào)用相應(yīng)的單實(shí)例檢查函數(shù)。
fn main() {#[cfg(target_os = "windows")]{let instance_name = "MyTauriApp";if !is_single_instance(instance_name) {println!("An instance of {} is already running.", instance_name);std::process::exit(1);}}#[cfg(target_os = "macos")]{let bundle_id = get_bundle_identifier();if is_single_instance(&bundle_id) {println!("An instance of {} is already running.", bundle_id);std::process::exit(1);}}#[cfg(target_os = "linux")]{let process_name = "my_tauri_app";if is_single_instance(process_name) {println!("An instance of {} is already running.", process_name);std::process::exit(1);}}tauri::run();
}
三、使用Tauri官方提供的插件實(shí)現(xiàn)單例程序
1. 安裝準(zhǔn)備
首先,確保你安裝的Rust版本符合條件,該插件要求你的Rust版本大于1.77.2
.
然后就是看你的應(yīng)用平臺(tái)是否支持該插件,官方給出以下表格
可以明顯看到,只有桌面系統(tǒng)受支持,也就是你的應(yīng)用只能是在windows
,linux
,macos
上,這個(gè)插件才會(huì)有用,否則插件是用不了的。
2. 自動(dòng)安裝(推薦)
使用你所選擇的包管理器直接安裝即可,例如pnpm
安裝
pnpm tauri add single-instance
3. 手動(dòng)安裝
首先添加依賴
# src-tauri/Cargo.toml
[dependencies]
tauri-plugin-single-instance = "2.0.0"
然后在tauri啟動(dòng)的時(shí)候添加插件
pub fn run() {tauri::Builder::default()// 就是下面這行.plugin(tauri_plugin_single_instance::init(|app, args, cwd| {})) .run(tauri::generate_context!()).expect("error while running tauri application");
}
然后運(yùn)行一下項(xiàng)目就裝好插件了
pnpm tauri dev
四、配置單例插件
如果你只是想要簡單的實(shí)現(xiàn)單實(shí)例的話,就以上安裝配置就已經(jīng)能夠達(dá)到這個(gè)效果了,如果你還想要在這個(gè)過程中實(shí)現(xiàn)其他功能,例如用戶啟動(dòng)了另一個(gè)程序后提示程序已經(jīng)啟動(dòng)了
,那么可以接著往下看。
1. init
函數(shù)
在配置安裝插件時(shí)有一個(gè)init
函數(shù)可以注意一下,也就是
.plugin(tauri_plugin_single_instance::init(|app, args, cwd| {// 在這里寫代碼 ……
}))
插件的 init()
方法接收一個(gè)閉包,該閉包在新 App 實(shí)例啟動(dòng)時(shí)調(diào)用,但由插件關(guān)閉。 這個(gè)閉包有三個(gè)參數(shù):
app
:應(yīng)用程序的 AppHandle ,即應(yīng)用的句柄,用來操作該程序。args
:用戶初始化新實(shí)例時(shí)傳遞的參數(shù)列表,也就是新打開的程序的傳入?yún)?shù)。cwd
:當(dāng)前工作目錄表示啟動(dòng)新應(yīng)用程序?qū)嵗哪夸?#xff0c;也就是另一個(gè)程序在哪個(gè)目錄打開的。
2. 新打開程序提示例子
注意,這部分邏輯你可以自己實(shí)現(xiàn),這只是個(gè)官方給的例子。
use tauri::{AppHandle, Manager};pub fn run() {tauri::Builder::default().plugin(tauri_plugin_single_instance::init(|app, args, cwd| {let _ = show_window(app);})).run(tauri::generate_context!()).expect("error while running tauri application");
}fn show_window(app: &AppHandle) {let windows = app.webview_windows();windows.values().next().expect("Sorry, no window found").set_focus().expect("Can't Bring Window to Focus");
}