mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
17 KiB
17 KiB
插件生命周期
本文档详细介绍阑山桌面插件的生命周期、加载流程和各个阶段的职责。
生命周期概览
插件从加载到卸载经历以下阶段:
┌─────────────────────────────────────────────────────────┐
│ 插件生命周期 │
└─────────────────────────────────────────────────────────┘
1. 发现 (Discovery)
├─ 扫描插件目录
├─ 读取 plugin.json
└─ 验证基本信息
↓
2. 加载 (Load)
├─ 创建 PluginLoadContext
├─ 加载程序集
├─ 解析依赖关系
└─ 验证兼容性
↓
3. 实例化 (Instantiate)
├─ 反射查找 IPlugin 实现
├─ 创建插件实例
└─ 注入依赖
↓
4. 初始化 (Initialize)
├─ 调用 InitializeAsync()
├─ 注册组件
├─ 注册设置页
├─ 注册服务
└─ 订阅事件
↓
5. 运行中 (Running)
├─ 组件渲染和更新
├─ 处理用户交互
├─ 响应事件
└─ 执行后台任务
↓
6. 关闭 (Shutdown)
├─ 调用 ShutdownAsync()
├─ 保存状态
├─ 取消订阅
├─ 清理资源
└─ 卸载程序集
详细阶段说明
1. 发现阶段 (Discovery)
时机: 宿主启动时
职责: 扫描和识别插件
流程:
// 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)
时机: 发现插件后
职责: 加载插件程序集
流程:
// 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);
}
}
依赖解析:
// plugin.json
{
"Dependencies": [
{
"PluginId": "com.example.anotherplugin",
"MinVersion": "1.0.0"
}
]
}
开发者注意事项:
- ✅ 主程序集名称应与插件 ID 匹配(或在清单中指定)
- ✅ 所有依赖的 DLL 应在插件目录中
- ✅ 声明对其他插件的依赖关系
3. 实例化阶段 (Instantiate)
时机: 程序集加载后
职责: 创建插件实例
流程:
// 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)
时机: 插件实例创建后
职责: 注册组件、服务和事件
插件代码示例:
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;
}
}
初始化最佳实践:
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)
时机: 初始化完成后
职责: 响应用户交互和系统事件
组件更新循环:
// 宿主会定时调用组件的 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");
}
}
}
事件响应:
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)
时机:
- 宿主应用退出
- 插件被禁用
- 插件热重载
职责: 清理资源和保存状态
插件代码示例:
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;
}
}
关闭最佳实践:
public async Task ShutdownAsync()
{
try
{
// ✅ 尽快完成(< 3 秒)
// ✅ 使用 try-catch 避免异常
// ✅ 按相反顺序清理资源
// ✅ 保存关键状态
// ❌ 不要抛出异常
// ❌ 不要执行耗时操作
// 取消异步操作
_cancellationTokenSource?.Cancel();
// 取消订阅(防止内存泄漏)
UnsubscribeEvents();
// 释放托管资源
DisposeResources();
// 保存状态(快速)
SaveCriticalState();
}
catch (Exception ex)
{
// 记录但不抛出
_logger?.LogError(ex, "Shutdown error");
}
}
开发者注意事项:
- ✅ ShutdownAsync 必须快速完成(< 3 秒)
- ✅ 取消所有异步操作
- ✅ 取消事件订阅(防止内存泄漏)
- ✅ 释放所有 IDisposable 资源
- ✅ 保存关键状态
- ❌ 不要抛出异常
生命周期事件
插件可以监听宿主的生命周期事件:
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;
}
}
}
错误处理
初始化失败
如果插件初始化失败,宿主会:
- 记录错误日志
- 标记插件为"加载失败"
- 继续加载其他插件
- 在 UI 中显示失败状态
运行时异常
组件代码中的未捕获异常:
- 被宿主捕获并记录
- 组件标记为"错误"状态
- 组件停止更新
- 不影响其他组件
关闭超时
如果 ShutdownAsync 超过 5 秒:
- 宿主强制终止
- 记录超时警告
- 继续关闭其他插件
插件热重载
宿主支持插件热重载(开发中功能):
1. 用户触发重载
↓
2. 调用 ShutdownAsync()
↓
3. 卸载程序集
↓
4. 重新加载程序集
↓
5. 创建新实例
↓
6. 调用 InitializeAsync()
↓
7. 恢复组件状态
小结
插件生命周期的关键点:
- ✅ 发现: 确保 plugin.json 正确
- ✅ 加载: 管理好依赖关系
- ✅ 初始化: 快速注册,耗时操作后台执行
- ✅ 运行: 高效更新,异步处理
- ✅ 关闭: 及时清理,避免异常
下一步
- 组件系统详解 - 学习组件开发
- 设置系统 - 管理插件配置
- 插件通信 - 插件间协作
- IPlugin 接口 - API 详细文档