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">
    <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <Nullable>enable</Nullable>
        <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
        <ApplicationManifest>app.manifest</ApplicationManifest>
        <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
        
        <!-- AOT 相关配置 -->
        <PublishAot>true</PublishAot>
        <TrimMode>full</TrimMode>
        <InvariantGlobalization>true</InvariantGlobalization>
        <IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
        <IlcOptimizationPreference>Size</IlcOptimizationPreference>
        <IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
        
        <JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
    </PropertyGroup>

    <!-- 其余部分保持不变 -->
</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生成序列化代码
[JsonSerializable(typeof(AppSettings))]
internal partial class AppSettingsContext : JsonSerializerContext
{
}

public class AppSettings {
    private static readonly string ConfigPath = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
        "StarBlogPublisher",
        "settings.json"
    );

    // ... 现有代码 ...

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

        return new AppSettings();
    }

    public void Save() {
        try {
            var directory = Path.GetDirectoryName(ConfigPath);
            if (!string.IsNullOrEmpty(directory)) {
                Directory.CreateDirectory(directory);
            }

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

            // 触发配置变更事件
            SettingsChanged?.Invoke(this, EventArgs.Empty);
        }
        catch (Exception) {
            // todo 处理保存失败的情况
        }
    }
}
解释#
  1. 添加了 [JsonSerializable] 特性和 JsonSerializerContext 派生类,这是.NET中支持AOT的JSON序列化的关键。这会在编译时生成序列化代码,而不是依赖运行时反射。
  2. 修改了 Load()Save() 方法,使用 AppSettingsContext.Default.AppSettings 作为类型信息,而不是依赖运行时类型推断。
  3. 这种方法确保了在AOT环境中,所有需要的序列化代码都会在编译时生成,而不需要运行时反射。

此外,还需要在项目文件中确保已启用AOT编译的JSON源生成器:

<PropertyGroup>
    <!-- 其他属性 -->
    <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
{
    /// <summary>
    /// 在应用启动时调用此方法,确保所有类型都被预注册
    /// </summary>
    public static void RegisterTypes()
    {
        // 注册常用的响应类型
        JsonConvert.DefaultSettings = () => new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.Auto,
            // 添加自定义转换器如果需要
            Converters = new List<JsonConverter>
            {
                // 可以添加自定义转换器
            }
        };

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

        // 触发类型加载
        foreach (var type in types)
        {
            var _ = type.FullName;
        }
    }
}
修改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 {
    private static ApiService? _instance;

    public static ApiService Instance {
        get {
            _instance ??= new ApiService();
            return _instance;
        }
    }

    private readonly RefitSettings _refitSettings;

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

    // ... 其余代码保持不变 ...
}
在App.axaml.cs中初始化类型注册#

确保在应用启动时调用类型注册:

public override void OnFrameworkInitializationCompleted()
{
    // 确保Refit类型被注册
    RefitTypeRegistration.RegisterTypes();
    
    // 其他初始化代码...
    
    base.OnFrameworkInitializationCompleted();
}

添加AOT兼容性配置#

由于AOT编译对反射和动态代码生成有限制,需要添加一个rd.xml文件来指定需要保留的类型:

<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
    <Application>
        <!-- 添加需要在AOT中保留的程序集和类型 -->
        <Assembly Name="StarBlogPublisher" Dynamic="Required All" />
        <Assembly Name="Avalonia.Markup.Xaml" Dynamic="Required All" />
        <Assembly Name="Avalonia" Dynamic="Required All" />

        <!-- Refit相关程序集 -->
        <Assembly Name="Refit" Dynamic="Required All" />
        <Assembly Name="Newtonsoft.Json" Dynamic="Required All" />

        <!-- 添加API接口和模型类型 -->
        <Assembly Name="StarBlogPublisher">
            <Type Name="StarBlogPublisher.Services.StarBlogApi.IAuth" Dynamic="Required All" />
            <Type Name="StarBlogPublisher.Services.StarBlogApi.ICategory" Dynamic="Required All" />
            <Type Name="StarBlogPublisher.Services.StarBlogApi.IBlogPost" Dynamic="Required All" />
        </Assembly>
        
        <Assembly Name="CodeLab.Share">
            <Type Name="CodeLab.Share.ViewModels.Response.ApiResponse`1" Dynamic="Required All" />
        </Assembly>
    </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兼容性问题。针对这些问题,以下解决方案可以解决:

  1. JSON序列化方面,优先选择了对AOT支持更好的 Newtonsoft.Json 库,并通过类型预注册确保了序列化的正确性。
  2. 对于需要反射的功能,通过rd.xml文件显式声明需要保留的类型,解决了AOT编译时的类型裁剪问题。
  3. 在项目配置方面,通过合理设置AOT相关的编译选项,平衡了性能和包大小。

虽然AOT发布还存在一些限制,比如调试相对困难、部分第三方库可能不兼容等,但随着.Net平台的发展(特别是.Net9之后的版本),AOT的支持会越来越完善。对于需要高性能、小体积、反编译保护的Avalonia应用来说,AOT发布是一个值得考虑的选择。

原创作者: deali 转载于: https://www.cnblogs.com/deali/p/18797316
【基于QT的调色板】是一个使用Qt框架开发的色彩选择工具,类似于Windows操作系统中常见的颜色选取器。Qt是一个跨平台的应用程序开发框架,广泛应用于桌面、移动和嵌入式设备,支持C++和QML语言。这个调色板功能提供了横竖两种渐变模式,用户可以方便地选取所需的颜色值。 在Qt中,调色板(QPalette)是一个关键的类,用于管理应用程序的视觉样式。QPalette包含了一系列的颜色角色,如背景色、前景色、文本色、高亮色等,这些颜色可以根据用户的系统设置或应用程序的需求进行定制。通过自定义QPalette,开发者可以创建具有独特视觉风格的应用程序。 该调色板功能可能使用了QColorDialog,这是一个标准的Qt对话框,允许用户选择颜色。QColorDialog提供了一种简单的方式来获取用户的颜色选择,通常包括一个调色板界面,用户可以通过滑动或点击来选择RGB、HSV或其他色彩模型中的颜色。 横渐变取色可能通过QGradient实现,QGradient允许开发者创建线性或径向的色彩渐变。线性渐变(QLinearGradient)沿直线从一个点到另一个点过渡颜色,而径向渐变(QRadialGradient)则以圆心为中心向外扩散颜色。在调色板中,用户可能可以通过滑动条或鼠标拖动来改变渐变的位置,从而选取不同位置的颜色。 竖渐变取色则可能是通过调整QGradient的方向来实现的,将原本水平的渐变方向改为垂直。这种设计可以提供另一种方式来探索颜色空间,使得选取颜色更为直观和便捷。 在【colorpanelhsb】这个文件名中,我们可以推测这是HSB(色相、饱和度、亮度)色彩模型相关的代码或资源。HSB模型是另一种常见且直观的颜色表示方式,RGB或CMYK模型不同,它以人的感知为基础,更容易理解。在这个调色板中,用户可能可以通过调整H、S、B三个参数来选取所需的颜色。 基于QT的调色板是一个利用Qt框架和其提供的色彩管理工具,如QPalette、QColorDialog、QGradient等,构建的交互式颜色选择组件。它不仅提供了横竖渐变的色彩选取方式,还可能支持HSB色彩模型,使得用户在开发图形用户界面时能更加灵活和精准地控制色彩。
标题基于Spring Boot的二手物品交易网站系统研究AI更换标题第1章引言阐述基于Spring Boot开发二手物品交易网站的研究背景、意义、现状及本文方法创新点。1.1研究背景意义介绍二手物品交易的市场需求和Spring Boot技术的适用性。1.2国内外研究现状概述当前二手物品交易网站的发展现状和趋势。1.3论文方法创新点说明本文采用的研究方法和在系统设计中的创新之处。第2章相关理论技术介绍开发二手物品交易网站所涉及的相关理论和关键技术。2.1Spring Boot框架解释Spring Boot的核心概念和主要特性。2.2数据库技术讨论适用的数据库技术及其在系统中的角色。2.3前端技术阐述后端配合的前端技术及其在系统中的应用。第3章系统需求分析详细分析二手物品交易网站系统的功能需求和性能需求。3.1功能需求列举系统应实现的主要功能模块。3.2性能需求明确系统应满足的性能指标和安全性要求。第4章系统设计实现具体描述基于Spring Boot的二手物品交易网站系统的设计和实现过程。4.1系统架构设计给出系统的整体架构设计和各模块间的交互方式。4.2数据库设计详细阐述数据库的结构设计和数据操作流程。4.3界面设计实现介绍系统的界面设计和用户交互的实现细节。第5章系统测试优化说明对系统进行测试的方法和性能优化的措施。5.1测试方法步骤测试环境的搭建、测试数据的准备及测试流程。5.2测试结果分析对测试结果进行详细分析,验证系统是否满足需求。5.3性能优化措施提出针对系统性能瓶颈的优化建议和实施方案。第6章结论展望总结研究成果,并展望未来可能的研究方向和改进空间。6.1研究结论概括本文基于Spring Boot开发二手物品交易网站的主要发现和成果。6.2展望改进讨论未来可能的系统改进方向和新的功能拓展。
1. 用户权限管理模块 角色管理: 学生:查看个人住宿信息、提交报修申请、查看卫生检查结果、请假外出登记 宿管人员:分配宿舍床位、处理报修申请、记录卫生检查结果、登记晚归情况 管理员:维护楼栋房间信息、管理用户账号、统计住宿数据、发布宿舍通知 用户操作: 登录认证:对接学校统一身份认证(模拟实现,用学号 / 工号作为账号),支持密码重置 信息管理:学生完善个人信息(院系、专业、联系电话),管理员维护所有用户信息 权限控制:不同角色仅可见对应功能(如学生无法修改床位分配信息) 2. 宿舍信息管理模块 楼栋房间管理: 楼栋信息:名称(如 "1 号宿舍楼")、层数、性别限制(男 / 女 / 混合)、管理员(宿管) 房间信息:房间号(如 "101")、户型(4 人间 / 6 人间)、床位数量、已住人数、可用状态 设施信息:记录房间内设施(如空调、热水器、桌椅)的配置完好状态 床位管理: 床位编号:为每个床位设置唯一编号(如 "101-1" 表示 101 房间 1 号床) 状态标记:标记床位为 "空闲 / 已分配 / 维修中",支持批量查询空闲床位 历史记录:保存床位的分配变更记录(如从学生 A 调换到学生 B 的时间原因) 3. 住宿分配调整模块 住宿分配: 新生分配:管理员导入新生名单后,宿管可按专业集中、性别匹配等规则批量分配床位 手动分配:针对转专业、复学学生,宿管手动指定空闲床位并记录分配时间 分配结果公示:学生登录后可查看自己的宿舍信息(楼栋、房间号、床位号、室友列表) 调整管理: 调宿申请:学生提交调宿原因(如室友矛盾、身体原因),选择意向宿舍(需有空位) 审批流程:宿管审核申请,通过后执行床位调换,更新双方住宿信息 换宿记录:保存调宿历史(申请人、原床位、新床位、审批人、时间) 4. 报修安全管理模块 报修管理: 报修提交:学生选择宿舍、设施类型(如 "
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值