mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-23 18:04:26 +08:00
* ava12升级 * Enable centralized package versioning Add <Project> and <PropertyGroup> with <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> to Directory.Packages.props to enable centralized package version management across the repository. This allows package versions to be controlled from this single file instead of individual project files. * Migrate codebase to Avalonia 12 APIs Apply Avalonia 12 migration changes: replace SystemDecorations with WindowDecorations and remove ExtendClientAreaChromeHints/ExtendClientAreaTitleBarHeightHint usages; update BindingPlugins removal logic (no-op); switch clipboard usage to ClipboardExtensions.SetTextAsync; update Bitmap.CopyPixels calls to the new signature. Replace TextBox.Watermark with PlaceholderText, convert NumberBox styles to FANumberBox and adjust templates, change Checked/Unchecked handlers to IsCheckedChanged, and adapt FluentIcons usages (SymbolIconSource -> FASymbol/FAFont/FluentIcon equivalents). Fix MainWindow partial classes to inherit Window and correct missing variables/fields/usings. Add migration docs/specs/tasks under .trae and include a small TestFluentIcons project for icon testing. * Migrate to Avalonia 12 and Plugin SDK v5 Upgrade project to the Avalonia 12 baseline and Plugin SDK v5: centralize Avalonia packages, remove legacy WebView.Avalonia usage (use NativeWebView/WebView2 EnvironmentRequested), and update Fluent/Material icon/package usages. Bump multiple package/project versions to 5.0.0 and Avalonia 12.0.1, update plugin template and README/docs to SDK v5, and add PLUGIN_SDK_V5_MIGRATION.md. Also fix runtime/behavior bugs: make DataLocationResolver use a fixed bootstrap launcher data path and avoid recursive ResolveDataRoot; add legacy-state handling and extraction in OobeStateService; and update component settings tests to reflect migrated storage (DB/backup) and reset cache for test reloads. Various csproj, tests, and docs updated to reflect the migration and ensure build/test compatibility. * Update icon glyphs and symbol mappings Replace and refine icon sources across settings pages and controls: many FAFontIconSource glyphs were updated to specific Seagull Fluent Icons codepoints, some FASymbolIconSource usages were replaced with FAFontIconSource, and a number of symbol-to-Symbol enum mappings were adjusted (e.g. "Bell" -> AlertOn, "Shield" -> ShieldLock). Also clarified a comment in SettingsWindow and fixed a trailing newline in StudySettingsPage. Changes standardize icon visuals and bridge FluentIcons glyphs into FluentAvalonia icon sources. * fix.修复合并产生的问题。
399 lines
13 KiB
Markdown
399 lines
13 KiB
Markdown
# 01-插件生命周期
|
||
|
||
理解插件的生命周期,是开发稳定可靠插件的基础。本文详细讲解插件从加载到卸载的完整过程。
|
||
|
||
---
|
||
|
||
## 🔄 生命周期概览
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 阑山桌面启动 │
|
||
└───────────────────────┬─────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 1. 发现插件 │
|
||
│ - 扫描插件目录 │
|
||
│ - 解析 plugin.json │
|
||
│ - 验证 API 版本兼容性 │
|
||
└───────────────────────┬─────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 2. 加载插件 │
|
||
│ - 创建 AssemblyLoadContext │
|
||
│ - 加载插件 DLL │
|
||
│ - 查找入口类(带 [PluginEntrance] 特性) │
|
||
└───────────────────────┬─────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 3. 初始化(Initialize) │
|
||
│ - 调用 Plugin.Initialize() │
|
||
│ - 注册组件、设置页面、服务 │
|
||
│ - ⚠️ 此时 UI 尚未完全就绪 │
|
||
└───────────────────────┬─────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 4. 运行中 │
|
||
│ - 组件被添加到桌面 │
|
||
│ - 用户与组件交互 │
|
||
│ - 设置页面被打开 │
|
||
└───────────────────────┬─────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 5. 停用/卸载 │
|
||
│ - 用户禁用插件 │
|
||
│ - 或关闭阑山桌面 │
|
||
│ - 释放资源(当前版本无显式卸载回调) │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 📋 各阶段详解
|
||
|
||
### 阶段 1:发现插件
|
||
|
||
**时机:** 阑山桌面启动时
|
||
|
||
**过程:**
|
||
1. 扫描 `%LOCALAPPDATA%\LanMountainDesktop\plugins\` 目录
|
||
2. 读取每个 `.laapp` 包中的 `plugin.json`
|
||
3. 验证 `apiVersion` 是否与宿主兼容
|
||
4. 检查 `id` 是否唯一
|
||
|
||
**可能失败的原因:**
|
||
- `plugin.json` 格式错误
|
||
- `apiVersion` 不兼容
|
||
- `id` 与其他插件冲突
|
||
|
||
---
|
||
|
||
### 阶段 2:加载插件
|
||
|
||
**时机:** 发现成功后
|
||
|
||
**过程:**
|
||
1. 创建独立的 `AssemblyLoadContext`
|
||
2. 加载插件 DLL 及其依赖项
|
||
3. 查找带有 `[PluginEntrance]` 特性的类
|
||
4. 实例化插件入口类
|
||
|
||
**代码示例:**
|
||
```csharp
|
||
[PluginEntrance] // ← 这个特性标记入口类
|
||
public sealed class Plugin : PluginBase
|
||
{
|
||
// 插件实例在此阶段被创建
|
||
}
|
||
```
|
||
|
||
⚠️ **重要:** 此阶段**不要**执行耗时操作,只应进行简单的字段初始化。
|
||
|
||
---
|
||
|
||
### 阶段 3:初始化(Initialize)
|
||
|
||
**时机:** 插件加载完成后
|
||
|
||
**这是插件开发中最重要的阶段!**
|
||
|
||
#### 方法签名
|
||
|
||
```csharp
|
||
public override void Initialize(
|
||
HostBuilderContext context, // 宿主构建上下文
|
||
IServiceCollection services) // 服务注册集合
|
||
```
|
||
|
||
#### 可执行的操作
|
||
|
||
✅ **可以做的:**
|
||
- 注册桌面组件
|
||
- 注册设置页面
|
||
- 注册服务到依赖注入容器
|
||
- 读取配置
|
||
- 初始化资源
|
||
|
||
❌ **不应该做的:**
|
||
- 访问 UI 元素(UI 尚未就绪)
|
||
- 执行耗时阻塞操作
|
||
- 创建窗口或对话框
|
||
|
||
#### 典型初始化代码
|
||
|
||
```csharp
|
||
public override void Initialize(HostBuilderContext context, IServiceCollection services)
|
||
{
|
||
// 1. 注册服务
|
||
services.AddSingleton<IWeatherService, WeatherService>();
|
||
|
||
// 2. 注册桌面组件
|
||
services.AddPluginDesktopComponent<WeatherWidget>(
|
||
new PluginDesktopComponentOptions
|
||
{
|
||
ComponentId = "MyPlugin.Weather",
|
||
DisplayName = "天气",
|
||
IconKey = "Weather",
|
||
Category = "工具",
|
||
MinWidthCells = 4,
|
||
MinHeightCells = 3
|
||
});
|
||
|
||
// 3. 注册设置页面
|
||
services.AddPluginSettingsSection(
|
||
"myplugin-settings",
|
||
"天气设置",
|
||
section => section
|
||
.AddToggle("auto_refresh", "自动刷新", defaultValue: true)
|
||
.AddNumber("interval", "刷新间隔(分钟)", defaultValue: 30),
|
||
iconKey: "Settings");
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 阶段 4:运行中
|
||
|
||
**时机:** 初始化完成后,直到插件被禁用或宿主关闭
|
||
|
||
**特点:**
|
||
- 组件可以被添加到桌面
|
||
- 用户可以与组件交互
|
||
- 设置页面可以被打开
|
||
- 定时器可以运行
|
||
|
||
#### 组件生命周期
|
||
|
||
```
|
||
用户添加组件
|
||
│
|
||
▼
|
||
┌─────────────────┐
|
||
│ 创建组件实例 │ ← 调用构造函数
|
||
│ (Dependency │ 注入 IServiceProvider
|
||
│ Injection) │
|
||
└────────┬────────┘
|
||
│
|
||
▼
|
||
┌─────────────────┐
|
||
│ 组件初始化 │ ← 可在此时加载数据
|
||
│ (Loaded事件) │
|
||
└────────┬────────┘
|
||
│
|
||
▼
|
||
┌─────────────────┐
|
||
│ 渲染显示 │ ← 用户看到组件
|
||
└────────┬────────┘
|
||
│
|
||
┌────┴────┐
|
||
▼ ▼
|
||
用户交互 定时更新
|
||
│ │
|
||
└────┬────┘
|
||
│
|
||
▼
|
||
┌─────────────────┐
|
||
│ 组件移除 │ ← 用户删除组件
|
||
│ (Unloaded事件) │ 或关闭宿主
|
||
└─────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### 阶段 5:停用/卸载
|
||
|
||
**时机:**
|
||
- 用户在设置中禁用插件
|
||
- 卸载插件
|
||
- 关闭阑山桌面
|
||
|
||
**当前限制:**
|
||
- SDK v5 暂无显式的卸载回调方法
|
||
- 资源释放依赖 .NET 垃圾回收
|
||
- 建议:
|
||
- 使用 `IDisposable` 模式管理资源
|
||
- 在组件卸载事件中清理资源
|
||
|
||
---
|
||
|
||
## ⏱️ 启动时序图
|
||
|
||
```
|
||
阑山桌面 插件系统 你的插件
|
||
│ │ │
|
||
│── 启动 ───────►│ │
|
||
│ │ │
|
||
│ │── 发现插件 ───►│
|
||
│ │ │ (读取 plugin.json)
|
||
│ │◄───────────────│
|
||
│ │ │
|
||
│ │── 加载 DLL ───►│
|
||
│ │ │ (AssemblyLoadContext)
|
||
│ │◄───────────────│
|
||
│ │ │
|
||
│ │── 创建实例 ───►│
|
||
│ │ │ (调用构造函数)
|
||
│ │◄───────────────│
|
||
│ │ │
|
||
│ │── Initialize ─►│
|
||
│ │ │ (注册组件/服务)
|
||
│ │◄───────────────│
|
||
│ │ │
|
||
│◄───────────────│ │
|
||
│ │ │
|
||
│── UI就绪 ─────►│ │
|
||
│ │ │
|
||
│ │── 用户添加组件 ─►│
|
||
│ │ │ (创建组件实例)
|
||
```
|
||
|
||
---
|
||
|
||
## 💡 最佳实践
|
||
|
||
### 1. Initialize 方法保持轻量
|
||
|
||
```csharp
|
||
// ✅ 好的做法:快速注册
|
||
public override void Initialize(HostBuilderContext context, IServiceCollection services)
|
||
{
|
||
services.AddSingleton<IDataService, DataService>();
|
||
services.AddPluginDesktopComponent<MyWidget>(options);
|
||
}
|
||
|
||
// ❌ 避免:耗时操作
|
||
public override void Initialize(HostBuilderContext context, IServiceCollection services)
|
||
{
|
||
// 不要这样做!
|
||
var data = FetchDataFromInternet().Result; // 阻塞!
|
||
services.AddSingleton(data);
|
||
}
|
||
```
|
||
|
||
### 2. 延迟加载数据
|
||
|
||
```csharp
|
||
public class MyWidget : Border
|
||
{
|
||
private readonly IDataService _dataService;
|
||
|
||
public MyWidget(PluginDesktopComponentContext context)
|
||
{
|
||
_dataService = context.ServiceProvider.GetRequiredService<IDataService>();
|
||
|
||
// 在 Loaded 事件中加载数据,而不是构造函数
|
||
Loaded += async (_, _) =>
|
||
{
|
||
await LoadDataAsync();
|
||
};
|
||
}
|
||
|
||
private async Task LoadDataAsync()
|
||
{
|
||
var data = await _dataService.GetDataAsync();
|
||
// 更新 UI
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 正确处理资源释放
|
||
|
||
```csharp
|
||
public class MyWidget : Border, IDisposable
|
||
{
|
||
private readonly Timer _timer;
|
||
private bool _disposed;
|
||
|
||
public MyWidget()
|
||
{
|
||
_timer = new Timer(OnTimerTick, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
|
||
|
||
// 在卸载时释放资源
|
||
Unloaded += (_, _) => Dispose();
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
if (_disposed) return;
|
||
_disposed = true;
|
||
|
||
_timer?.Dispose();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. 避免循环依赖
|
||
|
||
```csharp
|
||
// ❌ 避免:服务之间相互依赖
|
||
public class ServiceA
|
||
{
|
||
public ServiceA(ServiceB b) { } // 循环依赖风险
|
||
}
|
||
|
||
public class ServiceB
|
||
{
|
||
public ServiceB(ServiceA a) { }
|
||
}
|
||
|
||
// ✅ 好的做法:使用接口解耦
|
||
public class ServiceA
|
||
{
|
||
public ServiceA(IServiceB b) { }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🐛 常见问题
|
||
|
||
### 问题 1:Initialize 中访问 UI 报错
|
||
|
||
**现象:** `InvalidOperationException` 或空引用
|
||
|
||
**原因:** Initialize 在 UI 就绪前调用
|
||
|
||
**解决:** 延迟到组件创建后再访问 UI
|
||
|
||
### 问题 2:服务注册顺序问题
|
||
|
||
**现象:** 依赖注入找不到服务
|
||
|
||
**原因:** 服务注册顺序不正确
|
||
|
||
**解决:** 先注册服务,再注册依赖这些服务的组件
|
||
|
||
### 问题 3:插件加载慢
|
||
|
||
**现象:** 宿主启动变慢
|
||
|
||
**原因:** Initialize 中执行耗时操作
|
||
|
||
**解决:** 将耗时操作移到后台线程或延迟执行
|
||
|
||
---
|
||
|
||
## 📚 参考资源
|
||
|
||
- [PluginBase 源码](../../LanMountainDesktop.PluginSdk/PluginBase.cs)
|
||
- [IPlugin 接口](../../LanMountainDesktop.PluginSdk/IPlugin.cs)
|
||
- [02-桌面组件系统](02-桌面组件系统.md)
|
||
- [03-设置系统集成](03-设置系统集成.md)
|
||
|
||
---
|
||
|
||
## 🎯 下一步
|
||
|
||
理解生命周期后,学习如何创建桌面组件:
|
||
|
||
👉 **[02-桌面组件系统](02-桌面组件系统.md)** - 创建可视化组件
|
||
|
||
---
|
||
|
||
*最后更新:2026年4月*
|