.NET多平台应用程序UI (MAUI)将android、iOS、macOS和Windows API统一为一个API,这样你就可以编写一个应用程序在许多平台上本机运行。我们专注于提高您的日常生产力以及您的应用程序的性能。我们认为,开发人员生产率的提高不应该以应用程序性能为代价。
应用程序的大小也是如此——在一个空白的.NET MAUI应用程序中存在什么开销?当我们开始优化.NET MAUI时,很明显iOS需要做一些工作来改善应用程序的大小,而android则缺乏启动性能。.
一个dotnet new maui项目的iOS应用程序最初大约是18MB。同样,在之前的预览中.NET MAUI在android上的启动时间也不是很理想:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
这是在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代码,等等方面。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**这是原始的dotnet new maui模板,没有使用Shell。
内容十分丰富,来看是否有您期待的更新吧!
-
.NET Podcast:
https://github.com/microsoft/dotnet-podcasts
-
maui-profiling:
https://github.com/jonathanpeppers/maui-profiling
-
Shell:
https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/?ocid=AID3045631
-
.NET Podcast App (Shell):
https://github.com/microsoft/dotnet-podcasts
主要内容
-
在移动设备上进行分析
-
测量随着时间的推移
-
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中未使用的编码对象
启动性能的改进
▌在移动设备上进行分析
adb reverse tcp:9000 tcp:9001
adb 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-trace collect --diagnostic-port /tmp/maui-app --format speedscope
Press <Enter> or <Ctrl+C> to exit...812 (KB)
在您的应用程序完全启动后,只需按下enter键就可以得到一个保存在当前目录的*.speedscope。你可以在https://speedscope.app上打开这个文件,深入了解每个方法在应用程序启动期间所花费的时间:
在android应用程序中使用dotnet跟踪的更多细节,请参阅我们的文档。我建议在android设备上分析Release版本,以获得应用在现实世界中的最佳表现。
-
在移动设备上进行分析: https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#profiling-on-mobile?ocid=AID3045631 -
dotnet-dsrouter: https://docs.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dsrouter?ocid=AID3045631 -
我们的文档: https://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/tracing.md
▌测量随着时间的推移
-
包大小 -
磁盘大小(未压缩) -
单个文件分类 -
应用程序启动
随着时间的推移,这使我们能够看到改进或回归的影响,看到dotnet/maui回购的每个提交的数字。我们还可以确定这种差异是否是由xamarin-android、xamarin-macios或dotnet/runtime中的变化引起的。
请参阅dotnet-podcasts#58, Android x# 520和dotnet/maui#6419了解这些改进的详细信息。
-
测量随着时间的推移:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#measuring-over-time?ocid=AID3045631
-
.NET Podcas: https://github.com/microsoft/dotnet-podcasts -
dotnet-podcasts#58: https://github.com/microsoft/dotnet-podcasts/pull/58 -
Android x# 520: https://github.com/xamarin/AndroidX/pull/520 -
dotnet/maui#6419: https://github.com/dotnet/maui/pull/6419
▌异形AOT
|
|
|
|
|
|
每次调用c#方法时都会发生JIT处理,这会隐式地影响移动应用程序的启动性能。
另一个问题是AOT导致的应用程序大小增加。每个.NET程序集都会在最终应用中添加一个android本地库。为了更好地利用这两个世界,启动跟踪或分析AOT是Xamarin.Android当前的一个特性。这是一种AOT应用程序启动路径的机制,它显著提高了启动时间,而只增加了适度的应用程序大小。
查看xamarin-Android#6547和dotnet/maui#4859了解这个改进的细节。
-
Profiled AOT:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#profiled-aot?ocid=AID3045631
-
启动跟踪或分析AOT:
https://devblogs.microsoft.com/xamarin/faster-startup-times-with-startup-tracing-on-android/?ocid=AID3045631
-
Mono.Profiler. Android:
https://github.com/jonathanpeppers/Mono.Profiler.Android
-
xamarin-Android#6547:
https://github.com/xamarin/xamarin-android/pull/6547
-
dotnet/maui#4859:
https://github.com/dotnet/maui/pull/4859
▌单文件程序集存储器
assemblies/Java.Interop.dll
assemblies/Mono.android.dll
assemblies/System.Runtime.dll
assemblies/arm64-v8a/System.Private.CoreLib.dll
assemblies/armeabi-v7a/System.Private.CoreLib.dll
assemblies/x86/System.Private.CoreLib.dll
assemblies/x86_64/System.Private.CoreLib.dll
assemblies/assemblies.manifest
assemblies/assemblies.blob
assemblies/assemblies.arm64_v8a.blob
assemblies/assemblies.armeabi_v7a.blob
assemblies/assemblies.x86.blob
assemblies/assemblies.x86_64.blob
现在android启动只需要调用mmap两次:一次是assemblies.blob,第二次是特定于体系结构的Blob。这对带有许多. net程序集的应用程序产生了明显的影响。
如果你需要检查编译过的android应用程序中这些程序集的IL,我们创建了一个程序集存储读取器工具来“解包”这些文件。
dotnet build -c Release -p:AndroidUseAssemblyStore=false -p:Android EnableAssemblyCompression=false
查看xamarin-android#6311了解关于这个改进的详细信息。
-
单文件程序集存储器:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#single-file-assembly-stores
-
mmap系统调用:
https://man7.org/linux/man-pages/man2/mmap.2.html
-
mmap:
https://man7.org/linux/man-pages/man2/mmap.2.html
-
程序集存储读取器:
https://github.com/xamarin/xamarin-android/tree/main/tools/assembly-store-reader
-
ILSpy:
https://github.com/icsharpcode/ILSpy
-
xamarin-android#6311:
https://github.com/xamarin/xamarin-android/pull/6311
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);
}
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版本中重新构建它,我们有一些想法。
-
Spanify.RegisterNativeMembers:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#spanify-registernativemembers?ocid=AID3045631
-
Java本机接口(JNI): https://en.wikipedia.org/wiki/Java_Native_Interface -
Span: https://docs.microsoft.com/en-us/archive/msdn-magazine/2018/january/csharp-all-about-span-exploring-a-new-net-mainstay?ocid=AID3045631 -
xamarin-android#6708: https://github.com/xamarin/xamarin-android/pull/6708https://github.com/xamarin/xamarin-android/pull/6708
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)!;
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);
-
System.Reflection.Emit和构造函数:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#systemreflectionemit-and-constructors?ocid=AID3045631
-
xamarin-android#6766: https://github.com/xamarin/xamarin-android/pull/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 assembly
internal 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)
{
// ...
}
}
查看xamarin-android#6657和xamarin-android#6707了解这个改进的详细信息。
-
System.Reflection.Emit和方法: https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#systemreflectionemit-and-methods?ocid=AID3045631
-
xamarin-android#6657:
https://github.com/xamarin/xamarin-android/pull/6657
-
xamarin-android#6707:
https://github.com/xamarin/xamarin-android/pull/6707
▌更新的Java.Interop APIs
-
JNIEnv.CallStaticObjectMethod
-
JniEnvironment.StaticMethods.CallStaticObjectMethod
当在构建时为Java方法生成c#绑定时,默认使用更新/更快的方法—在Xamarin.Android中已经有一段时间了。以前,Java绑定项目可以将$(AndroidCodegenTarget)设置为XAJavaInterop1,它在每次调用中缓存和重用jmethodID实例。请参阅java.interop文档获取关于该特性的历史记录。
其他有问题的地方是有“手动”绑定的地方。这些往往也是经常使用的方法,所以值得修复这些!
-
JNIEnv.FindClass()在xamarin-android#6805
-
JavaList 和 JavaList<T>在 xamarin-android#6812
-
更新的Java.Interop APIs: https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#multi-dimensional-java-arrays?ocid=AID3045631 -
$(AndroidCodegenTarget): https://docs.microsoft.com/en-us/xamarin/android/deploy-test/building-apps/build-properties#androidcodegentarget?ocid=AID3045631 -
java.interop: https://github.com/xamarin/Java.Interop/commit/d9b43b52a2904e00b74b96c82a7c62c6a0c214ca -
xamarin-android#6805: https://github.com/xamarin/xamarin-android/pull/6805 -
xamarin-android#6812: https://github.com/xamarin/xamarin-android/pull/6812
当向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 from
monodroid-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了解这个改进的详细信息。
-
多维Java数组: https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#multi-dimensional-java-arrays?ocid=AID3045631 -
dotnet/maui#5654:
https://github.com/dotnet/maui/pull/5654
-
xamarin-android#6870:
https://github.com/xamarin/xamarin-android/pull/6870
▌为android图像使用Glide
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);
}
详见dotnet/maui#759和dotnet/maui#5198。
-
为android图像使用Glide: https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#multi-dimensional-java-arrays?ocid=AID3045631 -
Glide:
https://github.com/bumptech/glide
-
glidex.forms:
https://github.com/jonathanpeppers/glidex
-
dotnet/maui#759:
https://github.com/dotnet/maui/pull/759
-
dotnet/maui#5198:
https://github.com/dotnet/maui/pull/5198
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);
}
{
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);
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
请参阅dotnet/maui#3372了解有关此改进的详细信息。
-
减少Java互操作调用: https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#reduce-java-interop-calls?ocid=AID3045631 -
dotnet/maui#3372: https://github.com/dotnet/maui/pull/3372
20.32.ms mono.andorid!Andorid.Views.LayoutInflater.Inflate
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<?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>
@NonNull
public static List<View> createBottomTabLayout(Context context, int navigationStyle);
@NonNull
public static LinearLayout createLinearLayout(Context context);
@NonNull
public static FrameLayout createFrameLayout(Context context, LinearLayout layout);
@NonNull
public 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了解这些改进的详细信息。
-
将android XML移植到Java:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#port-android-xml-to-java?ocid=AID3045631
-
dotnet/maui#5424:
https://github.com/dotnet/maui/pull/5424
-
dotnet/maui#5493:
https://github.com/dotnet/maui/pull/5493
-
dotnet/maui#5528:
https://github.com/dotnet/maui/pull/5528
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。
-
删除Microsoft.Extensions.Hosting:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#remove-microsoftextensionshosting?ocid=AID3045631
-
.NET通用主机:
https://docs.microsoft.com/en-us/dotnet/core/extensions/generic-host?ocid=AID3045631
-
dotnet/maui#4505:
https://github.com/dotnet/maui/pull/4505
-
dotnet/maui#4545:
https://github.com/dotnet/maui/pull/4545
Xamarin. Forms Shell是跨平台应用程序导航的一种模式。这个模式是在.NET MAUI中提出的,它被推荐作为构建应用程序的默认方式。
当我们发现在启动时使用Shell的成本(对于Xamarin和Xamarin.form和.NET MAUI),我们找到了几个可以优化的地方:
-
不要在启动时解析路由——要等到一个需要它们的导航发生。 -
如果没有为导航提供查询字符串,则只需跳过处理查询字符串的代码。这将删除过度使用System.Reflection的代码路径。 -
如果页面没有可见的BottomNavigationView,那么不要设置菜单项或任何外观元素。
请参阅dotnet/maui#5262了解此改进的详细信息。
-
在启动时减少Shell初始化:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#less-shell-initialization-on-startup?ocid=AID3045631
-
Xamarin. Forms Shell:
https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/?ocid=AID3045631
-
dotnet/maui#5262:
https://github.com/dotnet/maui/pull/5262
32.19ms Microsoft.Maui!Microsoft.Maui.FontManager.CreateTypeface(System.ValueTuple`3<string, Microsoft.Maui.FontWeight, bool>)
请参阅dotnet/maui#4933了解有关此改进的详细信息。
-
字体不应该使用临时文件: https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#fonts-should-not-use-temporary-files?ocid=AID3045631 -
dotnet/maui#4933: https://github.com/dotnet/maui/pull/4933
<Label Text="Platform: " />
<Label Text="{OnPlatform Default=Unknown, android=android, iOS=iOS" />
详见dotnet/maui#4829和dotnet/maui#5611。
-
编译时在平台上计算: https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#compute-onplatform-at-compile-time?ocid=AID3045631 -
dotnet/maui#4829:
https://github.com/dotnet/maui/pull/4829
-
dotnet/maui#5611:
https://github.com/dotnet/maui/pull/5611
-
颜色:dotnet /maui# 4687 -
角半径: dotnet / maui # 5192 -
字形大小:dotnet / maui # 5338 -
网格长度, 行定义, 列定义: dotnet/maui#5489
-
在XAML中使用编译转换器: https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#use-compiled-converters-in-xaml?ocid=AID3045631 -
dotnet /maui# 4687:
https://github.com/dotnet/maui/pull/4687
-
dotnet / maui # 5192:
https://github.com/dotnet/maui/pull/5192
-
dotnet / maui # 5338:
https://github.com/dotnet/maui/pull/5338
-
dotnet/maui#5489:
https://github.com/dotnet/maui/pull/5489
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
看到dotnet / Microsoft.Maui.Graphics # 343和dotnet / Microsoft.Maui.Graphics # 345关于这个改进的细节。
-
优化颜色解析: https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#optimize-color-parsing?ocid=AID3045631 -
dotnet/csharplang#1881:
https://github.com/dotnet/csharplang/issues/1881
-
dotnet / Microsoft.Maui.Graphics # 343:
https://github.com/dotnet/Microsoft.Maui.Graphics/pull/343
-
dotnet / Microsoft.Maui.Graphics # 345:
https://github.com/dotnet/Microsoft.Maui.Graphics/pull/345
6.32ms Microsoft.Maui.Controls!Microsoft.Maui.Controls.ShellNavigationManager.GetNavigationState
3.82ms Microsoft.Maui.Controls!Microsoft.Maui.Controls.ShellUriHandler.FormatUri
3.82ms System.Private.CoreLib!System.String.StartsWith
2.57ms System.Private.CoreLib!System.Globalization.CultureInfo.get_CurrentCulture
if (text.StartsWith("f"))
{
// do something
}
if (text.StartsWith("f", StringComparision.Ordinal))
{
// do something
}
dotnet_diagnostic.CA1307.severity = error
dotnet_diagnostic.CA1309.severity = error
请参阅dotnet/maui#4988了解有关改进的详细信息。
-
不要使用区域性识别的字符串比较: https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#dont-use-culture-aware-string-comparisons?ocid=AID3045631 -
dotnet/maui#4988:
https://github.com/dotnet/maui/pull/4988
-
推迟创建“记录器”类,直到需要它们时再创建。 -
内置的日志记录基础设施在默认情况下是禁用的,必须显式启用。 -
延迟调用android的EmbeddedFontLoader中的Path.GetTempPath(),直到需要它。 -
不要使用ILoggerFactory创建通用记录器。而是直接获取ILogger服务,这样它就被缓存了。
请参阅dotnet/maui#5103了解有关此改进的详细信息。
-
懒惰地创建日志:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#create-loggers-lazily?ocid=AID3045631
-
dotnet/maui#5103: https://github.com/dotnet/maui/pull/5103
IServiceCollection services /* ... */;
services.TryAddSingleton<IFooService, FooService>();
// If FooService has no dependencies
services.TryAddSingleton<IFooService>(sp => new FooService());
// Or if you need to retrieve some dependencies
services.TryAddSingleton<IFooService>(sp => new FooService(sp.GetService<IBar>()));
请参阅dotnet/maui#5290了解有关此改进的详细信息。
-
使用工厂方法进行依赖注入: https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#use-factory-methods-for-dependency-injection?ocid=AID3045631 -
bannedapianalyzer:
https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md
-
dotnet/maui#5290:
https://github.com/dotnet/maui/pull/5290
configurationmanager并没有被许多移动应用程序使用,而且创建一个是非常昂贵的!(例如,在android上约为7.59ms)
在.NET MAUI中,一个ConfigurationManager在启动时默认被创建,我们可以使用Lazy延迟它的创建,所以它将不会被创建,除非请求。
请参阅dotnet/maui#5348了解有关此改进的详细信息。
-
懒惰地负载ConfigurationManager:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#load-configurationmanager-lazily?ocid=AID3045631
-
dotnet/maui#5348:
https://github.com/dotnet/maui/pull/5348
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallsiteFactory.ValidateTrimmingAnnotations()
MSBuild属性$(verifydependencyinjectionopengenericservicetrimability)触发该方法运行。这个特性开关确保dynamallyaccessedmembers被正确地应用于打开依赖注入中的泛型类型。
在基础.NET SDK中,当publishtrim =true时,该开关将被启用。然而,android应用程序在Debug版本中并没有设置publishtrim =true,所以开发者错过了这个验证。
相反,在已发布的应用程序中,我们不想支付这种验证的成本。所以这个特性开关应该在Release版本中关闭。
查看xamarin-android#6727和xamarin-macios#14130了解关于这个改进的详细信息。
-
默认VerifyDependencyInjectionOpenGenericServiceTrimmability:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#default-verifydependencyinjectionopengenericservicetrimmability?ocid=AID3045631
-
.NET Podcast:
https://github.com/microsoft/dotnet-podcasts
-
xamarin-android#6727:
https://github.com/xamarin/xamarin-android/pull/6727
-
xamarin-macios#14130:
https://github.com/xamarin/xamarin-macios/pull/14130
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)
_=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}" />
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配置文件:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#improve-the-built-in-aot-profile?ocid=AID3045631
-
参见我们的文档:
https://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/profiling.md#profiling-the-jit-compiler
-
.NET Podcast:
https://github.com/microsoft/dotnet-podcasts
-
dotnet/maui#5559:
https://github.com/dotnet/maui/pull/5559
-
dotnet/maui#5682:
https://github.com/dotnet/maui/pull/5682
-
dotnet/maui#6834:
https://github.com/dotnet/maui/pull/6834
-
Mono.Profiler.Android:
https://github.com/jonathanpeppers/Mono.Profiler.Android
<AndroidAotEnableLazyLoad>false</AndroidAotEnableLazyLoad>
查看dotnet/runtime#67024和xamarin-android #6940了解这些改进的详细信息。
-
启用AOT图像的延迟加载: https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#enable-lazy-loading-of-aot-images?ocid=AID3045631 -
dotnet/runtime#67024:
https://github.com/dotnet/runtime/pull/67024
-
xamarin-android #6940:
https://github.com/xamarin/xamarin-android/pull/6940
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了解有关此改进的详细信息。
-
删除System.Uri中未使用的编码对象:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#remove-unused-encoding-object-in-systemuri?ocid=AID3045631
-
dotnet/runtime#65326:
https://github.com/dotnet/runtime/pull/65326