版本:v3.0.0-alpha.2-Nonomi-bf53388(HEAD) · Windows 10 · 1920×1080 · 自包含 exe
现象
配合任意会注册浮窗按钮的插件(如 SecScore Launcher)可复现三件事:
- 退出/重启应用时崩溃;
- 把注销提前到
AppStopping 修掉崩溃后,每次干净退出,插件的浮窗按钮会从设置里消失,下次启动不再显示;
- 偶发:启动时浮窗首次刷新早于插件注册,也会误删该按钮的显示开关。
Bug 1 —— 退出崩溃(本体没有「安全注销点」)
插件在 hosted service StopAsync 里注销按钮是常规写法;但宿主 App.StopAsync()(SecRandom/App.axaml.cs)的执行次序是:
触发 AppStopping (1155) → DisposePluginsAsync (1167) → host.StopAsync() (1183,此刻才停 hosted service,插件注销发生在这) → host.Dispose() (1193) → IAppHost.Host = null (1204)。
注销会触发 FloatingWindowButtonRegistry.Changed → 宿主 FloatingWindowButtonRegistry_OnChanged 用 Dispatcher.Post(RefreshItems) 排队;该任务晚于 IAppHost.Host = null 才执行 → GetVisiblePluginButtons(FloatingWindow.axaml.cs:130)调用 IAppHost.GetService<IFloatingWindowButtonRegistry>(),而 IAppHost.GetService 在 Host 为 null 时直接抛 ArgumentException("Service ... is null!")(IAppHost.cs:21-27),应用在退出阶段崩溃并写 crash dump。
崩溃堆栈(data/crashes/*.txt):
System.ArgumentException: Service ...IFloatingWindowButtonRegistry is null!
at IAppHost.GetService[T]()
at FloatingWindow.GetVisiblePluginButtons(...)
at FloatingWindow.RefreshItems()
at Avalonia.Threading.DispatcherOperation.InvokeCore()
另一份为 ObjectDisposedException(处置时 App.TrySaveConfigForCrashRecovery 访问已 Dispose 的 IServiceProvider)。
注:构造函数(同文件 :79)用的是空安全的 TryGetService,:130 却用会抛异常的 GetService。
Bug 2 —— 干净退出后按钮消失
把注销提前到 AppStopping 后不再崩,但 RefreshItems 在 Host 存活时执行,GetVisiblePluginButtons 的「过期清理」(FloatingWindow.axaml.cs:133-141)会把注册表里已不存在的 id 从 settings.json 的 visible_plugin_button_ids 删除并保存 → 干净退出一次,按钮就永久消失(下次启动插件虽重新注册,但显示开关已被删)。
也就是说宿主没有给插件「关闭时安全注销且不影响持久化配置」的窗口:该清理的本意是「插件被移除」,不应在应用停机阶段触发。
Bug 3 —— 启动偶发误删
启动时浮窗首次 RefreshItems 若早于插件 hosted service 完成注册(注册表瞬时为空),同样会误删新插件的 id。
建议修复(本体)
FloatingWindow.GetVisiblePluginButtons 改用 TryGetService,Host 为 null(停机阶段)时返回空而不抛异常;或在 RefreshItems 入口先判 IAppHost.Host == null。
- 停机阶段的按钮注销不应触发「过期清理/持久化删除」:停机期间跳过该清理;或当某插件按钮注册而其 id 不在列表时自动补回而非删除。
TrySaveConfigForCrashRecovery 在 Host 已 Dispose / 为 null 时应直接跳过。
在以上修复前,插件侧只能以「注册后不注销」规避(浮窗按钮注册表为进程级、不落盘,进程退出即消失,可接受)。
版本:v3.0.0-alpha.2-Nonomi-bf53388(HEAD) · Windows 10 · 1920×1080 · 自包含 exe
现象
配合任意会注册浮窗按钮的插件(如 SecScore Launcher)可复现三件事:
AppStopping修掉崩溃后,每次干净退出,插件的浮窗按钮会从设置里消失,下次启动不再显示;Bug 1 —— 退出崩溃(本体没有「安全注销点」)
插件在 hosted service
StopAsync里注销按钮是常规写法;但宿主App.StopAsync()(SecRandom/App.axaml.cs)的执行次序是:触发 AppStopping (1155)→DisposePluginsAsync (1167)→host.StopAsync() (1183,此刻才停 hosted service,插件注销发生在这)→host.Dispose() (1193)→IAppHost.Host = null (1204)。注销会触发
FloatingWindowButtonRegistry.Changed→ 宿主FloatingWindowButtonRegistry_OnChanged用Dispatcher.Post(RefreshItems)排队;该任务晚于IAppHost.Host = null才执行 →GetVisiblePluginButtons(FloatingWindow.axaml.cs:130)调用IAppHost.GetService<IFloatingWindowButtonRegistry>(),而IAppHost.GetService在 Host 为 null 时直接抛ArgumentException("Service ... is null!")(IAppHost.cs:21-27),应用在退出阶段崩溃并写 crash dump。崩溃堆栈(
data/crashes/*.txt):另一份为
ObjectDisposedException(处置时App.TrySaveConfigForCrashRecovery访问已 Dispose 的IServiceProvider)。注:构造函数(同文件 :79)用的是空安全的
TryGetService,:130 却用会抛异常的GetService。Bug 2 —— 干净退出后按钮消失
把注销提前到
AppStopping后不再崩,但RefreshItems在 Host 存活时执行,GetVisiblePluginButtons的「过期清理」(FloatingWindow.axaml.cs:133-141)会把注册表里已不存在的 id 从settings.json的visible_plugin_button_ids删除并保存 → 干净退出一次,按钮就永久消失(下次启动插件虽重新注册,但显示开关已被删)。也就是说宿主没有给插件「关闭时安全注销且不影响持久化配置」的窗口:该清理的本意是「插件被移除」,不应在应用停机阶段触发。
Bug 3 —— 启动偶发误删
启动时浮窗首次
RefreshItems若早于插件 hosted service 完成注册(注册表瞬时为空),同样会误删新插件的 id。建议修复(本体)
FloatingWindow.GetVisiblePluginButtons改用TryGetService,Host 为 null(停机阶段)时返回空而不抛异常;或在RefreshItems入口先判IAppHost.Host == null。TrySaveConfigForCrashRecovery在 Host 已 Dispose / 为 null 时应直接跳过。在以上修复前,插件侧只能以「注册后不注销」规避(浮窗按钮注册表为进程级、不落盘,进程退出即消失,可接受)。