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))]
来注册所需要的类型,值得说的一个点是如果需要的类,你得把你需要的类,并且所有用到的类型也注册到,如下面的代码所示,这里我注册了
PcInterfaces
和List<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,具体使用方式查看一下官方文档就好了,比较简单,这里就不做过多声明了