refactor: update AppHandle usage to use Arc<AppHandle> for improved memory management (#4491)

* refactor: update AppHandle usage to use Arc<AppHandle> for improved memory management

* fix: clippy ci

* fix: ensure default_latency_test is safely accessed with non-null assertion
This commit is contained in:
Tunglies
2025-08-23 00:20:58 +08:00
committed by GitHub
parent c416bd5755
commit 0d070fb934
13 changed files with 140 additions and 96 deletions

View File

@@ -42,17 +42,17 @@ jobs:
sudo apt-get update
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "pnpm"
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i

View File

@@ -4,7 +4,7 @@ use crate::{
utils::{dirs, logging::Type},
wrap_err,
};
use tauri::Manager;
use tauri::{AppHandle, Manager};
/// 打开应用程序所在目录
#[tauri::command]
@@ -36,7 +36,7 @@ pub fn open_web_url(url: String) -> CmdResult<()> {
/// 打开/关闭开发者工具
#[tauri::command]
pub fn open_devtools(app_handle: tauri::AppHandle) {
pub fn open_devtools(app_handle: AppHandle) {
if let Some(window) = app_handle.get_webview_window("main") {
if !window.is_devtools_open() {
window.open_devtools();

View File

@@ -668,10 +668,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
/// 根据profile name修改profiles
#[tauri::command]
pub async fn patch_profiles_config_by_profile_index(
_app_handle: tauri::AppHandle,
profile_index: String,
) -> CmdResult<bool> {
pub async fn patch_profiles_config_by_profile_index(profile_index: String) -> CmdResult<bool> {
logging!(info, Type::Cmd, true, "切换配置到: {}", profile_index);
let profiles = IProfiles {
@@ -718,7 +715,7 @@ pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
/// 查看配置文件
#[tauri::command]
pub fn view_profile(app_handle: tauri::AppHandle, index: String) -> CmdResult {
pub fn view_profile(index: String) -> CmdResult {
let file = {
wrap_err!(Config::profiles().latest_ref().get_item(&index))?
.file
@@ -731,7 +728,7 @@ pub fn view_profile(app_handle: tauri::AppHandle, index: String) -> CmdResult {
ret_err!("the file not found");
}
wrap_err!(help::open_file(app_handle, path))
wrap_err!(help::open_file(path))
}
/// 读取配置文件内容

View File

@@ -255,7 +255,7 @@ impl NotificationSystem {
#[derive(Debug, Clone)]
pub struct Handle {
pub app_handle: Arc<RwLock<Option<AppHandle>>>,
pub app_handle: Arc<RwLock<Option<Arc<AppHandle>>>>,
pub is_exiting: Arc<RwLock<bool>>,
startup_errors: Arc<RwLock<Vec<ErrorMessage>>>,
startup_completed: Arc<RwLock<bool>>,
@@ -282,10 +282,10 @@ impl Handle {
Self::default()
}
pub fn init(&self, app_handle: &AppHandle) {
pub fn init(&self, app_handle: Arc<AppHandle>) {
{
let mut handle = self.app_handle.write();
*handle = Some(app_handle.clone());
*handle = Some(Arc::clone(&app_handle));
}
let mut system_opt = self.notification_system.write();
@@ -294,8 +294,9 @@ impl Handle {
}
}
pub fn app_handle(&self) -> Option<AppHandle> {
self.app_handle.read().clone()
/// 获取 AppHandle
pub fn app_handle(&self) -> Option<Arc<AppHandle>> {
self.app_handle.read().as_ref().map(Arc::clone)
}
pub fn get_window(&self) -> Option<WebviewWindow> {

View File

@@ -6,7 +6,7 @@ use crate::{
use anyhow::{bail, Result};
use parking_lot::Mutex;
use std::{collections::HashMap, fmt, str::FromStr, sync::Arc};
use tauri::Manager;
use tauri::{AppHandle, Manager};
use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState};
/// Enum representing all available hotkey functions
@@ -103,7 +103,7 @@ impl Hotkey {
}
/// Execute the function associated with a hotkey function enum
fn execute_function(function: HotkeyFunction, app_handle: &tauri::AppHandle) {
fn execute_function(function: HotkeyFunction, app_handle: Arc<AppHandle>) {
match function {
HotkeyFunction::OpenOrCloseDashboard => {
logging!(
@@ -218,7 +218,7 @@ impl Hotkey {
manager.unregister(hotkey)?;
}
let app_handle_clone = app_handle.clone();
let app_handle_clone = Arc::clone(&app_handle);
let is_quit = matches!(function, HotkeyFunction::Quit);
let _ = manager.on_shortcut(hotkey, move |app_handle, hotkey_event, event| {
@@ -229,7 +229,7 @@ impl Hotkey {
if let Some(window) = app_handle.get_webview_window("main") {
if window.is_focused().unwrap_or(false) {
logging!(debug, Type::Hotkey, "Executing quit function");
Self::execute_function(function, &app_handle_clone);
Self::execute_function(function, Arc::clone(&app_handle_clone));
}
}
} else {
@@ -241,14 +241,14 @@ impl Hotkey {
.unwrap_or(true);
if is_enable_global_hotkey {
Self::execute_function(function, &app_handle_clone);
Self::execute_function(function, Arc::clone(&app_handle_clone));
} else {
use crate::utils::window_manager::WindowManager;
let is_visible = WindowManager::is_main_window_visible();
let is_focused = WindowManager::is_main_window_focused();
if is_focused && is_visible {
Self::execute_function(function, &app_handle_clone);
Self::execute_function(function, Arc::clone(&app_handle_clone));
}
}
}

View File

@@ -15,6 +15,7 @@ use crate::{
use anyhow::Result;
use parking_lot::Mutex;
use std::sync::Arc;
use std::{
fs,
sync::atomic::{AtomicBool, Ordering},
@@ -244,7 +245,7 @@ impl Tray {
// 设置更新状态
self.menu_updating.store(true, Ordering::Release);
let result = self.update_menu_internal(&app_handle);
let result = self.update_menu_internal(app_handle);
{
let mut last_update = self.last_menu_update.lock();
@@ -255,7 +256,7 @@ impl Tray {
result
}
fn update_menu_internal(&self, app_handle: &AppHandle) -> Result<()> {
fn update_menu_internal(&self, app_handle: Arc<AppHandle>) -> Result<()> {
let verge = Config::verge().latest_ref().clone();
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
@@ -277,7 +278,7 @@ impl Tray {
match app_handle.tray_by_id("main") {
Some(tray) => {
let _ = tray.set_menu(Some(create_tray_menu(
app_handle,
&app_handle,
Some(mode.as_str()),
*system_proxy,
*tun_mode,
@@ -451,7 +452,7 @@ impl Tray {
#[cfg(target_os = "macos")]
pub fn unsubscribe_traffic(&self) {}
pub fn create_tray_from_handle(&self, app_handle: &AppHandle) -> Result<()> {
pub fn create_tray_from_handle(&self, app_handle: Arc<AppHandle>) -> Result<()> {
log::info!(target: "app", "正在从AppHandle创建系统托盘");
// 获取图标
@@ -477,7 +478,7 @@ impl Tray {
}
}
let tray = builder.build(app_handle)?;
let tray = builder.build(app_handle.as_ref())?;
tray.on_tray_icon_event(|_, event| {
let tray_event = { Config::verge().latest_ref().tray_event.clone() };

View File

@@ -11,15 +11,7 @@ use anyhow::{bail, Result};
/// Toggle proxy profile
pub fn toggle_proxy_profile(profile_index: String) {
AsyncHandler::spawn(|| async move {
let Some(app_handle) = handle::Handle::global().app_handle() else {
logging!(
error,
Type::Config,
"Failed to get app handle for profile toggle"
);
return;
};
match cmd::patch_profiles_config_by_profile_index(app_handle, profile_index).await {
match cmd::patch_profiles_config_by_profile_index(profile_index).await {
Ok(_) => {
let _ = tray::Tray::global().update_menu();
}

View File

@@ -17,6 +17,7 @@ use crate::{
};
use config::Config;
use parking_lot::Mutex;
use std::sync::Arc;
use tauri::AppHandle;
#[cfg(target_os = "macos")]
use tauri::Manager;
@@ -28,7 +29,7 @@ use utils::logging::Type;
/// A global singleton handle to the application.
pub struct AppHandleManager {
handle: Mutex<Option<AppHandle>>,
handle: Mutex<Option<Arc<AppHandle>>>,
}
impl AppHandleManager {
@@ -40,7 +41,7 @@ impl AppHandleManager {
}
/// Initialize the app handle manager with an app handle.
pub fn init(&self, handle: AppHandle) {
pub fn init(&self, handle: Arc<AppHandle>) {
let mut app_handle = self.handle.lock();
if app_handle.is_none() {
*app_handle = Some(handle);
@@ -54,12 +55,12 @@ impl AppHandleManager {
}
/// Get the app handle if it has been initialized.
pub fn get(&self) -> Option<AppHandle> {
fn get(&self) -> Option<Arc<AppHandle>> {
self.handle.lock().clone()
}
/// Get the app handle, panics if it hasn't been initialized.
pub fn get_handle(&self) -> AppHandle {
pub fn get_handle(&self) -> Arc<AppHandle> {
if let Some(handle) = self.get() {
handle
} else {
@@ -246,12 +247,12 @@ mod app_init {
}
/// Initialize core components asynchronously
pub fn init_core_async(app_handle: tauri::AppHandle) {
pub fn init_core_async(app_handle: Arc<AppHandle>) {
AsyncHandler::spawn(move || async move {
logging!(info, Type::Setup, true, "异步执行应用设置...");
match timeout(
Duration::from_secs(30),
resolve::resolve_setup_async(&app_handle),
resolve::resolve_setup_async(app_handle),
)
.await
{
@@ -271,12 +272,12 @@ mod app_init {
}
/// Initialize core components synchronously
pub fn init_core_sync(app_handle: &tauri::AppHandle) -> Result<(), Box<dyn std::error::Error>> {
pub fn init_core_sync(app_handle: Arc<AppHandle>) -> Result<(), Box<dyn std::error::Error>> {
logging!(info, Type::Setup, true, "初始化AppHandleManager...");
AppHandleManager::global().init(app_handle.clone());
AppHandleManager::global().init(Arc::clone(&app_handle));
logging!(info, Type::Setup, true, "初始化核心句柄...");
core::handle::Handle::global().init(app_handle);
core::handle::Handle::global().init(Arc::clone(&app_handle));
logging!(info, Type::Setup, true, "初始化配置...");
utils::init::init_config()?;
@@ -482,13 +483,16 @@ pub fn run() {
);
}
let app_handle = app.handle().clone();
let app_handle = Arc::new(app_handle);
// Initialize core components asynchronously
app_init::init_core_async(app.handle().clone());
app_init::init_core_async(Arc::clone(&app_handle));
logging!(info, Type::Setup, true, "执行主要设置操作...");
// Initialize core components synchronously
if let Err(e) = app_init::init_core_sync(app.handle()) {
if let Err(e) = app_init::init_core_sync(Arc::clone(&app_handle)) {
logging!(
error,
Type::Setup,
@@ -509,9 +513,9 @@ pub fn run() {
use super::*;
/// Handle application ready/resumed events
pub fn handle_ready_resumed(app_handle: &tauri::AppHandle) {
pub fn handle_ready_resumed(app_handle: Arc<AppHandle>) {
logging!(info, Type::System, true, "应用就绪或恢复");
AppHandleManager::global().init(app_handle.clone());
AppHandleManager::global().init(Arc::clone(&app_handle));
#[cfg(target_os = "macos")]
{
@@ -524,7 +528,7 @@ pub fn run() {
/// Handle application reopen events (macOS)
#[cfg(target_os = "macos")]
pub fn handle_reopen(app_handle: &tauri::AppHandle, has_visible_windows: bool) {
pub fn handle_reopen(app_handle: Arc<AppHandle>, has_visible_windows: bool) {
logging!(
info,
Type::System,
@@ -533,7 +537,7 @@ pub fn run() {
has_visible_windows
);
AppHandleManager::global().init(app_handle.clone());
AppHandleManager::global().init(Arc::clone(&app_handle));
if !has_visible_windows {
// 当没有可见窗口时,设置为 regular 模式并显示主窗口
@@ -685,9 +689,11 @@ pub fn run() {
std::process::exit(1);
});
app.run(|app_handle, e| match e {
app.run(|app_handle, e| {
let app_handle = Arc::new(app_handle.clone());
match e {
tauri::RunEvent::Ready | tauri::RunEvent::Resumed => {
event_handlers::handle_ready_resumed(app_handle);
event_handlers::handle_ready_resumed(Arc::clone(&app_handle));
}
#[cfg(target_os = "macos")]
tauri::RunEvent::Reopen {
@@ -725,5 +731,6 @@ pub fn run() {
}
}
_ => {}
}
});
}

View File

@@ -1,22 +1,63 @@
#[cfg(feature = "tokio-trace")]
use std::any::type_name;
use std::future::Future;
#[cfg(feature = "tokio-trace")]
use std::panic::Location;
use tauri::{async_runtime, async_runtime::JoinHandle};
pub struct AsyncHandler;
impl AsyncHandler {
#[track_caller]
pub fn spawn<F, Fut>(f: F) -> JoinHandle<()>
where
F: FnOnce() -> Fut + Send + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
#[cfg(feature = "tokio-trace")]
Self::log_task_info(&f);
async_runtime::spawn(f())
}
#[track_caller]
pub fn spawn_blocking<T, F>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
#[cfg(feature = "tokio-trace")]
Self::log_task_info(&f);
async_runtime::spawn_blocking(f)
}
#[cfg(feature = "tokio-trace")]
#[track_caller]
fn log_task_info<F>(f: &F)
where
F: ?Sized,
{
const TRACE_MINI_SIZE: usize = 0;
let size = std::mem::size_of_val(f);
if size <= TRACE_MINI_SIZE {
return;
}
let location = Location::caller();
let type_str = type_name::<F>();
let size_str = format!("{} bytes", size);
let loc_str = format!(
"{}:{}:{}",
location.file(),
location.line(),
location.column()
);
println!("┌────────────────────┬─────────────────────────────────────────────────────────────────────────────┐");
println!("{:<18}{:<80}", "Field", "Value");
println!("├────────────────────┼─────────────────────────────────────────────────────────────────────────────┤");
println!("{:<18}{:<80}", "Type of task", type_str);
println!("{:<18}{:<80}", "Size of task", size_str);
println!("{:<18}{:<80}", "Called from", loc_str);
println!("└────────────────────┴─────────────────────────────────────────────────────────────────────────────┘");
}
}

View File

@@ -120,7 +120,7 @@ pub fn get_last_part_and_decode(url: &str) -> Option<String> {
}
/// open file
pub fn open_file(_: tauri::AppHandle, path: PathBuf) -> Result<()> {
pub fn open_file(path: PathBuf) -> Result<()> {
open::that_detached(path.as_os_str())?;
Ok(())
}

View File

@@ -1,3 +1,5 @@
use std::sync::Arc;
use tauri::AppHandle;
use tauri_plugin_notification::NotificationExt;
@@ -14,7 +16,7 @@ pub enum NotificationEvent<'a> {
AppHidden,
}
fn notify(app: &AppHandle, title: &str, body: &str) {
fn notify(app: Arc<AppHandle>, title: &str, body: &str) {
app.notification()
.builder()
.title(title)
@@ -23,7 +25,7 @@ fn notify(app: &AppHandle, title: &str, body: &str) {
.ok();
}
pub fn notify_event(app: &AppHandle, event: NotificationEvent) {
pub fn notify_event(app: Arc<AppHandle>, event: NotificationEvent) {
use crate::utils::i18n::t;
match event {
NotificationEvent::DashboardToggled => {

View File

@@ -14,7 +14,10 @@ use once_cell::sync::OnceCell;
use parking_lot::{Mutex, RwLock};
use percent_encoding::percent_decode_str;
use scopeguard;
use std::time::{Duration, Instant};
use std::{
sync::Arc,
time::{Duration, Instant},
};
use tauri::{AppHandle, Manager};
use tauri::Url;
@@ -106,7 +109,7 @@ pub fn reset_ui_ready() {
}
/// 异步方式处理启动后的额外任务
pub async fn resolve_setup_async(app_handle: &AppHandle) {
pub async fn resolve_setup_async(app_handle: Arc<AppHandle>) {
let start_time = std::time::Instant::now();
logging!(
info,
@@ -162,7 +165,7 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) {
if let Some(app_handle) = handle::Handle::global().app_handle() {
logging!(info, Type::Tray, true, "创建系统托盘...");
let result = tray::Tray::global().create_tray_from_handle(&app_handle);
let result = tray::Tray::global().create_tray_from_handle(app_handle);
if result.is_ok() {
logging!(info, Type::Tray, true, "系统托盘创建成功");
} else if let Err(e) = result {
@@ -329,7 +332,7 @@ pub fn create_window(is_show: bool) -> bool {
};
match tauri::WebviewWindowBuilder::new(
&app_handle,
&*app_handle,
"main", /* the unique window label */
tauri::WebviewUrl::App("index.html".into()),
)

View File

@@ -48,7 +48,7 @@ export const ProxyHead = (props: Props) => {
useEffect(() => {
delayManager.setUrl(
groupName,
testUrl || url || verge?.default_latency_test,
testUrl || url || verge?.default_latency_test!,
);
}, [groupName, testUrl, verge?.default_latency_test]);