Avalonia实战之Native AOT编译

本文讲的是Avalonia使用Native AOT编译


1. 引言

本篇文章不讲那么多废话,直接讲点实际的,为什么需要AOT编译,AOT编译能干嘛,如何进行AOT编译

2.为什么需要AOT编译?

  • 启动时间缩短,运行时性能优化:
    传统 .NET 应用的启动时间可能高达数百毫秒,而 NativeAOT 编译的程序启动时间可减少 50% 以上。
    原生代码直接映射到 CPU 指令集,避免了 IL 解释和 JIT 编译的开销,执行速度更快。
  • 保护知识产权:
    AOT直接编译为本机机器码,几乎不太可能被反编译破解,真要有那本事,多的不说......

接下来我们着重讲一下如何解决Avalonia日常项目中的AOT编译难点,又该如何解决

  • 依赖注入(DI)

    相信大家项目中使用依赖注入的地方并不少,有时候我们并不希望要自己手动去创建服务、对象等,这个时候就希望能让DI容器来帮我们注入需要的服务和对象,但是我们也知道依赖注入背后大多使用的是反射,而AOT下反射其实并不太支持。所以我们得解决一下各种服务的注册和注入问题
    这里推荐一个支持AOT的依赖注入包,Jab
    dotnet add package Jab --version 0.12.0
    
    安装此依赖包之后,我们创建一个ServiceProvider,来注册模块
    [ServiceProvider]
    [Transient(typeof(IService), typeof(ServiceImplementation))]
    internal partial class MyServiceProvider { }
    
    这里[serviceProvider]特性加上partial声明部分类来告诉Jab这个类是一个依赖注入容器,这里注册服务和实现🥱都通过Attribute来注册,注册生命周期都是那几个,Scope,Transient等等
    如果你不想都注册在一个类里,可以拆分模块来注册,只需要使用[ServiceProviderModule]来标注这是Jab的DI注入容器模块,同时在MyServiceProvider里导入这个模块即可,同样也是用特性来导入 [Import<IMainModule>],这样就实现了拆分注册
    [ServiceProviderModule]
    [Transient<OpenProjectView>]
    [Scope<OpenProjectViewModel>]
    public interface IMainModule
    {
    }
    
    使用也很简单,直接new ServiceProvider() 即可,详细文档可直接查看nuget包上的Jab使用说明
    这里还需要说明的一个点是,编译器在进行AOT编译的时候会裁剪掉没用到的代码,虽然我不知道具体编译器里怎么定义的,但是根据我的个人经验,应该是从入口函数开始找,有直接依赖使用的才会保留,不然都会裁剪掉。
    但是我们使用的是mvvm模式(相信没人不是使用mvvm吧),这就导致我们的View都是靠ViewModel来驱动的,而本身的ViewLocator则是通过反射来查找定位对应的View,所以这里我们改造一下对应的ViewLocator
    public class ViewLocator : IDataTemplate
    {
      /// <summary>
      /// 添加视图字典
      /// </summary>
      private static readonly AvaloniaDictionary<string, Type> Views = new()
      {
          { "OpenProjectView", typeof(OpenProjectView) },
      };
    
      /// <summary>
      /// DI容器,用于解析视图实例
      /// </summary>
      private readonly IServiceProvider _serviceProvider = new ServiceProvider();
    
      public Control? Build(object? param)
      {
          if (param is null)
              return null;
    
          var name = param.GetType().Name!.Replace("ViewModel", "View", StringComparison.Ordinal);
          Views.TryGetValue(name, out var type);
          if (type == null || _serviceProvider.GetService(type) is not Control control)
              return new TextBlock { Text = "Not Found: " + name };
          control.DataContext = param;
          return control;
      }
    
      public bool Match(object? data)
      {
          return data is ViewModelBase;
      }
    }
    
    这里我的做法是将View保存到字典中,然后通过ViewModel来查找对应的View,然后再通过ServiceProvider来创建对应的View。这里的前提是需要在DI容器中注册一下所用到的View,
    值得说明的一个点是,建议把View注册为Transient瞬态生命周期,因为我们切换页面的时候再切换回来,如果是Scope或者Singleton生命周期时切换页面会报错,因为前后创建的是同一个View,这个时候再将View挂载到控件树上则会此View已存在控件树上的错误
    最后,最重要的一个点,告诉编译器我需要的东西不要裁剪
    <ItemGroup>
          <TrimmerRootAssembly Include="Avalonia.Controls" />
          <TrimmerRootAssembly Include="Avalonia.Themes.Fluent" />
          <TrimmerRootAssembly Include="System.Xml.Serialization" />
          <TrimmerRootAssembly Include="IoManager" />
    </ItemGroup>
    
    在csproj项目文件中配置一下上图的,建议把需要的程序集全部保留下来,尤其是主程序的程序集,不然,大概率还是会出问题,要么报找不到xxx类的默认构造方法,要么xxx类找不到,别问为什么,一个一个坑踩出来的。

  • 序列化

    相信没哪个项目不会用到序列化和反序列化吧
    要支持序列化也很简单,使用源生成器即可解决,这里我使用的是System.Text.Json,或者也可以找支持AOT的json库,这里我就讲官方推荐的做法,首先创建一个GenerationContext,然后用[JsonSerializable(typeof(XXX))]来注册所需要的类型,
    值得说的一个点是如果需要的类,你得把你需要的类,并且所有用到的类型也注册到,如下面的代码所示,这里我注册了PcInterfacesList<PcInterfaces> 如果只单纯注册PcInterfaces,则序列化集合对象的时候同样会报错
    [JsonSerializable(typeof(IOConfiguration))]
    [JsonSerializable(typeof(PcInterfaces))]
    [JsonSerializable(typeof(List<PcInterfaces>))]
    public partial class JsonGenerationContext:JsonSerializerContext
    {
    }
    
    使用也很简单
    • 明确实体类对象用法

      var data = JsonSerializer.Deserialize(json, JsonGenerationContext.Default.IOConfiguration);
      
    • 范型用法

       var a = JsonSerializer.Deserialize(json, typeof(T), JsonGenerationContext.Default);
      
    可能很多项目上用的是xml数据结构,这里我目前也没什么推荐的好方法,我个人的实现方法是XmlDocument来读取xml,转为json字符串再转换成对象。这个看个人需要自行扩展

  • Configuration配置文件

    这里讲一下配置文件如何通过DI容器注入IConfiguration来读取,项目中不可避免的会用到很多配置文件来做客户端的各种配置。所以如何读取配置文件是也是一个比较重要的点。
    当然,有很多种方法可以读取读取配置文件,但这里我讲的是我认为比较方便的一种方法,可能我习惯了写Web Api,所以比较喜欢appSettings.json这种配置文件。

    首先,引入对应的包

    <ItemGroup>
          <PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0-preview.3.25171.5" />
          <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0-preview.3.25171.5" />
          <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0-preview.3.25171.5" />
    </ItemGroup>
    

    接下来,我们借助上面说的Jab来注册IConfiguration,具体代码如下,注册为你需要的生命周期即可

    [ServiceProvider]
    [Scoped(typeof(IConfiguration),Factory = nameof(ConfigurationFactory))]
    public partial class ServiceProvider
    {
      public IConfiguration ConfigurationFactory()
      {
          var configuration = new ConfigurationBuilder()
              .SetBasePath(AppContext.BaseDirectory)
              .AddJsonFile("appSetting.json", optional: false, reloadOnChange: true)
              .Build();
          return configuration;
      }
    }
    
    到这里,我们就注册好了,直接在需要配置文件的地方通过构造函数注入IConfiguration来使用即可

  • orm框架

    有时候我们有数据读取和存储要求,就不可避免的得用到orm框架,这里推荐两个支持AOT的orm,dapper和SqlSugar,具体使用方式查看一下官方文档就好了,比较简单,这里就不做过多声明了

到这里,本篇文章就结束了,感兴趣或者有帮助的小伙伴可以点个赞。有需要也可以私信本博主

posted @ 2025-09-30 15:26  阳光小码农  阅读(115)  评论(0)    收藏  举报