.NET多平台应用程序UI (MAUI)将android、iOS、macOS和Windows API对立为一个API,这样你就能够编写一个应用程序在许多平台上本机运行。咱们专一于进步您的日常生产力以及您的应用程序的性能。咱们认为,开发人员生产率的进步不应该以应用程序性能为代价。

应用程序的大小也是如此——在一个空白的.NET MAUI应用程序中存在什么开销?当咱们开始优化.NET MAUI时,很显著iOS须要做一些工作来改善应用程序的大小,而android则不足启动性能。

一个dotnet new maui我的项目的iOS应用程序最后大概是18MB。同样,在之前的预览中.NET MAUI在android上的启动工夫也不是很现实:

.NET Podcast:
https://github.com/microsoft/...

这是在Pixel 5设施上均匀运行10次失去的后果。无关这些数字是如何取得的,请参阅咱们的maui-profiling文件。

咱们的指标是让.NET MAUI比它的前身Xamarin更快。很显著,咱们在.NET MAUI自身也有一些工作要做。dotnet new android 模板的公布速度曾经超过Xamarin.Android,次要是因为.NET 6中新的BCL和Mono运行时。

新的.NET maui模板还没有应用Shell导航模式,然而打算将其作为.NET maui的默认导航模式。当咱们采纳这个更改时,咱们晓得会对模板中的性能造成影响。

几个不同团队的单干才有了明天的成就。咱们改良了Microsoft.Extensions ,依赖注入的应用,AOT编译,Java互操作,XAML,.NET MAUI代码,等等方面。

.NET Podcast App (Shell):https://github.com/microsoft/...
**这是原始的dotnet new maui模板,没有应用Shell。

内容非常丰盛,来看是否有您期待的更新吧!

次要内容

启动性能的改良

  • 在挪动设施上进行剖析
  • 测量随着工夫的推移
  • Profiled AOT
  • 单文件程序集存储器
  • Spanify.RegisterNativeMembers
  • System.Reflection.Emit和构造函数
  • System.Reflection.Emit和办法
  • 更新的Java.Interop APIs
  • 多维Java数组
  • 为android图像应用Glide
  • 缩小Java互操作调用
  • 将android XML移植到Java
  • 删除Microsoft.Extensions.Hosting
  • 在启动时缩小Shell初始化
  • 字体不应该应用临时文件
  • 编译时在平台上计算
  • 在XAML中应用编译转换器
  • 优化色彩解析
  • 不要应用区域性辨认的字符串比拟
  • 懈怠地创立日志
  • 应用工厂办法进行依赖注入
  • 懈怠地负载ConfigurationManager
  • 默认VerifyDependencyInjectionOpenGenericServiceTrimmability
  • 改良内置AOT配置文件
  • 启用AOT图像的提早加载
  • 删除System.Uri中未应用的编码对象

应用程序大小的改良

  • 修复默认的MauiImage大小
  • 删除Application.Properties 和DataContractSerializer
  • 修剪未应用的HTTP实现

.NET Podcast示例中的改良

  • 删除Microsoft.Extensions.Http用法
  • 删除Newtonsoft.Json应用
  • 在后盾运行第一个网络申请

实验性或高级选项

  • 修剪Resource.designer.cs
  • R8 Java代码膨胀器
  • AOT所有
  • AOT和LLVM
  • 记录自定义AOT配置文件:

启动性能的改良

▌在挪动设施上进行剖析

我必须提到挪动平台上可用的.NET诊断工具,因为它是咱们使.NET MAUI更快的第0步。
剖析.NET 6 android应用程序须要应用一个叫做dotnet-dsrouter的工具。该工具使dotnet跟踪连贯到一个运行的挪动应用程序在android, iOS等。这可能是咱们用来剖析.NET MAUI的最有影响力的工具。

要开始应用dotnet trace和dsrouter,首先通过adb配置一些设置并启动dsrouter:

adb reverse tcp:9000 tcp:9001adb shell setprop debug.mono.profile '127.0.0.1:9000,suspend'dotnet-dsrouter client-server -tcps 127.0.0.1:9001 -ipcc /tmp/maui-app --verbose debug

下一步启动dotnet跟踪,如:

dotnet-trace collect --diagnostic-port /tmp/maui-app --format speedscope

在启动一个应用-c Release和-p:androidEnableProfiler=true构建的android应用程序后,当dotnet trace输入时,你会留神到连贯:

Press <Enter> or <Ctrl+C> to exit...812  (KB)

在您的应用程序齐全启动后,只需按下enter键就能够失去一个保留在当前目录的*.speedscope。你能够在https://speedscope.app上关上这个文件,深刻理解每个办法在应用程序启动期间所破费的工夫:

在android应用程序中应用dotnet跟踪的更多细节,请参阅咱们的文档。我倡议在android设施上剖析Release版本,以取得利用在事实世界中的最佳体现。

▌测量随着工夫的推移

咱们在.NET根底团队的敌人建设了一个管道来跟踪.NET MAUI性能场景,例如:

  • 包大小
  • 磁盘大小(未压缩)
  • 单个文件分类
  • 应用程序启动
    随着工夫的推移,这使咱们可能看到改良或回归的影响,看到dotnet/maui回购的每个提交的数字。咱们还能够确定这种差别是否是由xamarin-android、xamarin-macios或dotnet/runtime中的变动引起的。

例如,在物理Pixel 4a设施上运行的dotnet new maui模板的启动工夫(以毫秒为单位)图:

留神,Pixel 4a比Pixel 5要慢得多。

咱们能够准确地指出在dotnet/maui中产生的回归和改良。这对于追踪咱们的指标是十分有用的。

同样地,咱们能够在雷同的Pixel 4a设施上看到.NET Podcast利用随着工夫的推移所获得的停顿:

这张图表是咱们真正关注的焦点,因为它是一款“真正的利用”,靠近于开发者在本人的手机利用中看到的内容。

至于应用程序大小,它是一个更稳固的数字——当状况变得更糟或更好时,它很容易归零:

请参阅dotnet-podcasts#58, Android x# 520和dotnet/maui#6419理解这些改良的详细信息。

▌异形AOT

在咱们对.NET MAUI的初始性能测试中,咱们看到了JIT(及时)和AOT(提前)编译的代码是如何执行的:

每次调用c#办法时都会产生JIT解决,这会隐式地影响挪动应用程序的启动性能。

另一个问题是AOT导致的应用程序大小减少。每个.NET程序集都会在最终利用中增加一个android本地库。为了更好地利用这两个世界,启动跟踪或剖析AOT是Xamarin.Android以后的一个个性。这是一种AOT应用程序启动门路的机制,它显著进步了启动工夫,而只减少了适度的应用程序大小。

在.NET 6版本中,这是齐全有意义的默认选项。在过来,应用Xamarin.Android进行任何类型的AOT都须要Android NDK(下载多个gb)。咱们在没有装置android NDK的状况下构建了AOT应用程序,使其成为可能。

咱们为 dotnet new android, maui,和maui-blazor模板的内置配置文件,使大多数应用程序受害。如果你想在.NET 6中记录一个自定义配置文件,你能够试试咱们的实验性的Mono.Profiler. Android包。咱们正在致力在将来的.NET版本中齐全反对记录自定义概要文件。
查看xamarin-Android#6547和dotnet/maui#4859理解这个改良的细节。

▌单文件程序集存储器

之前,如果你在你最喜爱的zip文件实用程序中查看Release android .apk内容,你能够看到.NET程序集位于:

assemblies/Java.Interop.dllassemblies/Mono.android.dllassemblies/System.Runtime.dllassemblies/arm64-v8a/System.Private.CoreLib.dllassemblies/armeabi-v7a/System.Private.CoreLib.dllassemblies/x86/System.Private.CoreLib.dllassemblies/x86_64/System.Private.CoreLib.dll

这些文件是通过mmap零碎调用独自加载的,这是应用程序中每个.NET程序集的老本。这是在android工作负载中用C/ c++实现的,应用Mono运行时为程序集加载提供的回调。MAUI应用程序有很多程序集,所以咱们引入了一个新的$(androidUseAssemblyStore)个性,该个性在Release版本中默认启用。
在这个扭转之后,你会失去:

assemblies/assemblies.manifestassemblies/assemblies.blobassemblies/assemblies.arm64_v8a.blobassemblies/assemblies.armeabi_v7a.blobassemblies/assemblies.x86.blobassemblies/assemblies.x86_64.blob

当初android启动只须要调用mmap两次:一次是assemblies.blob,第二次是特定于体系结构的Blob。这对带有许多. net程序集的应用程序产生了显著的影响。

如果你须要查看编译过的android应用程序中这些程序集的IL,咱们创立了一个程序集存储读取器工具来“解包”这些文件。

另一个抉择是在构建应用程序时禁用这些设置:

dotnet build -c Release -p:AndroidUseAssemblyStore=false -p:Android EnableAssemblyCompression=false

这样你就能够用你喜爱的压缩工具解压生成的.apk文件,并应用ILSpy这样的工具来查看.NET程序集。这是一个很好的办法来诊断修剪器/链接器问题。
查看xamarin-android#6311理解对于这个改良的详细信息。

▌Spanify RegisterNativeMembers

当用Java创立c#对象时,会调用一个小型的Java包装器,例如:

public class MainActivity extends Android.app.Activity{    public static final String methods;    static {        methods = "n_onCreate:(LAndroid/os/Bundle;)V:GetOnCreate_Landroid_os_Bundle_Handler\n";        mono.Android.Runtime.register ("foo.MainActivity, foo", MainActivity.class, methods);    }

办法列表是一个以\n和:分隔的Java本机接口(JNI)签名列表,这些签名在托管的c#代码中被重写。对于在c#中重写的每个Java办法,您都会失去一个这样的办法。
当理论的Java onCreate()办法被调用为一个android流动:

public void onCreate (Android.os.Bundle p0){    n_onCreate (p0);}private native void n_onCreate (Android.os.Bundle p0);

通过各种各样的魔术和手势,n_onCreate调用到Mono运行时,并调用c#中的OnCreate()办法。

拆分\n和:-分隔的办法列表的代码是在Xamarin晚期应用string.Split()编写的。能够说,Span<T>在那时还不存在,但咱们当初能够应用它!这进步了任何继承Java类的c#类的老本,因而这是一个比.NET MAUI更宽泛的改良。

你可能会问,“为什么要应用字符串呢?”应用Java数组仿佛比分隔字符串对性能的影响更大。在咱们的测试中,调用JNI来获取Java数组元素,性能比字符串差。Split和Span的新用法。对于如何在将来的.NET版本中从新构建它,咱们有一些想法。

除了.NET 6之外,针对以后客户Xamarin. Android的最新版本也附带了这一更改。
查看xamarin-android#6708理解对于此改良的详细信息。

▌System.Reflection.Emit和构造函数

在应用Xamarin的晚期,咱们有一个从Java调用c#构造函数的有点简单的办法。
首先,咱们有一些在启动时产生的反射调用:

static MethodInfo newobject = typeof (System.Runtime.CompilerServices.RuntimeHelpers).GetMethod ("GetUninitializedObject", BindingFlags.Public | BindingFlags.Static)!;static MethodInfo gettype = typeof (System.Type).GetMethod ("GetTypeFromHandle", BindingFlags.Public | BindingFlags.Static)!;static FieldInfo handle = typeof (Java.Lang.Object).GetField ("handle", BindingFlags.NonPublic | BindingFlags.Instance)!;

这仿佛是Mono晚期版本遗留下来的,并始终连续到明天。例如,能够间接调用RuntimeHelpers.GetUninitializedObject()。
而后是一些简单的System.Reflection.Emit用法,并在
System.Reflection.ConstructorInfo中传递一个cinfo实例:

DynamicMethod method = new DynamicMethod (DynamicMethodNameCounter.GetUniqueName (), typeof (void), new Type [] {typeof (IntPtr), typeof (object []) }, typeof (DynamicMethodNameCounter), true);ILGenerator il = method.GetILGenerator ();il.DeclareLocal (typeof (object));il.Emit (OpCodes.Ldtoken, type);il.Emit (OpCodes.Call, gettype);il.Emit (OpCodes.Call, newobject);il.Emit (OpCodes.Stloc_0);il.Emit (OpCodes.Ldloc_0);il.Emit (OpCodes.Ldarg_0);il.Emit (OpCodes.Stfld, handle);il.Emit (OpCodes.Ldloc_0);var len = cinfo.GetParameters ().Length;for (int i = 0; i < len; i++) {    il.Emit (OpCodes.Ldarg, 1);    il.Emit (OpCodes.Ldc_I4, i);    il.Emit (OpCodes.Ldelem_Ref);}il.Emit (OpCodes.Call, cinfo);il.Emit (OpCodes.Ret);return (Action<IntPtr, object?[]?>) method.CreateDelegate (typeof (Action <IntPtr, object []>));

调用返回的委托,使得IntPtr是Java.Lang.Object子类的句柄,而对象[]是该特定c#构造函数的任何参数。emit对于在启动时第一次应用它以及当前的每次调用都有很大的老本。

通过认真的审查,咱们能够将handle字段设置为外部的,并将此代码简化为:

var newobj = RuntimeHelpers.GetUninitializedObject (cinfo.DeclaringType);if (newobj is Java.Lang.Object o) {    o.handle = jobject;} else if (newobj is Java.Lang.Throwable throwable) {    throwable.handle = jobject;} else {    throw new InvalidOperationException ($"Unsupported type: '{newobj}'");}cinfo.Invoke (newobj, parms);

这段代码所做的是在不调用构造函数的状况下创立一个对象,设置句柄字段,而后调用构造函数。这样做是为了当c#构造函数开始时,Handle在任何Java.Lang.Object上都是无效的。构造函数外部的任何Java互操作(比方调用类上的其余Java办法)以及调用任何根本Java构造函数都须要Handle。
新代码显著改良了从Java调用的任何c#构造函数,因而这个非凡的更改改良的不仅仅是.NET MAUI。除了.NET 6之外,针对以后客户Xamarin. android的最新版本也附带了这一更改。
查看xamarin-android#6766理解这个改良的详细信息。

▌System.Reflection.Emit和办法

当你在c#中重写一个Java办法时,比方:

public class MainActivity : Activity{    protected override void OnCreate(Bundle savedInstanceState)    {         base.OnCreate(savedInstanceState);         //...    }}

在从Java到c#的转换过程中,咱们必须封装c#办法来解决异样,例如:

try{    // Call the actual C# method here}catch (Exception e) when (_unhandled_exception (e)){    androidEnvironment.UnhandledException (e);    if (Debugger.IsAttached || !JNIEnv.PropagateExceptions)        throw;}

例如,如果在OnCreate()中未解决托管异样,那么实际上会导致本机解体(并且没有托管的c#堆栈跟踪)。咱们须要确保调试器在附加异样时可能中断,否则将记录c#堆栈跟踪。

从Xamarin开始,下面的代码是通过System.Reflection.Emit生成的:

var dynamic = new DynamicMethod (DynamicMethodNameCounter.GetUniqueName (), ret_type, param_types, typeof (DynamicMethodNameCounter), true);var ig = dynamic.GetILGenerator ();LocalBuilder? retval = null;if (ret_type != typeof (void))    retval = ig.DeclareLocal (ret_type);ig.Emit (OpCodes.Call, wait_for_bridge_processing_method!);var label = ig.BeginExceptionBlock ();for (int i = 0; i < param_types.Length; i++)    ig.Emit (OpCodes.Ldarg, i);ig.Emit (OpCodes.Call, dlg.Method);if (retval != null)    ig.Emit (OpCodes.Stloc, retval);ig.Emit (OpCodes.Leave, label);bool  filter = Debugger.IsAttached || !JNIEnv.PropagateExceptions;if (filter && JNIEnv.mono_unhandled_exception_method != null) {    ig.BeginExceptFilterBlock ();    ig.Emit (OpCodes.Call, JNIEnv.mono_unhandled_exception_method);    ig.Emit (OpCodes.Ldc_I4_1);    ig.BeginCatchBlock (null!);} else {    ig.BeginCatchBlock (typeof (Exception));}ig.Emit (OpCodes.Dup);ig.Emit (OpCodes.Call, exception_handler_method!);if (filter)    ig.Emit (OpCodes.Throw);ig.EndExceptionBlock ();if (retval != null)    ig.Emit (OpCodes.Ldloc, retval);ig.Emit (OpCodes.Ret);

这段代码被调用两次为一个 dotnet new android 应用程序,但~58次为一个dotnet new maui应用程序!

咱们意识到实际上能够为每个通用委托类型编写一个强类型的“疾速门路”,而不是应用System.Reflection.Emit。有一个生成的委托匹配每个签名:

void OnCreate(Bundle savedInstanceState);// Maps to *JNIEnv, JavaClass, Bundle// Internal to each assemblyinternal delegate void _JniMarshal_PPL_V(IntPtr, IntPtr, IntPtr);

这样咱们就能够列出所有应用过的dotnet maui应用程序的签名,比方:

class JNINativeWrapper{    static Delegate? CreateBuiltInDelegate (Delegate dlg, Type delegateType)    {        switch (delegateType.Name)        {            // Unsafe.As<T>() is used, because _JniMarshal_PPL_V is generated internal in each assembly            case nameof (_JniMarshal_PPL_V):                return new _JniMarshal_PPL_V (Unsafe.As<_JniMarshal_PPL_V> (dlg).Wrap_JniMarshal_PPL_V);            // etc.        }        return null;    }    // Static extension method is generated to avoid capturing variables in anonymous methods    internal static void Wrap_JniMarshal_PPL_V (this _JniMarshal_PPL_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0)    {        // ...    }}

这种办法的毛病是,当应用新签名时,咱们必须列出更多的状况。咱们不想详尽地列出每一种组合,因为这会导致IL大小的增长。咱们正在钻研如何在将来的.NET版本中改良这一点。

查看xamarin-android#6657和xamarin-android#6707理解这个改良的详细信息。

▌更新的Java.Interop APIs

Java.Interop.dll中原始的Xamarin api是这样的api:

  • JNIEnv.CallStaticObjectMethod

在Java中调用的“新办法”每次调用占用的内存更少:

  • JniEnvironment.StaticMethods.CallStaticObjectMethod

当在构建时为Java办法生成c#绑定时,默认应用更新/更快的办法—在Xamarin.Android中曾经有一段时间了。以前,Java绑定我的项目能够将$(AndroidCodegenTarget)设置为XAJavaInterop1,它在每次调用中缓存和重用jmethodID实例。请参阅java.interop文档获取对于该个性的历史记录。

其余有问题的中央是有“手动”绑定的中央。这些往往也是常常应用的办法,所以值得修复这些!

一些改善这种状况的例子:

  • JNIEnv.FindClass()在xamarin-android#6805
  • JavaList 和 JavaList<T>在 xamarin-android#6812

$(AndroidCodegenTarget):
https://docs.microsoft.com/en...
java.interop:
https://github.com/xamarin/Ja...
xamarin-android#6805:
https://github.com/xamarin/xa...
xamarin-android#6812:
https://github.com/xamarin/xa...

▌多维Java数组

当向Java来回传递c#数组时,两头步骤必须复制数组,以便适当的运行时可能拜访它。这真的是一个开发者体验的状况,因为c#开发者冀望写这样的货色:

var array = new int[] { 1, 2, 3, 4};MyJavaMethod (array);

在MyJavaMethod外面会做:

IntPtr native_items = JNIEnv.NewArray (items);try{    // p/invoke here, actually calls into Java}finally{    if (items != null)    {        JNIEnv.CopyArray (native_items, items); // If the calling method mutates the array        JNIEnv.DeleteLocalRef (native_items); // Delete our Java local reference    }}

JNIEnv.NewArray()拜访一个“类型映射”,以晓得须要将哪个Java类用于数组的元素。

dotnet new maui我的项目应用的特定android API有问题:

public ColorStateList (int[][]? states, int[]? colors)

发现一个多维 int[][] 数组能够拜访每个元素的“类型映射”。当启用额定的日志记录时,咱们能够看到这一点,许多实例:

monodroid: typemap: failed to map managed type to Java type: System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e (Module ID: 8e4cd939-3275-41c4-968d-d5a4376b35f5; Type token: 33554653)monodroid-assembly: typemap: called frommonodroid-assembly: at android.Runtime.JNIEnv.TypemapManagedToJava(Type )monodroid-assembly: at android.Runtime.JNIEnv.GetJniName(Type )monodroid-assembly: at android.Runtime.JNIEnv.FindClass(Type )monodroid-assembly: at android.Runtime.JNIEnv.NewArray(Array , Type )monodroid-assembly: at android.Runtime.JNIEnv.NewArray[Int32[]](Int32[][] )monodroid-assembly: at android.Content.Res.ColorStateList..ctor(Int32[][] , Int32[] )monodroid-assembly: at Microsoft.Maui.Platform.ColorStateListExtensions.CreateButton(Int32 enabled, Int32 disabled, Int32 off, Int32 pressed)

对于这种状况,咱们应该可能调用JNIEnv.FindClass()一次,并为数组中的每一项重用这个值!

咱们正在钻研如何在将来的.NET版本中进一步改良这一点。一个这样的例子是dotnet/maui#5654,在这里咱们只是简略地思考齐全用Java来创立数组。

查看xamarin-android#6870理解这个改良的详细信息。

▌为android图像应用Glide

Glide是古代android应用程序举荐的图片加载库。谷歌文档甚至举荐应用它,因为内置的android Bitmap类可能很难正确应用。glidex.forms是在Xamarin.Forms中应用Glide的原型。但咱们将 Glide 晋升为将来在 .NET MAUI 中加载图像的“形式”。
为了缩小JNI互操作的开销,.NET MAUI的Glide实现次要是用Java编写的,例如:

import com.bumptech.glide.Glide;//...public static void loadImageFromUri(ImageView imageView, String uri, Boolean cachingEnabled, ImageLoaderCallback callback) {    //...    RequestBuilder<Drawable> builder = Glide        .with(imageView)        .load(androidUri);    loadInto(builder, imageView, cachingEnabled, callback);}

ImageLoaderCallback在c#中子类化以解决托管代码中的实现。其后果是,来自web的图像的性能应该比以前在Xamarin.Forms中失去的性能有了显著进步。
详见dotnet/maui#759和dotnet/maui#5198。

▌缩小Java互操作调用

假如你有以下Java api:

public void setFoo(int foo);public void setBar(int bar);

这些办法的互操作如下:

public unsafe static void SetFoo(int foo){    JniArgumentValue* __args = stackalloc JniArgumentValue[1];    __args[0] = new JniArgumentValue(foo);    return _members.StaticMethods.InvokeInt32Method("setFoo.(I)V", __args);}public unsafe static void SetBar(int bar){    JniArgumentValue* __args = stackalloc JniArgumentValue[1];    __args[0] = new JniArgumentValue(bar);    return _members.StaticMethods.InvokeInt32Method("setBar.(I)V", __args);}

所以调用这两个办法会两次调用stackalloc,两次调用p/invoke。创立一个小型的Java包装器会更有性能,例如:

public void setFooAndBar(int foo, int bar){    setFoo(foo);    setBar(bar);}

翻译为:

public unsafe static void SetFooAndBar(int foo, int bar){    JniArgumentValue* __args = stackalloc JniArgumentValue[2];    __args[0] = new JniArgumentValue(foo);    __args[1] = new JniArgumentValue(bar);    return _members.StaticMethods.InvokeInt32Method("setFooAndBar.(II)V", __args);}

.NET MAUI视图实质上是c#对象,有很多属性须要在Java中以完全相同的形式设置。如果咱们将这个概念利用到.NET MAUI中的每个android View中,咱们能够创立一个~18参数的办法用于View创立。后续的属性更改能够间接调用规范的android api。
对于非常简单的.NET MAUI控件来说,这在性能上有了显著的进步:

请参阅dotnet/maui#3372理解无关此改良的详细信息。

▌将android XML移植到Java

回顾android上的dotnet跟踪输入,咱们能够看到正当的工夫破费在:

20.32.ms mono.andorid!Andorid.Views.LayoutInflater.Inflate

回顾堆栈跟踪,工夫实际上花在了android/Java扩大布局上,而在.NET端没有任何工作产生。
如果你看看编译过的android .apk和res/layouts/bottomtablayout。在android Studio中,XML只是一般的XML。只有多数标识符被转换为整数。这意味着android必须解析XML并通过Java的反射api创立Java对象——仿佛咱们不应用XML就能够取得更快的性能?
通过规范的BenchmarkDotNet比照,咱们发现在波及互操作时,应用android布局的体现甚至比应用c#更差:

接下来,咱们将BenchmarkDotNet配置为单次运行,以更好地模仿启动时产生的状况:
办法

咱们在.NET MAUI中看到了一个更简略的布局,底部标签导航:

<?xml version="1.0" encoding="utf-8"?><LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"  android:orientation="vertical"  android:layout_width="match_parent"  android:layout_height="match_parent">  <FrameLayout    android:id="@+id/bottomtab.navarea"    android:layout_width="match_parent"    android:layout_height="0dp"    android:layout_gravity="fill"    android:layout_weight="1" />  <com.google.android.material.bottomnavigation.BottomNavigationView    android:id="@+id/bottomtab.tabbar"    android:theme="@style/Widget.Design.BottomNavigationView"    android:layout_width="match_parent"    android:layout_height="wrap_content" /></LinearLayout>

咱们能够将其移植到四个Java办法中,例如:

@NonNullpublic static List<View> createBottomTabLayout(Context context, int navigationStyle);@NonNullpublic static LinearLayout createLinearLayout(Context context);@NonNullpublic static FrameLayout createFrameLayout(Context context, LinearLayout layout);@NonNullpublic static BottomNavigationView createNavigationBar(Context context, int navigationStyle, FrameLayout bottom)

这使得咱们在android上创立底部标签导航时只能从c#切换到Java 4次。它还容许android操作系统跳过加载和解析.xml来“收缩”Java对象。咱们在dotnet/maui中执行了这个想法,在启动时删除所有LayoutInflater.Inflate()调用。

请参阅dotnet/maui#5424, dotnet/maui#5493,和dotnet/maui#5528理解这些改良的详细信息。

▌删除Microsoft.Extensions.Hosting

hosting提供了一个.NET通用主机,用于在.NET应用程序中治理依赖注入、日志记录、配置和利用生命周期。这对启动工夫有影响,仿佛不适宜挪动应用程序。

从.NET MAUI中移除Microsoft.Extensions.Hosting应用是有意义的。. net MAUI没有试图与“通用主机”互操作来构建DI容器,而是有本人的简略实现,它针对挪动启动进行了优化。此外,. net MAUI默认不再增加日志记录提供程序。

通过这一扭转,咱们看到dotnet new maui android应用程序的启动工夫缩小了5-10%。在iOS上,它缩小了雷同应用程序的大小,从19.2 MB => 18.0 MB。

详见dotnet/maui#4505和dotnet/maui#4545。

▌在启动时缩小Shell初始化

Xamarin. Forms Shell是跨平台应用程序导航的一种模式。这个模式是在.NET MAUI中提出的,它被举荐作为构建应用程序的默认形式。

当咱们发现在启动时应用Shell的老本(对于Xamarin和Xamarin.form和.NET MAUI),咱们找到了几个能够优化的中央:

  • 不要在启动时解析路由——要等到一个须要它们的导航产生。
  • 如果没有为导航提供查问字符串,则只需跳过解决查问字符串的代码。这将删除适度应用System.Reflection的代码门路。
  • 如果页面没有可见的BottomNavigationView,那么不要设置菜单项或任何外观元素。

请参阅dotnet/maui#5262理解此改良的详细信息。

▌字体不应该应用临时文件

大量的工夫花在.NET MAUI应用程序加载字体上:

32.19ms Microsoft.Maui!Microsoft.Maui.FontManager.CreateTypeface(System.ValueTuple`3<string, Microsoft.Maui.FontWeight, bool>)

查看代码时,它所做的工作比须要的更多:
1.将androidAsset文件保留到长期文件夹。
2.应用android API, Typeface.CreateFromFile()来加载文件。

咱们实际上能够间接应用Typeface.CreateFromAsset() android API,基本不必临时文件。
请参阅dotnet/maui#4933理解无关此改良的详细信息。

▌编译时在平台上计算

{OnPlatform}标记扩大的应用:

<Label Text="Platform: " /><Label Text="{OnPlatform Default=Unknown, android=android, iOS=iOS" />

…实际上能够在编译时计算,net6.0-android和net6.0-ios会失去适当的值。在将来的.NET版本中,咱们将对 XML元素进行同样的优化。
详见dotnet/maui#4829和dotnet/maui#5611。

▌在XAML中应用编译转换器

以下类型当初在XAML编译时转换,而不是在运行时:

  • 色彩:dotnet /maui# 4687
  • 角半径: dotnet / maui # 5192
  • 字形大小:dotnet / maui # 5338
  • 网格长度, 行定义, 列定义: dotnet/maui#5489
    这导致从.xaml文件生成更好/更快的IL。

▌优化色彩解析

Microsoft.Maui.Graphics.Color.Parse()的原始代码能够重写,以更好地应用Span并防止字符串调配。


可能在ReadonlySpan<char> dotnet/csharplang#1881上应用switch语句,将在将来的.NET版本中进一步改善这种状况。

看到dotnet / Microsoft.Maui.Graphics # 343和dotnet / Microsoft.Maui.Graphics # 345对于这个改良的细节。

▌不要应用区域性辨认的字符串比拟

回顾一个新的naui我的项目的dotnet跟踪输入,能够看到android上第一个区域性感知字符串比拟的实在老本:

6.32ms Microsoft.Maui.Controls!Microsoft.Maui.Controls.ShellNavigationManager.GetNavigationState3.82ms Microsoft.Maui.Controls!Microsoft.Maui.Controls.ShellUriHandler.FormatUri3.82ms System.Private.CoreLib!System.String.StartsWith2.57ms System.Private.CoreLib!System.Globalization.CultureInfo.get_CurrentCulture

实际上,咱们甚至不心愿在本例中应用区域性比拟—它只是从Xamarin.Forms引入的代码。
例如,如果你有:

if (text.StartsWith("f")){    // do something}

在这种状况下,你能够简略地这样做:

if (text.StartsWith("f", StringComparision.Ordinal)){    // do something}

如果在整个应用程序中执行,System.Globalization.CultureInfo.CurrentCulture能够防止被调用,并且能够略微进步If语句的总体速度。

为了解决整个dotnet/maui回购的这种状况,咱们引入了代码剖析规定来捕获这些:

dotnet_diagnostic.CA1307.severity = errordotnet_diagnostic.CA1309.severity = error

请参阅dotnet/maui#4988理解无关改良的详细信息。

▌懈怠地创立日志

ConfigureFonts() API在启动时破费了一些工夫来做一些能够提早到当前的工作。咱们还能够改良Microsoft.Extensions中日志基础设施的个别用法。
咱们所做的一些改良如下:

  • 推延创立“记录器”类,直到须要它们时再创立。
  • 内置的日志记录基础设施在默认状况下是禁用的,必须显式启用。
  • 提早调用android的EmbeddedFontLoader中的Path.GetTempPath(),直到须要它。
  • 不要应用ILoggerFactory创立通用记录器。而是间接获取ILogger服务,这样它就被缓存了。
    请参阅dotnet/maui#5103理解无关此改良的详细信息。

▌应用工厂办法进行依赖注入

当应用Microsoft.Extensions。DependencyInjection,注册服务,比方:

IServiceCollection services /* ... */;services.TryAddSingleton<IFooService, FooService>();

Microsoft.Extensions必须做一些System.Reflection来创立FooService的第一个实例。这是值得注意的dotnet跟踪输入在android上。
相同,如果你这样做了:

// If FooService has no dependenciesservices.TryAddSingleton<IFooService>(sp => new FooService());// Or if you need to retrieve some dependenciesservices.TryAddSingleton<IFooService>(sp => new FooService(sp.GetService<IBar>()));

在这种状况下,Microsoft.Extensions能够简略地调用lamdba/匿名办法,而不须要零碎。反射。
咱们在所有的dotnet/maui上进行了改良,并应用了bannedapianalyzer,这样就不会有人意外地应用TryAddSingleton()更慢的重载。
请参阅dotnet/maui#5290理解无关此改良的详细信息。

▌懈怠地负载ConfigurationManager

configurationmanager并没有被许多挪动应用程序应用,而且创立一个是十分低廉的!(例如,在android上约为7.59ms)

在.NET MAUI中,一个ConfigurationManager在启动时默认被创立,咱们能够应用Lazy提早它的创立,所以它将不会被创立,除非申请。

请参阅dotnet/maui#5348理解无关此改良的详细信息。

▌默认VerifyDependencyInjectionOpenGenericServiceTrimmability

.NET Podcast样本破费了4-7ms的工夫:

Microsoft.Extensions.DependencyInjection.ServiceLookup.CallsiteFactory.ValidateTrimmingAnnotations()

MSBuild属性$(verifydependencyinjectionopengenericservicetrimability)触发该办法运行。这个个性开关确保dynamallyaccessedmembers被正确地利用于关上依赖注入中的泛型类型。

在根底.NET SDK中,当publishtrim =true时,该开关将被启用。然而,android应用程序在Debug版本中并没有设置publishtrim =true,所以开发者错过了这个验证。

相同,在已公布的应用程序中,咱们不想领取这种验证的老本。所以这个个性开关应该在Release版本中敞开。

查看xamarin-android#6727和xamarin-macios#14130理解对于这个改良的详细信息。

▌改良内置AOT配置文件

Mono运行时有一个对于每个办法的JIT工夫的报告(参见咱们的文档),例如:

Total(ms) | Self(ms) | Method     3.51 |     3.51 | Microsoft.Maui.Layouts.GridLayoutManager/GridStructure:.ctor (Microsoft.Maui.IGridLayout,double,double)     1.88 |     1.88 | Microsoft.Maui.Controls.Xaml.AppThemeBindingExtension/<>c__DisplayClass20_0:<Microsoft.Maui.Controls.Xaml.IMarkupExtension<Microsoft.Maui.Controls.BindingBase>.ProvideValue>g__minforetriever|0 ()     1.66 |     1.66 | Microsoft.Maui.Controls.Xaml.OnIdiomExtension/<>c__DisplayClass32_0:<ProvideValue>g__minforetriever|0 ()     1.54 |     1.54 | Microsoft.Maui.Converters.ThicknessTypeConverter:ConvertFrom (System.ComponentModel.ITypeDescriptorContext,System.Globalization.CultureInfo,object)

这是一个应用Profiled AOT的版本构建中.NET Podcast示例中的顶级jit工夫抉择。这些仿佛是开发人员心愿在. net MAUI应用程序中应用的罕用api。
为了确保这些办法在AOT配置文件中,咱们在dotnet/maui中应用了这些api

_=new Microsoft.Maui.Layouts.GridLayoutManager(new Grid()).Measure(100, 100);<SolidColorBrush x:Key="ProfiledAot_AppThemeBinding_Color" Color="{AppThemeBinding Default=Black}"/><CollectionView x:Key="ProfiledAot_CollectionView_OnIdiom_Thickness" Margin="{OnIdiom Default=1,1,1,1}" />

在这个测试应用程序中调用这些办法能够确保它们位于内置的. net MAUI AOT配置文件中。
在这个更改之后,咱们看了一个更新的JIT报告:

Total (ms) |  Self (ms) | Method      2.61 |       2.61 | string:SplitInternal (string,string[],int,System.StringSplitOptions)      1.57 |       1.57 | System.Number:NumberToString (System.Text.ValueStringBuilder&,System.Number/NumberBuffer&,char,int,System.Globalization.NumberFormatInfo)      1.52 |       1.52 | System.Number:TryParseInt32IntegerStyle (System.ReadOnlySpan`1<char>,System.Globalization.NumberStyles,System.Globalization.NumberFormatInfo,int&)

这导致了进一步的补充:

var split = "foo;bar".Split(';');var x = int.Parse("999");x.ToString();

咱们对Color.Parse()、Connectivity做了相似的批改.NETworkAccess DeviceInfo。成语,AppInfo。.NET MAUI应用程序中应该常常应用的requestdtheme。

请参阅dotnet/maui#5559, dotnet/maui#5682,和dotnet/maui#6834理解这些改良的详细信息。

如果你想在.NET 6中记录一个自定义的AOT配置文件,你能够尝试咱们的试验包Mono.Profiler.Android。咱们正在致力在将来的.NET版本中齐全反对记录自定义概要文件。

▌启用AOT图像的提早加载

以前,Mono运行时将在启动时加载所有AOT图像,以验证托管.NET程序集(例如Foo.dll)的MVID是否与AOT图像(libFoo.dll.so)匹配。在大多数.NET应用程序中,一些AOT映像可能稍后才须要加载。
Mono中引入了一个新的——aot-lazy-assembly-load或mono_opt_aot_lazy_assembly_load设置,android工作负载能够抉择。咱们发现这将dotnet new maui我的项目在Pixel 6 Pro上的启动工夫进步了约25ms。

这是默认启用的,但如果须要,你能够在你的。csproj中通过以下形式禁用此设置:

<AndroidAotEnableLazyLoad>false</AndroidAotEnableLazyLoad>

查看dotnet/runtime#67024和xamarin-android #6940理解这些改良的详细信息。

▌删除System.Uri中未应用的编码对象

一个MAUI应用程序的dotnet跟踪输入,显示大概7ms破费了加载UTF32和Latin1编码的第一次零碎。应用Uri api:

namespace System{    internal static class UriHelper    {        internal static readonly Encoding s_noFallbackCharUTF8 = Encoding.GetEncoding(            Encoding.UTF8.CodePage, new EncoderReplacementFallback(""), new DecoderReplacementFallback(""));

这个字段是不小心留在原地的。只需删除s_noFallbackCharUTF8字段,就能够改良任何应用System.Uri 或相干的api的. net应用程序的启动。

参见dotnet/runtime#65326理解无关此改良的详细信息。

应用程序大小的改良

▌修复默认的MauiImage大小

dotnet new maui模板显示一个敌对的"网络机器人”的形象。这是通过应用一个.svg文件作为一个MauiImage和内容来实现的:

<svg width="419" height="519" viewBox="0 0 419 519" fill="none" xmlns="http://www.w3.org/2000/svg"><!-- everything else -->

默认状况下,MauiImage应用.svg中的宽度和高度值作为图像的“根底大小”。回顾构建输入,这些图像被缩放为:

objReleasenet6.0-androidresizetizerrmipmap-xxxhdpi    appiconfg.png = 1824x1824    dotnet_bot.png = 1676x2076

这对于android设施来说仿佛有点太大了?咱们能够简略地在模板中指定%(BaseSize),它还提供了一个如何为这些图像抉择适合大小的示例:

<!-- 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

咱们还能够批改.svg内容,但这可能不可取,这取决于图形设计师如何在其余设计工具中应用该图像。

在另一个例子中,一个3008×5340 .jpg图像:

<MauiImage Include="Resources\Images\large.jpg" />

正在降级到21360×12032!设置Resize="false"将避免图像被调整大小,但咱们将此设置为非矢量图像的默认选项。接下来,开发人员应该可能依赖默认值,或者依据须要指定%(根本尺寸)和%(调整大小)。

这些扭转改善了启动性能和应用程序的大小。请参阅dotnet/maui#4759和dotnet/maui#6419理解这些改良的细节。

▌删除Application.Properties 和DataContractSerializer

Xamarin.Forms 有一个 API,用于通过 Application.Properties 字典长久化键值对。这在外部应用了DataContractSerializer,这对于自蕴含和修剪的挪动应用程序不是最佳抉择。来自BCL的System.Xml的局部可能相当大,咱们不想在每个.NET MAUI应用程序中都为此付出代价。

简略地删除这个API和所有DataContractSerializer的应用,在android上能够进步约855KB,在iOS上进步约1MB。

请参阅dotnet/maui#4976理解无关此改良的详细信息。

▌修剪未应用的HTTP实现

System.NET.Http.UseNativeHttpHandler没有适当地削减底层托管HTTP处理程序(SocketsHttpHandler)。默认状况下,androidMessageHandler和NSUrlSessionHandler被用来利用底层的android和iOS网络栈。
通过修改这个问题,在任何.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

查看dotnet/runtime#64852, xamarin-android#6749,和xamarin-macios#14297对于这个改良的详细信息。

.NET Podcast示例中的改良

咱们对样本自身做了一些调整,其中更改被认为是“最佳实际”。

▌删除Microsoft.Extensions.Http用法

应用Microsoft.Extensions.Http对于挪动应用程序来说太重了,并且在这种状况下没有提供任何真正的价值。
因而,HttpClient不应用DI:

builder.Services.AddHttpClient<ShowsService>(client => {    client.BaseAddress = new Uri(Config.APIUrl);});// Then in the service ctorpublic 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理解无关改良的详细信息。

▌删除Newtonsoft.Json应用

.NET Podcast 样本应用了一个名为MonkeyCache的库,它依赖于Newtonsoft.Json。这自身并不是一个问题,只是.NET MAUI + Blazor应用程序依赖于一些ASP.NET Core库反过来依赖于System.Text.Json。这款利用实际上是为JSON解析库“付了两倍钱”,这对利用的大小产生了影响。

咱们移植了MonkeyCache 2.0来应用System.Text。Json,不须要Newtonsoft。这将iOS上的利用大小从29.3MB缩小到26.1MB!
参见monkey-cache#109和dotnet-podcasts#58理解无关改良的详细信息。

▌在后盾运行第一个网络申请

回顾dotnet跟踪输入,初始申请在ShowsService阻塞UI线程初始化连贯.NETworkAccess Barrel.Current。失去,HttpClient。这项工作能够在后盾线程中实现-在这种状况下导致更快的启动工夫。在Task.Run()中封装第一个调用,能够在肯定水平上进步这个示例的启动效率。

在Pixel 5a设施上均匀运行10次:

BeforeAverage(ms): 843.7Average(ms): 847.8AfterAverage(ms): 817.2Average(ms): 812.8

对于这种类型的更改,总是倡议依据dotnet跟踪或其余剖析后果来做出决定,并度量更改前后的变动。

请参阅dotnet-podcasts#57理解无关此改良的详细信息。

实验性或高级选项

如果你想在android上进一步优化你的.NET MAUI应用程序,这里有一些高级或实验性的个性,默认状况下不是启用的。

▌修剪Resource.designer.cs

自从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理解该性能的详细信息。

▌R8 Java代码膨胀器

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文档。

▌AOT

Profiled AOT是默认的,因为它在应用程序大小和启动性能之间给出了最好的衡量。如果应用程序的大小与你的应用程序无关,你能够思考对所有.NET程序集应用AOT。

要抉择退出,在你的.csproj中增加以下Release配置:

<PropertyGroup Condition="'$(Configuration)' == 'Release'">  <RunAOTCompilation>true</RunAOTCompilation>  <androidEnableProfiledAot>false</androidEnableProfiledAot></PropertyGroup>

这将缩小在应用程序启动期间产生的JIT编译量,以及导航到前面的屏幕等。

▌AOT和LLVM

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的文档。

LLVM:

EnableLLVM的文档:

▌记录自定义AOT配置文件

概要AOT默认应用咱们在.NET MAUI和android工作负载中提供的“内置”概要文件,对大多数应用程序都很有用。为了获得最佳的启动性能,现实状况下应该记录应用程序特定的配置文件。针对这种状况,咱们有一个实验性的Mono.Profiler.Android包。

记录配置文件:

dotnet add package Mono.AotProfiler.androiddotnet build -t:BuildAndStartAotProfiling# Wait until app launches, or you navigate to a screendotnet build -t:FinishAotProfiling

这将在你的我的项目目录下产生一个custom.aprof。要在将来的构建中应用它:

<ItemGroup>  <androidAotProfile Include="custom.aprof" /></ItemGroup>

咱们正在致力在将来的.NET版本中齐全反对记录自定义概要文件。

论断

我心愿您喜爱咱们的.NET MAUI性能阐述。请尝试.NET MAUI并且能够在http://dot.net/maui理解更多!



长按辨认二维码
关注微软中国MSDN

点击获取学习资源~