主要内容
-
修复默认的MauiImage大小 -
删除Application.Properties 和DataContractSerializer. -
修剪未使用的HTTP实现
-
删除Microsoft.Extensions.Http用法 -
删除Newtonsoft.Json使用 -
在后台运行第一个网络请求
-
修剪Resource.designer.cs -
R8 Java代码收缩器 -
AOT一切 -
AOT和LLVM -
记录自定义AOT配置文件
应用程序大小的改进
▌修复默认的MauiImage大小
<svg width="419" height="519" viewBox="0 0 419 519" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- everything else -->
objReleasenet6.0-androidresizetizerrmipmap-xxxhdpi
appiconfg.png = 1824x1824
dotnet_bot.png = 1676x2076
<!-- Splash Screen -->
<MauiSplashScreen Include="Resources\appiconfg.svg" Color="#512BD4" BaseSize="128,128" />
<!-- Images -->
<MauiImage Include="Resources\Images\*" />
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
obj\Release\net6.0-android\resizetizer\r\mipmap-xxxhdpi\
appiconfg.png = 512x512
dotnet_bot.png = 672x832
<MauiImage Include="Resources\Images\large.jpg" />
-
修复默认的MauiImage大小:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#fix-defaults-for-mauiimage-sizes?ocid=AID3045631
-
dotnet/maui#4759:
https://github.com/dotnet/maui/pull/4759
-
dotnet/maui#6419:
https://github.com/dotnet/maui/pull/6419
-
删除Application.Properties 和DataContractSerializer:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#remove-applicationproperties-and-datacontractserializer?ocid=AID3045631
-
dotnet/maui#4976: https://github.com/dotnet/maui/pull/4976
▌修剪未使用的HTTP实现
通过修正这个问题,在任何.NET MAUI应用程序中都可以删除更多的IL代码。在一个例子中,一个使用HTTP的android应用程序能够完全删除几个程序集:
-
Microsoft.Win32.Primitives.dll
-
System.Formats.Asn1.dll
-
System.IO.Compression.Brotli.dll
-
System.NET.NameResolution.dll
-
System.NET.NETworkInformation.dll
-
System.NET.Quic.dll
-
System.NET.Security.dll
-
System.NET.Sockets.dll
-
System.Runtime.InteropServices.RuntimeInformation.dll
-
System.Runtime.Numerics.dll
-
System.Security.Cryptography.Encoding.dll
-
System.Security.Cryptography.X509Certificates.dll
-
System.Threading.Channels.dll
-
修剪未使用的HTTP实现:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#trim-unused-http-implementations?ocid=AID3045631
-
dotnet/runtime#64852:
https://github.com/dotnet/runtime/pull/64852
-
xamarin-android#6749:
https://github.com/xamarin/xamarin-android/pull/6749
-
xamarin-macios#14297:
https://github.com/xamarin/xamarin-macios/pull/14297
.NET Podcast示例中的改进
▌删除Microsoft.Extensions.Http用法
因此,HttpClient不使用DI:
builder.Services.AddHttpClient<ShowsService>(client =>
{
client.BaseAddress = new Uri(Config.APIUrl);
});
// Then in the service ctor
public ShowsService(HttpClient httpClient, ListenLaterService listenLaterService)
{
this.httpClient = httpClient;
// ...
}
我们简单地创建一个HttpClient来在服务中使用:
public ShowsService(ListenLaterService listenLaterService)
{
this.httpClient = new HttpClient() { BaseAddress = new Uri(Config.APIUrl) };
// ...
}
我们建议对应用程序需要交互的每个web服务使用一个单独的HttpClient实例。
请参阅dotnet/runtime#66863和dotnet podcasts#44了解有关改进的详细信息。
-
.NET Podcast:
https://github.com/microsoft/dotnet-podcasts
-
删除Microsoft.Extensions.Http用法:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#remove-microsoftextensionshttp-usage?ocid=AID3045631
-
dotnet/runtime#66863:
https://github.com/dotnet/runtime/issues/66863
-
dotnet podcasts#44:
https://github.com/microsoft/dotnet-podcasts/pull/44
.NET Podcast 样本使用了一个名为MonkeyCache的库,它依赖于Newtonsoft.Json。这本身并不是一个问题,只是.NET MAUI + Blazor应用程序依赖于一些ASP.NET Core库反过来依赖于System.Text.Json。这款应用实际上是为JSON解析库“付了两倍钱”,这对应用的大小产生了影响。
-
删除Newtonsoft.Json使用:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#remove-newtonsoftjson-usage?ocid=AID3045631
-
.NET Podcast :
https://github.com/microsoft/dotnet-podcasts/pull/44
-
MonkeyCache:
https://github.com/jamesmontemagno/monkey-cache
-
monkey-cache#109:
https://github.com/jamesmontemagno/monkey-cache/pull/109
-
dotnet-podcasts#58:
https://github.com/microsoft/dotnet-podcasts/pull/58
回顾dotnet跟踪输出,初始请求在ShowsService阻塞UI线程初始化连接.NETworkAccess Barrel.Current。得到,HttpClient。这项工作可以在后台线程中完成-在这种情况下导致更快的启动时间。在Task.Run()中封装第一个调用,可以在一定程度上提高这个示例的启动效率。
在Pixel 5a设备上平均运行10次:
Before
Average(ms): 843.7
Average(ms): 847.8
After
Average(ms): 817.2
Average(ms): 812.8
对于这种类型的更改,总是建议根据dotnet跟踪或其他分析结果来做出决定,并度量更改前后的变化。
请参阅dotnet-podcasts#57了解有关此改进的详细信息。
-
在后台运行第一个网络请求:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#run-first-network-request-in-background?ocid=AID3045631
-
dotnet-podcasts#57: https://github.com/microsoft/dotnet-podcasts/pull/57
实验性或高级选项
如果你想在android上进一步优化你的.NET MAUI应用程序,这里有一些高级或实验性的特性,默认情况下不是启用的。
自从Xamarin诞生以来,android应用程序就包含了一个生成的Properties/Resource.designer.cs文件,用于访问androidResource文件的整数标识符。这是R.java类的c# /托管版本,允许使用这些标识符作为普通的c#字段(有时是const),而无需与Java进行任何互操作。
在一个android Studio“库”项目中,当你包含一个像res/drawable/foo.png这样的文件时,你会得到一个像这样的字段:
package com.yourlibrary;
public class R
{
public class drawable
{
// The actual integer here maps to a table inside the final .apk file
public final int foo = 1234;
}
}
你可以使用这个值,例如,在ImageView中显示这个图像:
ImageView imageView = new ImageView(this);
imageView.setImageResource(R.drawable.foo);
当你构建com.yourlibrary.aar时, android的gradle插件实际上并没有把这个类放在包中。相反,android应用程序实际上知道整数的值是多少。因此,R类是在android应用程序构建时生成的,为每个android库生成一个R类。
Xamarin.Android采取了不同的方法,在运行时进行整数修复。用c#和MSBuild做这样的事情真的没有一个很好的先例吗?例如,一个c# android库可能有:
public class Resource
{
public class Drawable
{
// The actual integer here is *not* final
public int foo = -1;
}
}
然后主应用程序就会有如下代码:
public class Resource
{
public class Drawable
{
public Drawable()
{
// Copy the value at runtime
global::MyLibrary.Resource.Drawable.foo = foo;
}
// The actual integer here *is* final
public const int foo = 1234;
}
}
这种情况已经很好地运行了一段时间,但不幸的是,像androidX、Material、谷歌Play Services等谷歌的库中的资源数量已经开始复合。例如,在dotnet/maui#2606中,启动时设置了21497个字段!我们创建了一种方法来解决这个问题,但我们也有一个新的自定义修剪步骤来执行修复在构建时(在修剪期间)而不是在运行时。
<AndroidLinkResources>true</ AndroidLinkResources>
这将使你的版本版本替换案例如下:
ImageView imageView = new(this);
imageView.SetImageResource(Resource.Drawable.foo);
相反,直接内联整数:
ImageView imageView = new(this);
imageView.SetImageResource(1234); // The actual integer here *is* final
这个特性的一个已知问题是:
public partial class Styleable
{
public static int[] ActionBarLayout = new int[] { 16842931 };
}
目前不支持替换int[]值,这使得我们不能默认启用它。一些应用程序将能够打开这个功能,dotnet新的maui模板,也许许多.NET maui android应用程序不会遇到这个限制。
在未来的.NET版本中,我们可能会默认启用$(androidLinkResources),或者完全重新设计。
查看xamarin-android#5317, xamarin-android#6696,和dotnet/maui#4912了解该功能的详细信息。
-
修剪Resource.designer.cs:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#trimming-resourcedesignercs?ocid=AID3045631
-
dotnet/maui#2606:
https://github.com/dotnet/maui/pull/2606
-
xamarin-android#5317:
https://github.com/xamarin/xamarin-android/pull/5317
-
xamarin-android#6696:
https://github.com/xamarin/xamarin-android/pull/6696
-
和dotnet/maui#4912:
https://github.com/dotnet/maui/pull/4912
R8是全程序优化、收缩和缩小工具,将java字节代码转换为优化的dex代码。R8使用Proguard keep规则格式为应用程序指定入口点。如您所料,许多应用程序需要额外的Proguard规则来保持工作。R8可能过于激进,并且删除了Java反射所调用的一些东西,等等。我们还没有一个很好的方法让它成为所有.NET android应用程序的默认设置。
要选择使用R8 for Release版本,请在你的.csproj中添加以下内容:
<!-- NOTE: not recommended for Debug builds! -->
<AndroidLinkTool Condition="'$(Configuration)' == 'Release'">r8</AndroidLinkTool>
如果启动你的应用程序的Release构建在启用后崩溃,检查adb logcat输出,看看哪里出了问题。
如果你看到java.lang. classnotfoundexception或java.lang。你可能需要添加一个ProguardConfiguration文件到你的项目中,比如:
<ItemGroup>
<ProguardConfiguration Include="proguard.cfg" />
</ItemGroup>
-keep class com.thepackage.TheClassYouWantToPreserve { *; <init>(...); }
我们正在研究在未来的.NET版本中默认启用R8的选项。
详情请参阅我们的D8/R8文档。
-
R8 Java代码收缩器:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#r8-java-code-shrinker?ocid=AID3045631
-
我们的D8/R8文档: https://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/D8andR8.md
Profiled AOT是默认的,因为它在应用程序大小和启动性能之间给出了最好的权衡。如果应用程序的大小与你的应用程序无关,你可以考虑对所有.NET程序集使用AOT。
要选择加入,在你的.csproj中添加以下Release配置:
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<RunAOTCompilation>true</RunAOTCompilation>
<androidEnableProfiledAot>false</androidEnableProfiledAot>
</PropertyGroup>
这将减少在应用程序启动期间发生的JIT编译量,以及导航到后面的屏幕等。
-
AOT:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#aot-everything?ocid=AID3045631
LLVM提供了一个独立于源和目标的现代优化器,可以与Mono AOT Compiler输出相结合。其结果是,应用的尺寸略大,发行构建时间更长,运行时性能更好。
要选择将LLVM用于Release版本,请将以下内容添加到你的.csproj中:
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<RunAOTCompilation>true</RunAOTCompilation>
<EnableLLVM>true</EnableLLVM>
</PropertyGroup>
此特性可以与Profiled AOT(或AOT-ing一切)结合使用。对比应用程序的前后,了解EnableLLVM对应用程序大小和启动性能的影响。
目前,需要安装一个android NDK来使用这个功能。如果我们能够解决这个需求,EnableLLVM将成为未来.NET版本中的默认选项。
有关详细信息,请参阅我们关于EnableLLVM的文档。
-
AOT和LLVM:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#aot-and-llvm?ocid=AID3045631
-
LLVM: https://llvm.org/ -
EnableLLVM的文档: https://docs.microsoft.com/en-us/xamarin/android/deploy-test/building-apps/build-properties#enablellvm?ocid=AID3045631
概要AOT默认使用我们在.NET MAUI和android工作负载中提供的“内置”概要文件,对大多数应用程序都很有用。为了获得最佳的启动性能,理想情况下应该记录应用程序特定的配置文件。针对这种情况,我们有一个实验性的Mono.Profiler.Android包。
记录配置文件:
dotnet add package Mono.AotProfiler.android
dotnet build -t:BuildAndStartAotProfiling
# Wait until app launches, or you navigate to a screen
dotnet build -t:FinishAotProfiling
这将在你的项目目录下产生一个custom.aprof。要在未来的构建中使用它:
<ItemGroup>
<androidAotProfile Include="custom.aprof" />
</ItemGroup>
我们正在努力在未来的.NET版本中完全支持记录自定义概要文件。
-
记录自定义AOT配置文件:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#record-a-custom-aot-profile?ocid=AID3045631
-
Mono.Profiler.Android: https://github.com/jonathanpeppers/Mono.Profiler.Android
希望您喜欢我们的.NET MAUI性能论述。请尝试.NET MAUI并且可以在http://dot.net/maui了解更多!