mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-23 18:04:26 +08:00
feat.文档完善
This commit is contained in:
683
docs/01-插件开发/02-核心概念/01-插件生命周期.md
Normal file
683
docs/01-插件开发/02-核心概念/01-插件生命周期.md
Normal file
@@ -0,0 +1,683 @@
|
||||
# 插件生命周期
|
||||
|
||||
本文档详细介绍阑山桌面插件的生命周期、加载流程和各个阶段的职责。
|
||||
|
||||
## 生命周期概览
|
||||
|
||||
插件从加载到卸载经历以下阶段:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 插件生命周期 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
|
||||
1. 发现 (Discovery)
|
||||
├─ 扫描插件目录
|
||||
├─ 读取 plugin.json
|
||||
└─ 验证基本信息
|
||||
↓
|
||||
2. 加载 (Load)
|
||||
├─ 创建 PluginLoadContext
|
||||
├─ 加载程序集
|
||||
├─ 解析依赖关系
|
||||
└─ 验证兼容性
|
||||
↓
|
||||
3. 实例化 (Instantiate)
|
||||
├─ 反射查找 IPlugin 实现
|
||||
├─ 创建插件实例
|
||||
└─ 注入依赖
|
||||
↓
|
||||
4. 初始化 (Initialize)
|
||||
├─ 调用 InitializeAsync()
|
||||
├─ 注册组件
|
||||
├─ 注册设置页
|
||||
├─ 注册服务
|
||||
└─ 订阅事件
|
||||
↓
|
||||
5. 运行中 (Running)
|
||||
├─ 组件渲染和更新
|
||||
├─ 处理用户交互
|
||||
├─ 响应事件
|
||||
└─ 执行后台任务
|
||||
↓
|
||||
6. 关闭 (Shutdown)
|
||||
├─ 调用 ShutdownAsync()
|
||||
├─ 保存状态
|
||||
├─ 取消订阅
|
||||
├─ 清理资源
|
||||
└─ 卸载程序集
|
||||
```
|
||||
|
||||
## 详细阶段说明
|
||||
|
||||
### 1. 发现阶段 (Discovery)
|
||||
|
||||
**时机**: 宿主启动时
|
||||
|
||||
**职责**: 扫描和识别插件
|
||||
|
||||
**流程**:
|
||||
|
||||
```csharp
|
||||
// PluginDiscoveryService.cs (宿主代码)
|
||||
public List<PluginDescriptor> DiscoverPlugins()
|
||||
{
|
||||
var pluginsDir = Path.Combine(
|
||||
AppDataPath,
|
||||
"plugins"
|
||||
);
|
||||
|
||||
var descriptors = new List<PluginDescriptor>();
|
||||
|
||||
// 1. 扫描插件目录
|
||||
foreach (var dir in Directory.GetDirectories(pluginsDir))
|
||||
{
|
||||
var manifestPath = Path.Combine(dir, "plugin.json");
|
||||
|
||||
// 2. 读取 plugin.json
|
||||
if (!File.Exists(manifestPath))
|
||||
{
|
||||
_logger.LogWarning($"Plugin manifest not found: {dir}");
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(manifestPath);
|
||||
var manifest = JsonSerializer.Deserialize<PluginManifest>(json);
|
||||
|
||||
// 3. 验证基本信息
|
||||
if (string.IsNullOrEmpty(manifest?.Id))
|
||||
{
|
||||
_logger.LogWarning($"Invalid plugin manifest: {dir}");
|
||||
continue;
|
||||
}
|
||||
|
||||
descriptors.Add(new PluginDescriptor
|
||||
{
|
||||
Id = manifest.Id,
|
||||
Name = manifest.Name,
|
||||
Version = manifest.Version,
|
||||
Directory = dir,
|
||||
Manifest = manifest
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Failed to read plugin manifest: {dir}");
|
||||
}
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
```
|
||||
|
||||
**开发者注意事项**:
|
||||
- ✅ 确保 `plugin.json` 存在且格式正确
|
||||
- ✅ 确保插件 ID 唯一
|
||||
- ✅ 确保版本号符合语义化版本规范
|
||||
|
||||
### 2. 加载阶段 (Load)
|
||||
|
||||
**时机**: 发现插件后
|
||||
|
||||
**职责**: 加载插件程序集
|
||||
|
||||
**流程**:
|
||||
|
||||
```csharp
|
||||
// PluginLoader.cs (宿主代码)
|
||||
public PluginLoadResult LoadPlugin(PluginDescriptor descriptor)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. 创建隔离的加载上下文
|
||||
var loadContext = new PluginLoadContext(descriptor.Directory);
|
||||
|
||||
// 2. 查找主程序集
|
||||
var assemblyPath = Path.Combine(
|
||||
descriptor.Directory,
|
||||
$"{descriptor.Id}.dll"
|
||||
);
|
||||
|
||||
if (!File.Exists(assemblyPath))
|
||||
{
|
||||
return PluginLoadResult.Failed(
|
||||
$"Plugin assembly not found: {assemblyPath}"
|
||||
);
|
||||
}
|
||||
|
||||
// 3. 加载程序集
|
||||
var assembly = loadContext.LoadFromAssemblyPath(assemblyPath);
|
||||
|
||||
// 4. 验证依赖
|
||||
if (!ValidateDependencies(descriptor.Manifest.Dependencies))
|
||||
{
|
||||
return PluginLoadResult.Failed("Dependency validation failed");
|
||||
}
|
||||
|
||||
// 5. 验证宿主版本兼容性
|
||||
if (!IsHostVersionCompatible(descriptor.Manifest.MinHostVersion))
|
||||
{
|
||||
return PluginLoadResult.Failed(
|
||||
$"Incompatible host version. Required: {descriptor.Manifest.MinHostVersion}"
|
||||
);
|
||||
}
|
||||
|
||||
return PluginLoadResult.Success(assembly, loadContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Failed to load plugin: {descriptor.Id}");
|
||||
return PluginLoadResult.Failed(ex.Message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**依赖解析**:
|
||||
|
||||
```json
|
||||
// plugin.json
|
||||
{
|
||||
"Dependencies": [
|
||||
{
|
||||
"PluginId": "com.example.anotherplugin",
|
||||
"MinVersion": "1.0.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**开发者注意事项**:
|
||||
- ✅ 主程序集名称应与插件 ID 匹配(或在清单中指定)
|
||||
- ✅ 所有依赖的 DLL 应在插件目录中
|
||||
- ✅ 声明对其他插件的依赖关系
|
||||
|
||||
### 3. 实例化阶段 (Instantiate)
|
||||
|
||||
**时机**: 程序集加载后
|
||||
|
||||
**职责**: 创建插件实例
|
||||
|
||||
**流程**:
|
||||
|
||||
```csharp
|
||||
// PluginActivator.cs (宿主代码)
|
||||
public IPlugin? CreatePluginInstance(Assembly assembly)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. 查找 IPlugin 实现类
|
||||
var pluginType = assembly.GetTypes()
|
||||
.FirstOrDefault(t =>
|
||||
typeof(IPlugin).IsAssignableFrom(t) &&
|
||||
!t.IsAbstract &&
|
||||
!t.IsInterface
|
||||
);
|
||||
|
||||
if (pluginType == null)
|
||||
{
|
||||
_logger.LogError("No IPlugin implementation found");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 创建实例
|
||||
var plugin = Activator.CreateInstance(pluginType) as IPlugin;
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
_logger.LogError("Failed to create plugin instance");
|
||||
return null;
|
||||
}
|
||||
|
||||
return plugin;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to instantiate plugin");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**开发者注意事项**:
|
||||
- ✅ 插件类必须有公共无参构造函数
|
||||
- ✅ 一个插件程序集只能有一个 IPlugin 实现
|
||||
- ✅ 不要在构造函数中执行耗时操作
|
||||
|
||||
### 4. 初始化阶段 (Initialize)
|
||||
|
||||
**时机**: 插件实例创建后
|
||||
|
||||
**职责**: 注册组件、服务和事件
|
||||
|
||||
**插件代码示例**:
|
||||
|
||||
```csharp
|
||||
public class MyPlugin : IPlugin
|
||||
{
|
||||
public async Task InitializeAsync(IPluginContext context)
|
||||
{
|
||||
// 1. 记录日志
|
||||
context.Logger.LogInformation($"{Name} is initializing...");
|
||||
|
||||
// 2. 注册组件
|
||||
var componentRegistry = context.Services
|
||||
.GetService<IComponentRegistry>();
|
||||
|
||||
if (componentRegistry != null)
|
||||
{
|
||||
// 注册多个组件
|
||||
componentRegistry.RegisterComponent<WeatherComponent>();
|
||||
componentRegistry.RegisterComponent<ClockComponent>();
|
||||
|
||||
context.Logger.LogInformation("Components registered");
|
||||
}
|
||||
|
||||
// 3. 注册设置页
|
||||
var settingsRegistry = context.Services
|
||||
.GetService<ISettingsPageRegistry>();
|
||||
|
||||
if (settingsRegistry != null)
|
||||
{
|
||||
settingsRegistry.RegisterPage<MySettingsPage>(
|
||||
title: "我的插件",
|
||||
category: "插件"
|
||||
);
|
||||
|
||||
context.Logger.LogInformation("Settings page registered");
|
||||
}
|
||||
|
||||
// 4. 注册公共 IPC 服务(如果需要)
|
||||
var ipcBuilder = context.Services
|
||||
.GetService<IPluginPublicIpcBuilder>();
|
||||
|
||||
if (ipcBuilder != null)
|
||||
{
|
||||
ipcBuilder.AddService<IMyPublicService>(
|
||||
objectId: "default",
|
||||
notifyIds: new[] { "myplugin.event.changed" }
|
||||
);
|
||||
}
|
||||
|
||||
// 5. 订阅宿主事件
|
||||
var eventBus = context.Services
|
||||
.GetService<IEventBus>();
|
||||
|
||||
if (eventBus != null)
|
||||
{
|
||||
eventBus.Subscribe<ThemeChangedEvent>(OnThemeChanged);
|
||||
}
|
||||
|
||||
// 6. 初始化后台服务(如果有)
|
||||
await InitializeBackgroundServicesAsync(context);
|
||||
|
||||
context.Logger.LogInformation($"{Name} initialized successfully");
|
||||
}
|
||||
|
||||
private void OnThemeChanged(ThemeChangedEvent evt)
|
||||
{
|
||||
// 响应主题变更
|
||||
}
|
||||
|
||||
private async Task InitializeBackgroundServicesAsync(IPluginContext context)
|
||||
{
|
||||
// 启动定时任务等
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**初始化最佳实践**:
|
||||
|
||||
```csharp
|
||||
public async Task InitializeAsync(IPluginContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
// ✅ 使用 try-catch 捕获异常
|
||||
// ✅ 记录详细的日志
|
||||
// ✅ 验证服务是否可用
|
||||
// ✅ 使用 async/await 处理异步操作
|
||||
// ❌ 不要阻塞 UI 线程
|
||||
// ❌ 不要执行超过 5 秒的操作
|
||||
|
||||
_context = context;
|
||||
|
||||
// 快速初始化
|
||||
RegisterComponents(context);
|
||||
RegisterSettings(context);
|
||||
|
||||
// 耗时操作使用后台任务
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await LoadDataAsync();
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Logger.LogError(ex, "Plugin initialization failed");
|
||||
throw; // 让宿主知道初始化失败
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**开发者注意事项**:
|
||||
- ✅ InitializeAsync 应尽快完成(< 5 秒)
|
||||
- ✅ 耗时操作放在后台线程
|
||||
- ✅ 妥善处理异常
|
||||
- ✅ 保存 IPluginContext 引用供后续使用
|
||||
- ❌ 不要在此阶段访问其他插件的服务(可能还未加载)
|
||||
|
||||
### 5. 运行中阶段 (Running)
|
||||
|
||||
**时机**: 初始化完成后
|
||||
|
||||
**职责**: 响应用户交互和系统事件
|
||||
|
||||
**组件更新循环**:
|
||||
|
||||
```csharp
|
||||
// 宿主会定时调用组件的 UpdateAsync()
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
private HttpClient _httpClient;
|
||||
private DateTime _lastUpdate;
|
||||
|
||||
public override async Task UpdateAsync()
|
||||
{
|
||||
// 定时更新数据(默认 1 秒调用一次)
|
||||
if (DateTime.Now - _lastUpdate > TimeSpan.FromMinutes(5))
|
||||
{
|
||||
await FetchDataAsync();
|
||||
_lastUpdate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task FetchDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = await _httpClient.GetStringAsync("https://api.example.com/data");
|
||||
// 更新组件属性
|
||||
Data = ParseData(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Failed to fetch data");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**事件响应**:
|
||||
|
||||
```csharp
|
||||
public class MyPlugin : IPlugin
|
||||
{
|
||||
public async Task InitializeAsync(IPluginContext context)
|
||||
{
|
||||
// 订阅系统事件
|
||||
var eventBus = context.Services.GetService<IEventBus>();
|
||||
|
||||
eventBus?.Subscribe<ThemeChangedEvent>(OnThemeChanged);
|
||||
eventBus?.Subscribe<LanguageChangedEvent>(OnLanguageChanged);
|
||||
eventBus?.Subscribe<SettingChangedEvent>(OnSettingChanged);
|
||||
}
|
||||
|
||||
private void OnThemeChanged(ThemeChangedEvent evt)
|
||||
{
|
||||
_logger.LogInformation($"Theme changed to: {evt.NewTheme}");
|
||||
// 更新组件外观
|
||||
}
|
||||
|
||||
private void OnLanguageChanged(LanguageChangedEvent evt)
|
||||
{
|
||||
_logger.LogInformation($"Language changed to: {evt.NewLanguage}");
|
||||
// 重新加载本地化资源
|
||||
}
|
||||
|
||||
private void OnSettingChanged(SettingChangedEvent evt)
|
||||
{
|
||||
if (evt.Key.StartsWith("MyPlugin."))
|
||||
{
|
||||
// 响应插件设置变更
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**开发者注意事项**:
|
||||
- ✅ 组件更新应快速完成
|
||||
- ✅ 使用缓存避免重复计算
|
||||
- ✅ 异步操作使用 async/await
|
||||
- ✅ 妥善处理网络错误
|
||||
- ❌ 不要在 UpdateAsync 中执行超过 1 秒的操作
|
||||
|
||||
### 6. 关闭阶段 (Shutdown)
|
||||
|
||||
**时机**:
|
||||
- 宿主应用退出
|
||||
- 插件被禁用
|
||||
- 插件热重载
|
||||
|
||||
**职责**: 清理资源和保存状态
|
||||
|
||||
**插件代码示例**:
|
||||
|
||||
```csharp
|
||||
public class MyPlugin : IPlugin
|
||||
{
|
||||
private IDisposable? _eventSubscription;
|
||||
private HttpClient? _httpClient;
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
public async Task ShutdownAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation($"{Name} is shutting down...");
|
||||
|
||||
// 1. 取消正在进行的操作
|
||||
_cts?.Cancel();
|
||||
|
||||
// 2. 取消事件订阅
|
||||
_eventSubscription?.Dispose();
|
||||
|
||||
// 3. 保存状态
|
||||
await SaveStateAsync();
|
||||
|
||||
// 4. 释放资源
|
||||
_httpClient?.Dispose();
|
||||
|
||||
// 5. 停止后台任务
|
||||
await StopBackgroundServicesAsync();
|
||||
|
||||
_logger.LogInformation($"{Name} shutdown completed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error during plugin shutdown");
|
||||
// 不要抛出异常,避免影响其他插件
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveStateAsync()
|
||||
{
|
||||
// 保存插件状态到设置
|
||||
_context?.Settings.SetValue("LastUpdateTime", DateTime.Now);
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task StopBackgroundServicesAsync()
|
||||
{
|
||||
// 停止定时任务等
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关闭最佳实践**:
|
||||
|
||||
```csharp
|
||||
public async Task ShutdownAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// ✅ 尽快完成(< 3 秒)
|
||||
// ✅ 使用 try-catch 避免异常
|
||||
// ✅ 按相反顺序清理资源
|
||||
// ✅ 保存关键状态
|
||||
// ❌ 不要抛出异常
|
||||
// ❌ 不要执行耗时操作
|
||||
|
||||
// 取消异步操作
|
||||
_cancellationTokenSource?.Cancel();
|
||||
|
||||
// 取消订阅(防止内存泄漏)
|
||||
UnsubscribeEvents();
|
||||
|
||||
// 释放托管资源
|
||||
DisposeResources();
|
||||
|
||||
// 保存状态(快速)
|
||||
SaveCriticalState();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 记录但不抛出
|
||||
_logger?.LogError(ex, "Shutdown error");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**开发者注意事项**:
|
||||
- ✅ ShutdownAsync 必须快速完成(< 3 秒)
|
||||
- ✅ 取消所有异步操作
|
||||
- ✅ 取消事件订阅(防止内存泄漏)
|
||||
- ✅ 释放所有 IDisposable 资源
|
||||
- ✅ 保存关键状态
|
||||
- ❌ 不要抛出异常
|
||||
|
||||
## 生命周期事件
|
||||
|
||||
插件可以监听宿主的生命周期事件:
|
||||
|
||||
```csharp
|
||||
public class MyPlugin : IPlugin
|
||||
{
|
||||
public async Task InitializeAsync(IPluginContext context)
|
||||
{
|
||||
var hostLifecycle = context.Services
|
||||
.GetService<IHostLifecycleService>();
|
||||
|
||||
if (hostLifecycle != null)
|
||||
{
|
||||
hostLifecycle.Starting += OnHostStarting;
|
||||
hostLifecycle.Started += OnHostStarted;
|
||||
hostLifecycle.Stopping += OnHostStopping;
|
||||
hostLifecycle.Stopped += OnHostStopped;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHostStarting(object? sender, EventArgs e)
|
||||
{
|
||||
// 宿主正在启动
|
||||
}
|
||||
|
||||
private void OnHostStarted(object? sender, EventArgs e)
|
||||
{
|
||||
// 宿主已启动完成
|
||||
}
|
||||
|
||||
private void OnHostStopping(object? sender, EventArgs e)
|
||||
{
|
||||
// 宿主即将关闭
|
||||
}
|
||||
|
||||
private void OnHostStopped(object? sender, EventArgs e)
|
||||
{
|
||||
// 宿主已关闭
|
||||
}
|
||||
|
||||
public async Task ShutdownAsync()
|
||||
{
|
||||
// 取消订阅
|
||||
var hostLifecycle = _context?.Services
|
||||
.GetService<IHostLifecycleService>();
|
||||
|
||||
if (hostLifecycle != null)
|
||||
{
|
||||
hostLifecycle.Starting -= OnHostStarting;
|
||||
hostLifecycle.Started -= OnHostStarted;
|
||||
hostLifecycle.Stopping -= OnHostStopping;
|
||||
hostLifecycle.Stopped -= OnHostStopped;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 初始化失败
|
||||
|
||||
如果插件初始化失败,宿主会:
|
||||
|
||||
1. 记录错误日志
|
||||
2. 标记插件为"加载失败"
|
||||
3. 继续加载其他插件
|
||||
4. 在 UI 中显示失败状态
|
||||
|
||||
### 运行时异常
|
||||
|
||||
组件代码中的未捕获异常:
|
||||
|
||||
1. 被宿主捕获并记录
|
||||
2. 组件标记为"错误"状态
|
||||
3. 组件停止更新
|
||||
4. 不影响其他组件
|
||||
|
||||
### 关闭超时
|
||||
|
||||
如果 ShutdownAsync 超过 5 秒:
|
||||
|
||||
1. 宿主强制终止
|
||||
2. 记录超时警告
|
||||
3. 继续关闭其他插件
|
||||
|
||||
## 插件热重载
|
||||
|
||||
宿主支持插件热重载(开发中功能):
|
||||
|
||||
```
|
||||
1. 用户触发重载
|
||||
↓
|
||||
2. 调用 ShutdownAsync()
|
||||
↓
|
||||
3. 卸载程序集
|
||||
↓
|
||||
4. 重新加载程序集
|
||||
↓
|
||||
5. 创建新实例
|
||||
↓
|
||||
6. 调用 InitializeAsync()
|
||||
↓
|
||||
7. 恢复组件状态
|
||||
```
|
||||
|
||||
## 小结
|
||||
|
||||
插件生命周期的关键点:
|
||||
|
||||
- ✅ **发现**: 确保 plugin.json 正确
|
||||
- ✅ **加载**: 管理好依赖关系
|
||||
- ✅ **初始化**: 快速注册,耗时操作后台执行
|
||||
- ✅ **运行**: 高效更新,异步处理
|
||||
- ✅ **关闭**: 及时清理,避免异常
|
||||
|
||||
## 下一步
|
||||
|
||||
- [组件系统详解](02-组件系统.md) - 学习组件开发
|
||||
- [设置系统](03-设置系统.md) - 管理插件配置
|
||||
- [插件通信](05-插件通信.md) - 插件间协作
|
||||
- [IPlugin 接口](../03-API参考/01-IPlugin接口.md) - API 详细文档
|
||||
Reference in New Issue
Block a user