战匈琼 发表于 2025-6-3 13:37:00

AOT编译Avalonia应用:StarBlog Publisher项目实践与挑战

前言

最近我使用 Avalonia 开发了一个文章发布工具,StarBlog Publisher。
Avalonia 是一个跨平台的 UI 框架,它可以在 Windows、Linux 和 macOS 上运行。它的特点是高性能、跨平台、易于使用。
Avalonia 有很多优点,比如高性能、跨平台、易于使用。但是,它也有一些缺点,比如学习曲线较陡峭、文档较难找到。
但 Avalonia 是基于 .NetCore 框架开发的,最终打包出来的可执行文件,如果选择 framework-dependant 发布,那么需要在客户端上安装 .NetCore 运行时环境,这对用户来说是一个很大的负担。如果使用 self-contained 发布,体积又比较大。
并且还容易被反编译,这在一些商业软件中是不允许的。(不过我这个项目是开源的,所以没有这个问题)
本文以 StarBlog Publisher 项目为例,记录一下使用 AOT 发布 Avalonia 应用的踩坑过程。
新的1.1版本已经发布,欢迎下载尝试: https://github.com/star-blog/starblog-publisher/releases
关于 AOT

从 .Net7 开始,逐步开始支持 AOT 发布,这是一个非常重要的特性。AOT 发布可以将 .Net 应用程序编译成不依赖运行库的机器码,体积较小,而且不容易被反编译。
Native AOT = 编译时把 .NET 程序的 IL(中间语言)直接提前编译成机器码,生成真正的、单独的、没有依赖 CLR 的可执行文件。
相比传统 .NET 应用在运行时用 JIT(即时编译器)动态编译,Native AOT是 提前(Ahead of Time) 直接搞定所有编译工作。
以下是一个简单的 Native AOT 流程图
C# 代码
   ↓
Roslyn 编译(生成 IL)
   ↓
Native AOT 编译器 (ilc)
   ↓
- 静态分析
- Tree Shaking
- IL到机器码转换
- 链接器合成可执行文件
   ↓
最终单个 .exe (无JIT、无大型CLR)目前的 LTS 版本是 .Net8,对 AOT 的支持已经比较完善了,这次我来尝试使用 AOT 方式发布 Avalonia 应用。
PS:据说 .Net9 对 AOT 方式提供了很多优化和改进,接下来我会尝试一下。
使用 AOT 可能会遇到的问题


[*]兼容性问题 :AOT编译可能与某些依赖库不兼容,特别是那些依赖反射、动态代码生成或JIT编译的库。如果遇到问题,可能需要在rd.xml中添加更多配置。
[*]包大小 :AOT编译会生成更大的可执行文件(相比起 framework-dependant 模式而言),但启动速度更快。
[*]调试困难 :AOT编译的应用程序调试可能更加困难。
[*]第三方库 :检查项目中使用的第三方库是否支持AOT编译。例如, Microsoft.Extensions.AI 和 Microsoft.Extensions.AI.OpenAI 是预览版,可能需要特别注意其AOT兼容性。
[*]Avalonia特定配置 :对于Avalonia应用,可能需要确保XAML相关的类型信息被正确保留。
修改项目文件

首先需要在项目文件中添加AOT相关的配置:
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><PropertyGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><OutputType>WinExe</OutputType>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><TargetFramework>net8.0</TargetFramework>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><Nullable>enable</Nullable>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>app.manifest</ApplicationManifest>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>true</AvaloniaUseCompiledBindingsByDefault>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><PublishAot>true</PublishAot>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><TrimMode>full</TrimMode>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><InvariantGlobalization>true</InvariantGlobalization>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><IlcOptimizationPreference>Size</IlcOptimizationPreference>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup></PropertyGroup>

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
</Project>JSON序列化问题

在AOT编译环境中,JSON序列化是一个常见的问题点,因为它通常依赖于运行时反射。
这个项目有几个地方用到了 JSON
一个是应用设置,另一个是网络请求
先说结论:Newtonsoft.Json 相比 System.Text.Json 对 AOT 的支持更好,如果要使用 AOT,优先使用 Newtonsoft.Json 库。
修改应用设置 AppSettings.cs 支持AOT

如果非要使用System.Text.Json ,那么需要修改一下。用 Newtonsoft.Json 的话直接跳过。
using System;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using StarBlogPublisher.Services.Security;
using System.Text.Json.Serialization.Metadata; // 添加此命名空间

namespace StarBlogPublisher.Services;

// 添加JsonSerializable特性,为AOT生成序列化代码

internal partial class AppSettingsContext : JsonSerializerContext
{
}

public class AppSettings {
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>private static readonly string ConfigPath = Path.Combine(
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>"StarBlogPublisher",
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>"settings.json"
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>);

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// ... 现有代码 ...

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>private static AppSettings Load() {
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>try {
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>if (File.Exists(ConfigPath)) {
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>var json = File.ReadAllText(ConfigPath);
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 使用AOT友好的序列化方式
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>var settings = JsonSerializer.Deserialize(json, AppSettingsContext.Default.AppSettings);
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>return settings ?? new AppSettings();
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>}
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>}
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>catch (Exception ex) {
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 如果加载失败,返回默认设置
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>Console.WriteLine($"Failed to load app settings. {ex}");
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>}

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>return new AppSettings();
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>}

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>public void Save() {
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>try {
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>var directory = Path.GetDirectoryName(ConfigPath);
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>if (!string.IsNullOrEmpty(directory)) {
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>Directory.CreateDirectory(directory);
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>}

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 使用AOT友好的序列化方式
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>var json = JsonSerializer.Serialize(this, AppSettingsContext.Default.AppSettings, new JsonSerializerOptions {
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>WriteIndented = true
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>});
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>File.WriteAllText(ConfigPath, json);

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 触发配置变更事件
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>SettingsChanged?.Invoke(this, EventArgs.Empty);
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>}
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>catch (Exception) {
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// todo 处理保存失败的情况
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>}
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>}
}解释


[*]添加了 特性和 JsonSerializerContext 派生类,这是.NET中支持AOT的JSON序列化的关键。这会在编译时生成序列化代码,而不是依赖运行时反射。
[*]修改了 Load() 和 Save() 方法,使用 AppSettingsContext.Default.AppSettings 作为类型信息,而不是依赖运行时类型推断。
[*]这种方法确保了在AOT环境中,所有需要的序列化代码都会在编译时生成,而不需要运行时反射。
此外,还需要在项目文件中确保已启用AOT编译的JSON源生成器:
<PropertyGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>这些修改将确保AppSettings类在AOT编译环境中能够正确地进行JSON序列化和反序列化。
Refit在AOT模式下的JSON序列化问题

在AOT模式下,Refit库的JSON处理也可以使用 Newtonsoft.Json
先安装 Refit.Newtonsoft.Json 库,并且需要额外配置来处理类型信息。
添加类型预注册

需要创建一个新的类来预注册所有API接口中使用的类型:
using Newtonsoft.Json;
using StarBlogPublisher.Models;
using System.Collections.Generic;
using CodeLab.Share.ViewModels.Response;

namespace StarBlogPublisher.Services;

/// <summary>
/// 为AOT编译预注册Refit使用的类型
/// </summary>
public static class RefitTypeRegistration
{
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>/// <summary>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>/// 在应用启动时调用此方法,确保所有类型都被预注册
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>/// </summary>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>public static void RegisterTypes()
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>{
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 注册常用的响应类型
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>JsonConvert.DefaultSettings = () => new JsonSerializerSettings
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>{
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>TypeNameHandling = TypeNameHandling.Auto,
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 添加自定义转换器如果需要
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>Converters = new List<JsonConverter>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>{
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 可以添加自定义转换器
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>}
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>};

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 预热类型 - 确保这些类型在AOT编译时被包含
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>var types = new[]
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>{
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>typeof(ApiResponse<>),
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>typeof(ApiResponse<List<Category>>),
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>typeof(ApiResponse<List<WordCloud>>),
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 添加其他API响应类型
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>typeof(List<Category>),
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>typeof(Category),
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>typeof(WordCloud),
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 添加所有模型类型
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>};

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 触发类型加载
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>foreach (var type in types)
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>{
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>var _ = type.FullName;
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>}
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>}
}修改ApiService类

修改ApiService类,确保在初始化时注册类型:
using Refit;
using StarBlogPublisher.Services.StarBlogApi;
using System;
using System.Net;
using System.Net.Http;
using Newtonsoft.Json;

namespace StarBlogPublisher.Services;

public class ApiService {
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>private static ApiService? _instance;

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>public static ApiService Instance {
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>get {
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>_instance ??= new ApiService();
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>return _instance;
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>}
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>}

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>private readonly RefitSettings _refitSettings;

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>private ApiService() {
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 确保类型被注册
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>RefitTypeRegistration.RegisterTypes();
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 配置Refit设置
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>_refitSettings = new RefitSettings(new NewtonsoftJsonContentSerializer(
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>new JsonSerializerSettings
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>{
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>TypeNameHandling = TypeNameHandling.Auto,
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 禁用反射优化,这在AOT环境中很重要
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>}
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>));
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>}

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// ... 其余代码保持不变 ...
}在App.axaml.cs中初始化类型注册

确保在应用启动时调用类型注册:
public override void OnFrameworkInitializationCompleted()
{
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 确保Refit类型被注册
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>RefitTypeRegistration.RegisterTypes();
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>// 其他初始化代码...
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>base.OnFrameworkInitializationCompleted();
}添加AOT兼容性配置

由于AOT编译对反射和动态代码生成有限制,需要添加一个rd.xml文件来指定需要保留的类型:
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><Type Name="StarBlogPublisher.Services.StarBlogApi.IAuth" Dynamic="Required All" />
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><Type Name="StarBlogPublisher.Services.StarBlogApi.ICategory" Dynamic="Required All" />
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><Type Name="StarBlogPublisher.Services.StarBlogApi.IBlogPost" Dynamic="Required All" />
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup></Assembly>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><Type Name="CodeLab.Share.ViewModels.Response.ApiResponse`1" Dynamic="Required All" />
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup><ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup></Assembly>
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup></Application>
</Directives>然后在项目文件中引用这个rd.xml文件:
<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>发布

使用以下命令发布AOT版本的应用程序:
dotnet publish -c Release -r win-x64 -p:PublishAot=true对于其他平台,可以替换相应的RID:

[*]Windows: win-x64
[*]macOS: osx-x64
[*]Linux: linux-x64
小结

AOT 发布是 .Net 平台一个重要的特性,它能将应用程序编译成不依赖运行时的机器码,不仅减小了发布包体积,还能提升启动速度,同时也增加了反编译的难度。
使用 AOT 方式发布 Avalonia 应用程序还是有一些坑的。:JSON序列化问题、类型注册问题以及AOT兼容性问题。针对这些问题,以下解决方案可以解决:

[*]JSON序列化方面,优先选择了对AOT支持更好的 Newtonsoft.Json 库,并通过类型预注册确保了序列化的正确性。
[*]对于需要反射的功能,通过rd.xml文件显式声明需要保留的类型,解决了AOT编译时的类型裁剪问题。
[*]在项目配置方面,通过合理设置AOT相关的编译选项,平衡了性能和包大小。
虽然AOT发布还存在一些限制,比如调试相对困难、部分第三方库可能不兼容等,但随着.Net平台的发展(特别是.Net9之后的版本),AOT的支持会越来越完善。对于需要高性能、小体积、反编译保护的Avalonia应用来说,AOT发布是一个值得考虑的选择。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: AOT编译Avalonia应用:StarBlog Publisher项目实践与挑战