2026-08-11 23:03:55 +08:00
|
|
|
|
# IAirApp 接口详解
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
`IAirApp` 是所有轻应用的入口接口,定义了轻应用的基本信息和生命周期方法。
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
## 接口定义
|
|
|
|
|
|
|
|
|
|
|
|
```csharp
|
2026-08-11 23:03:55 +08:00
|
|
|
|
namespace LanMountainDesktop.AirAppSdk;
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-08-11 23:03:55 +08:00
|
|
|
|
/// 轻应用接口
|
2026-06-08 12:18:58 +08:00
|
|
|
|
/// </summary>
|
2026-08-11 23:03:55 +08:00
|
|
|
|
public interface IAirApp
|
2026-06-08 12:18:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
2026-08-11 23:03:55 +08:00
|
|
|
|
/// 轻应用唯一标识符
|
2026-06-08 12:18:58 +08:00
|
|
|
|
/// 建议使用反向域名格式,如:com.example.myplugin
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
string Id { get; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-08-11 23:03:55 +08:00
|
|
|
|
/// 轻应用显示名称
|
2026-06-08 12:18:58 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
string Name { get; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-08-11 23:03:55 +08:00
|
|
|
|
/// 轻应用版本号
|
2026-06-08 12:18:58 +08:00
|
|
|
|
/// 应遵循语义化版本规范(如:1.2.3)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
string Version { get; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-08-11 23:03:55 +08:00
|
|
|
|
/// 轻应用初始化
|
|
|
|
|
|
/// 在轻应用加载后调用,用于注册组件、服务和事件
|
2026-06-08 12:18:58 +08:00
|
|
|
|
/// </summary>
|
2026-08-11 23:03:55 +08:00
|
|
|
|
/// <param name="context">轻应用上下文</param>
|
2026-06-08 12:18:58 +08:00
|
|
|
|
/// <returns>异步任务</returns>
|
2026-08-11 23:03:55 +08:00
|
|
|
|
Task InitializeAsync(IAirAppRuntimeContext context);
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-08-11 23:03:55 +08:00
|
|
|
|
/// 轻应用关闭
|
|
|
|
|
|
/// 在轻应用卸载前调用,用于清理资源和保存状态
|
2026-06-08 12:18:58 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns>异步任务</returns>
|
|
|
|
|
|
Task ShutdownAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 属性详解
|
|
|
|
|
|
|
|
|
|
|
|
### Id
|
|
|
|
|
|
|
|
|
|
|
|
**类型**: `string`
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
**说明**: 轻应用的全局唯一标识符,必须在所有轻应用中唯一。
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
**命名规范**:
|
|
|
|
|
|
- 使用反向域名格式:`com.company.pluginname`
|
|
|
|
|
|
- 只包含小写字母、数字、点号和连字符
|
|
|
|
|
|
- 不能以数字或连字符开头
|
|
|
|
|
|
|
|
|
|
|
|
**示例**:
|
|
|
|
|
|
```csharp
|
|
|
|
|
|
public string Id => "com.example.weatherplugin";
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**最佳实践**:
|
|
|
|
|
|
```csharp
|
|
|
|
|
|
// ✅ 好的示例
|
|
|
|
|
|
"com.example.weatherplugin"
|
|
|
|
|
|
"io.github.username.todoplugin"
|
|
|
|
|
|
"org.myorganization.monitorplugin"
|
|
|
|
|
|
|
|
|
|
|
|
// ❌ 不好的示例
|
2026-08-11 23:03:55 +08:00
|
|
|
|
"WeatherAirApp" // 不是反向域名格式
|
|
|
|
|
|
"com.example.Weather AirApp" // 包含空格
|
2026-06-08 12:18:58 +08:00
|
|
|
|
"123.example.plugin" // 以数字开头
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Name
|
|
|
|
|
|
|
|
|
|
|
|
**类型**: `string`
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
**说明**: 轻应用的显示名称,会在 UI 中展示给用户。
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
**要求**:
|
|
|
|
|
|
- 简洁明了,不超过 20 个字符
|
|
|
|
|
|
- 可以包含中文、英文、空格
|
|
|
|
|
|
- 不要包含版本号
|
|
|
|
|
|
|
|
|
|
|
|
**示例**:
|
|
|
|
|
|
```csharp
|
2026-08-11 23:03:55 +08:00
|
|
|
|
public string Name => "天气轻应用";
|
2026-06-08 12:18:58 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**最佳实践**:
|
|
|
|
|
|
```csharp
|
|
|
|
|
|
// ✅ 好的示例
|
2026-08-11 23:03:55 +08:00
|
|
|
|
"天气轻应用"
|
2026-06-08 12:18:58 +08:00
|
|
|
|
"待办事项"
|
|
|
|
|
|
"系统监控"
|
|
|
|
|
|
|
|
|
|
|
|
// ❌ 不好的示例
|
2026-08-11 23:03:55 +08:00
|
|
|
|
"天气轻应用 v1.0" // 包含版本号
|
|
|
|
|
|
"The Best Weather AirApp" // 过长且夸张
|
2026-06-08 12:18:58 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Version
|
|
|
|
|
|
|
|
|
|
|
|
**类型**: `string`
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
**说明**: 轻应用的版本号,应遵循[语义化版本](https://semver.org/lang/zh-CN/)规范。
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
**格式**: `主版本号.次版本号.修订号`
|
|
|
|
|
|
|
|
|
|
|
|
**规则**:
|
|
|
|
|
|
- **主版本号**: 不兼容的 API 修改
|
|
|
|
|
|
- **次版本号**: 向下兼容的功能性新增
|
|
|
|
|
|
- **修订号**: 向下兼容的问题修正
|
|
|
|
|
|
|
|
|
|
|
|
**示例**:
|
|
|
|
|
|
```csharp
|
|
|
|
|
|
public string Version => "1.2.3";
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**版本示例**:
|
|
|
|
|
|
```csharp
|
|
|
|
|
|
"1.0.0" // 首个稳定版本
|
|
|
|
|
|
"1.1.0" // 添加新功能,兼容 1.0.0
|
|
|
|
|
|
"1.1.1" // 修复 Bug,兼容 1.1.0
|
|
|
|
|
|
"2.0.0" // 不兼容的 API 变更
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 方法详解
|
|
|
|
|
|
|
|
|
|
|
|
### InitializeAsync
|
|
|
|
|
|
|
|
|
|
|
|
**签名**:
|
|
|
|
|
|
```csharp
|
2026-08-11 23:03:55 +08:00
|
|
|
|
Task InitializeAsync(IAirAppRuntimeContext context);
|
2026-06-08 12:18:58 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
**说明**: 轻应用加载后立即调用,用于初始化轻应用、注册组件和服务。
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
**参数**:
|
2026-08-11 23:03:55 +08:00
|
|
|
|
- `context`: 轻应用上下文,提供对宿主服务的访问
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
**返回值**: 异步任务
|
|
|
|
|
|
|
|
|
|
|
|
**调用时机**:
|
2026-08-11 23:03:55 +08:00
|
|
|
|
- 宿主启动时,所有轻应用发现后
|
|
|
|
|
|
- 轻应用热重载时
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
**执行要求**:
|
|
|
|
|
|
- ✅ 应该快速完成(< 5 秒)
|
|
|
|
|
|
- ✅ 耗时操作应放在后台线程
|
|
|
|
|
|
- ✅ 应该处理所有可能的异常
|
|
|
|
|
|
- ❌ 不要阻塞 UI 线程
|
|
|
|
|
|
|
|
|
|
|
|
**典型实现**:
|
|
|
|
|
|
|
|
|
|
|
|
```csharp
|
2026-08-11 23:03:55 +08:00
|
|
|
|
public async Task InitializeAsync(IAirAppRuntimeContext context)
|
2026-06-08 12:18:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. 保存上下文引用
|
|
|
|
|
|
_context = context;
|
|
|
|
|
|
_logger = context.Logger;
|
|
|
|
|
|
_settings = context.Settings;
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 记录日志
|
|
|
|
|
|
_logger.LogInformation($"{Name} v{Version} is initializing...");
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 注册组件
|
|
|
|
|
|
RegisterComponents(context);
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 注册设置页
|
|
|
|
|
|
RegisterSettingsPage(context);
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 注册服务
|
|
|
|
|
|
RegisterServices(context);
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 订阅事件
|
|
|
|
|
|
SubscribeEvents(context);
|
|
|
|
|
|
|
|
|
|
|
|
// 7. 耗时初始化(后台执行)
|
|
|
|
|
|
_ = Task.Run(async () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
await InitializeDataAsync();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation($"{Name} initialized successfully");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
context.Logger.LogError(ex, $"Failed to initialize {Name}");
|
|
|
|
|
|
throw; // 让宿主知道初始化失败
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
private void RegisterComponents(IAirAppRuntimeContext context)
|
2026-06-08 12:18:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
var registry = context.Services.GetService<IComponentRegistry>();
|
|
|
|
|
|
if (registry != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
registry.RegisterComponent<WeatherComponent>();
|
|
|
|
|
|
registry.RegisterComponent<ClockComponent>();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
private void RegisterSettingsPage(IAirAppRuntimeContext context)
|
2026-06-08 12:18:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
var settingsRegistry = context.Services
|
|
|
|
|
|
.GetService<ISettingsPageRegistry>();
|
|
|
|
|
|
|
|
|
|
|
|
if (settingsRegistry != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
settingsRegistry.RegisterPage(
|
2026-08-11 23:03:55 +08:00
|
|
|
|
title: "天气轻应用",
|
|
|
|
|
|
category: "轻应用",
|
2026-06-08 12:18:58 +08:00
|
|
|
|
pageFactory: () => new WeatherSettingsPage()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
private void RegisterServices(IAirAppRuntimeContext context)
|
2026-06-08 12:18:58 +08:00
|
|
|
|
{
|
2026-08-11 23:03:55 +08:00
|
|
|
|
// 注册轻应用内部服务
|
2026-06-08 12:18:58 +08:00
|
|
|
|
_weatherService = new WeatherService(_settings, _logger);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
private void SubscribeEvents(IAirAppRuntimeContext context)
|
2026-06-08 12:18:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
var eventBus = context.Services.GetService<IEventBus>();
|
|
|
|
|
|
if (eventBus != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
eventBus.Subscribe<ThemeChangedEvent>(OnThemeChanged);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task InitializeDataAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 加载缓存数据
|
|
|
|
|
|
await LoadCachedDataAsync();
|
|
|
|
|
|
|
|
|
|
|
|
// 预加载资源
|
|
|
|
|
|
await PreloadResourcesAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**错误处理**:
|
|
|
|
|
|
|
|
|
|
|
|
```csharp
|
2026-08-11 23:03:55 +08:00
|
|
|
|
public async Task InitializeAsync(IAirAppRuntimeContext context)
|
2026-06-08 12:18:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 初始化代码
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (FileNotFoundException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
context.Logger.LogError(ex, "Required file not found");
|
2026-08-11 23:03:55 +08:00
|
|
|
|
throw new AirAppInitializationException(
|
|
|
|
|
|
"轻应用初始化失败:缺少必需文件",
|
2026-06-08 12:18:58 +08:00
|
|
|
|
ex
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (UnauthorizedAccessException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
context.Logger.LogError(ex, "Permission denied");
|
2026-08-11 23:03:55 +08:00
|
|
|
|
throw new AirAppInitializationException(
|
|
|
|
|
|
"轻应用初始化失败:权限不足",
|
2026-06-08 12:18:58 +08:00
|
|
|
|
ex
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
context.Logger.LogError(ex, "Unexpected error during initialization");
|
|
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### ShutdownAsync
|
|
|
|
|
|
|
|
|
|
|
|
**签名**:
|
|
|
|
|
|
```csharp
|
|
|
|
|
|
Task ShutdownAsync();
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
**说明**: 轻应用卸载前调用,用于清理资源、保存状态和取消订阅。
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
**返回值**: 异步任务
|
|
|
|
|
|
|
|
|
|
|
|
**调用时机**:
|
|
|
|
|
|
- 宿主应用关闭时
|
2026-08-11 23:03:55 +08:00
|
|
|
|
- 轻应用被禁用时
|
|
|
|
|
|
- 轻应用热重载前
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
**执行要求**:
|
|
|
|
|
|
- ✅ 必须快速完成(< 3 秒)
|
|
|
|
|
|
- ✅ 必须捕获所有异常,不能抛出
|
|
|
|
|
|
- ✅ 应该取消所有异步操作
|
|
|
|
|
|
- ✅ 应该释放所有资源
|
|
|
|
|
|
- ❌ 不要执行耗时操作
|
|
|
|
|
|
|
|
|
|
|
|
**典型实现**:
|
|
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
|
public async Task ShutdownAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger?.LogInformation($"{Name} is shutting down...");
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 取消正在进行的操作
|
|
|
|
|
|
_cancellationTokenSource?.Cancel();
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 取消事件订阅
|
|
|
|
|
|
UnsubscribeEvents();
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 保存关键状态
|
|
|
|
|
|
SaveState();
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 停止后台服务
|
|
|
|
|
|
await StopBackgroundServicesAsync();
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 释放资源
|
|
|
|
|
|
DisposeResources();
|
|
|
|
|
|
|
|
|
|
|
|
_logger?.LogInformation($"{Name} shutdown completed");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 记录但不抛出异常
|
|
|
|
|
|
_logger?.LogError(ex, $"Error during {Name} shutdown");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void UnsubscribeEvents()
|
|
|
|
|
|
{
|
|
|
|
|
|
var eventBus = _context?.Services.GetService<IEventBus>();
|
|
|
|
|
|
if (eventBus != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
eventBus.Unsubscribe<ThemeChangedEvent>(OnThemeChanged);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void SaveState()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 保存关键状态到设置
|
|
|
|
|
|
_settings?.SetValue("LastShutdownTime", DateTime.Now);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger?.LogWarning(ex, "Failed to save state");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task StopBackgroundServicesAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_weatherService != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _weatherService.StopAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger?.LogWarning(ex, "Failed to stop services");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void DisposeResources()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
_cancellationTokenSource?.Dispose();
|
|
|
|
|
|
_weatherService?.Dispose();
|
|
|
|
|
|
_httpClient?.Dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger?.LogWarning(ex, "Failed to dispose resources");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**超时处理**:
|
|
|
|
|
|
|
|
|
|
|
|
宿主会监控 `ShutdownAsync` 的执行时间:
|
|
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
|
// 宿主代码(伪代码)
|
|
|
|
|
|
var shutdownTask = plugin.ShutdownAsync();
|
|
|
|
|
|
var completedTask = await Task.WhenAny(
|
|
|
|
|
|
shutdownTask,
|
|
|
|
|
|
Task.Delay(TimeSpan.FromSeconds(5))
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (completedTask != shutdownTask)
|
|
|
|
|
|
{
|
2026-08-11 23:03:55 +08:00
|
|
|
|
_logger.LogWarning($"AirApp {plugin.Name} shutdown timeout");
|
2026-06-08 12:18:58 +08:00
|
|
|
|
// 强制终止
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
所以轻应用应该确保快速完成:
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
|
public async Task ShutdownAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await ShutdownInternalAsync(cts.Token);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger?.LogWarning("Shutdown cancelled due to timeout");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 完整示例
|
|
|
|
|
|
|
|
|
|
|
|
### 最小实现
|
|
|
|
|
|
|
|
|
|
|
|
```csharp
|
2026-08-11 23:03:55 +08:00
|
|
|
|
using LanMountainDesktop.AirAppSdk;
|
2026-06-08 12:18:58 +08:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
namespace MyAirApp;
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
public class AirApp : IAirApp
|
2026-06-08 12:18:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
public string Id => "com.example.minimalplugin";
|
2026-08-11 23:03:55 +08:00
|
|
|
|
public string Name => "Minimal AirApp";
|
2026-06-08 12:18:58 +08:00
|
|
|
|
public string Version => "1.0.0";
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
public Task InitializeAsync(IAirAppRuntimeContext context)
|
2026-06-08 12:18:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
context.Logger.LogInformation($"{Name} initialized");
|
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Task ShutdownAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 完整实现
|
|
|
|
|
|
|
|
|
|
|
|
```csharp
|
2026-08-11 23:03:55 +08:00
|
|
|
|
using LanMountainDesktop.AirAppSdk;
|
2026-06-08 12:18:58 +08:00
|
|
|
|
using LanMountainDesktop.Shared.Contracts;
|
|
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
namespace MyAirApp;
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-08-11 23:03:55 +08:00
|
|
|
|
/// 天气轻应用
|
2026-06-08 12:18:58 +08:00
|
|
|
|
/// </summary>
|
2026-08-11 23:03:55 +08:00
|
|
|
|
public class WeatherAirApp : IAirApp
|
2026-06-08 12:18:58 +08:00
|
|
|
|
{
|
2026-08-11 23:03:55 +08:00
|
|
|
|
// === 轻应用信息 ===
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
public string Id => "com.example.weatherplugin";
|
2026-08-11 23:03:55 +08:00
|
|
|
|
public string Name => "天气轻应用";
|
2026-06-08 12:18:58 +08:00
|
|
|
|
public string Version => "1.2.3";
|
|
|
|
|
|
|
|
|
|
|
|
// === 私有字段 ===
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
private IAirAppRuntimeContext? _context;
|
2026-06-08 12:18:58 +08:00
|
|
|
|
private ILogger? _logger;
|
|
|
|
|
|
private ISettingsService? _settings;
|
|
|
|
|
|
private CancellationTokenSource? _cancellationTokenSource;
|
|
|
|
|
|
private WeatherService? _weatherService;
|
|
|
|
|
|
|
|
|
|
|
|
// === 生命周期方法 ===
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-08-11 23:03:55 +08:00
|
|
|
|
/// 轻应用初始化
|
2026-06-08 12:18:58 +08:00
|
|
|
|
/// </summary>
|
2026-08-11 23:03:55 +08:00
|
|
|
|
public async Task InitializeAsync(IAirAppRuntimeContext context)
|
2026-06-08 12:18:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 保存引用
|
|
|
|
|
|
_context = context;
|
|
|
|
|
|
_logger = context.Logger;
|
|
|
|
|
|
_settings = context.Settings;
|
|
|
|
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation(
|
2026-08-11 23:03:55 +08:00
|
|
|
|
"{AirAppName} v{Version} is initializing...",
|
2026-06-08 12:18:58 +08:00
|
|
|
|
Name,
|
|
|
|
|
|
Version
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 注册组件
|
|
|
|
|
|
RegisterComponents(context);
|
|
|
|
|
|
|
|
|
|
|
|
// 注册设置页
|
|
|
|
|
|
RegisterSettingsPage(context);
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化服务
|
|
|
|
|
|
_weatherService = new WeatherService(
|
|
|
|
|
|
_settings,
|
|
|
|
|
|
_logger,
|
|
|
|
|
|
_cancellationTokenSource.Token
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 订阅事件
|
|
|
|
|
|
SubscribeToHostEvents(context);
|
|
|
|
|
|
|
|
|
|
|
|
// 后台初始化
|
|
|
|
|
|
_ = Task.Run(async () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
await InitializeBackgroundAsync();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation(
|
2026-08-11 23:03:55 +08:00
|
|
|
|
"{AirAppName} initialized successfully",
|
2026-06-08 12:18:58 +08:00
|
|
|
|
Name
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
await Task.CompletedTask;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
context.Logger.LogError(
|
|
|
|
|
|
ex,
|
2026-08-11 23:03:55 +08:00
|
|
|
|
"Failed to initialize {AirAppName}",
|
2026-06-08 12:18:58 +08:00
|
|
|
|
Name
|
|
|
|
|
|
);
|
|
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-08-11 23:03:55 +08:00
|
|
|
|
/// 轻应用关闭
|
2026-06-08 12:18:58 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task ShutdownAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger?.LogInformation(
|
2026-08-11 23:03:55 +08:00
|
|
|
|
"{AirAppName} is shutting down...",
|
2026-06-08 12:18:58 +08:00
|
|
|
|
Name
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 取消异步操作
|
|
|
|
|
|
_cancellationTokenSource?.Cancel();
|
|
|
|
|
|
|
|
|
|
|
|
// 取消订阅
|
|
|
|
|
|
UnsubscribeFromHostEvents();
|
|
|
|
|
|
|
|
|
|
|
|
// 保存状态
|
|
|
|
|
|
SaveState();
|
|
|
|
|
|
|
|
|
|
|
|
// 停止服务
|
|
|
|
|
|
if (_weatherService != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _weatherService.StopAsync();
|
|
|
|
|
|
_weatherService.Dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 释放资源
|
|
|
|
|
|
_cancellationTokenSource?.Dispose();
|
|
|
|
|
|
|
|
|
|
|
|
_logger?.LogInformation(
|
2026-08-11 23:03:55 +08:00
|
|
|
|
"{AirAppName} shutdown completed",
|
2026-06-08 12:18:58 +08:00
|
|
|
|
Name
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger?.LogError(
|
|
|
|
|
|
ex,
|
2026-08-11 23:03:55 +08:00
|
|
|
|
"Error during {AirAppName} shutdown",
|
2026-06-08 12:18:58 +08:00
|
|
|
|
Name
|
|
|
|
|
|
);
|
|
|
|
|
|
// 不抛出异常
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// === 私有方法 ===
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
private void RegisterComponents(IAirAppRuntimeContext context)
|
2026-06-08 12:18:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
var registry = context.Services
|
|
|
|
|
|
.GetService<IComponentRegistry>();
|
|
|
|
|
|
|
|
|
|
|
|
if (registry != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
registry.RegisterComponent<WeatherComponent>();
|
|
|
|
|
|
_logger?.LogDebug("WeatherComponent registered");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
private void RegisterSettingsPage(IAirAppRuntimeContext context)
|
2026-06-08 12:18:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
var settingsRegistry = context.Services
|
|
|
|
|
|
.GetService<ISettingsPageRegistry>();
|
|
|
|
|
|
|
|
|
|
|
|
if (settingsRegistry != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
settingsRegistry.RegisterPage(
|
|
|
|
|
|
title: Name,
|
2026-08-11 23:03:55 +08:00
|
|
|
|
category: "轻应用",
|
2026-06-08 12:18:58 +08:00
|
|
|
|
pageFactory: () => new WeatherSettingsPage(
|
|
|
|
|
|
_settings!,
|
|
|
|
|
|
_logger!
|
|
|
|
|
|
)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
_logger?.LogDebug("Settings page registered");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
private void SubscribeToHostEvents(IAirAppRuntimeContext context)
|
2026-06-08 12:18:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
var eventBus = context.Services.GetService<IEventBus>();
|
|
|
|
|
|
if (eventBus != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
eventBus.Subscribe<ThemeChangedEvent>(OnThemeChanged);
|
|
|
|
|
|
_logger?.LogDebug("Subscribed to host events");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void UnsubscribeFromHostEvents()
|
|
|
|
|
|
{
|
|
|
|
|
|
var eventBus = _context?.Services.GetService<IEventBus>();
|
|
|
|
|
|
if (eventBus != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
eventBus.Unsubscribe<ThemeChangedEvent>(OnThemeChanged);
|
|
|
|
|
|
_logger?.LogDebug("Unsubscribed from host events");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task InitializeBackgroundAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 加载缓存数据
|
|
|
|
|
|
await _weatherService!.LoadCacheAsync();
|
|
|
|
|
|
|
|
|
|
|
|
// 预加载天气数据
|
|
|
|
|
|
var defaultCity = _settings!.GetValue("DefaultCity", "北京");
|
|
|
|
|
|
await _weatherService.FetchWeatherAsync(defaultCity);
|
|
|
|
|
|
|
|
|
|
|
|
_logger?.LogInformation("Background initialization completed");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger?.LogError(ex, "Background initialization failed");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnThemeChanged(ThemeChangedEvent evt)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger?.LogInformation(
|
|
|
|
|
|
"Theme changed to: {Theme}",
|
|
|
|
|
|
evt.NewTheme
|
|
|
|
|
|
);
|
|
|
|
|
|
// 响应主题变更
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void SaveState()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
_settings?.SetValue("LastShutdownTime", DateTime.Now);
|
|
|
|
|
|
_logger?.LogDebug("State saved");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger?.LogWarning(ex, "Failed to save state");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 常见问题
|
|
|
|
|
|
|
|
|
|
|
|
### Q: InitializeAsync 可以执行多久?
|
|
|
|
|
|
|
|
|
|
|
|
**A**: 建议在 5 秒内完成。超时可能导致宿主启动缓慢。耗时操作应放在后台线程。
|
|
|
|
|
|
|
|
|
|
|
|
### Q: 可以在构造函数中初始化吗?
|
|
|
|
|
|
|
|
|
|
|
|
**A**: 不建议。构造函数应该非常轻量,只初始化字段。所有初始化逻辑应在 `InitializeAsync` 中。
|
|
|
|
|
|
|
|
|
|
|
|
### Q: ShutdownAsync 可以不实现吗?
|
|
|
|
|
|
|
|
|
|
|
|
**A**: 必须实现,但可以是空实现。如果有资源需要清理,必须在此方法中处理。
|
|
|
|
|
|
|
|
|
|
|
|
### Q: 如果 InitializeAsync 失败会怎样?
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
**A**: 轻应用会被标记为"加载失败",不会被激活,但不影响其他轻应用。
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
### Q: 可以访问其他轻应用的服务吗?
|
2026-06-08 12:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
**A**: 不建议在 `InitializeAsync` 中访问,因为加载顺序不确定。应该在运行时通过服务定位器获取。
|
|
|
|
|
|
|
|
|
|
|
|
## 相关文档
|
|
|
|
|
|
|
2026-08-11 23:03:55 +08:00
|
|
|
|
- [IAirAppRuntimeContext 详解](02-IAirAppRuntimeContext.md) - 轻应用上下文
|
|
|
|
|
|
- [轻应用生命周期](../02-核心概念/01-轻应用生命周期.md) - 生命周期详解
|
|
|
|
|
|
- [创建第一个轻应用](../01-快速开始/02-创建第一个轻应用.md) - 实战教程
|