Files
LanMountainDesktop/docs/Plugins develop/02-核心概念与原理/01-插件生命周期.md
2026-04-13 19:54:37 +08:00

399 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 v4 暂无显式的卸载回调方法
- 资源释放依赖 .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) { }
}
```
---
## 🐛 常见问题
### 问题 1Initialize 中访问 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月*