当前位置:网站首页>. Net Maui performance improvement
. Net Maui performance improvement
2022-07-07 11:30:00 【Dotnet cross platform】
Click on the blue words
Pay attention to our
author :Jonathan Peppers
translate :Yijing Sun
proofread :Amy Peng
Typesetting :Rani Sun
Highlights
* This article is full of dry goods , Estimated reading time 32 minute , It is recommended to save .
.NET Multi platform applications UI (MAUI) take android、iOS、macOS and Windows API Unify into one API, This way you can write an application that runs natively on many platforms . We focus on improving your daily productivity and the performance of your applications . We think , Developer productivity gains should not come at the expense of application performance .
The same is true for the size of the application —— In a blank .NET MAUI What overhead exists in the application ? When we start optimizing .NET MAUI when , Obviously iOS Something needs to be done to improve the size of the application , and android Lack of startup performance .
One dotnet new maui Project iOS The initial application is about 18MB. Again , In the previous Preview .NET MAUI stay android The startup time on the is not ideal :
Applications | frame | Starting time (ms) |
Xamarin.Android | Xamarin | 306.5 |
Xamarin.Forms | Xamarin | 498.6 |
Xamarin.Forms (Shell) | Xamarin | 817.7 |
dotnet new android | .NET 6 ( Early preview ) | 210.5 |
dotnet new maui | .NET 6 ( Early preview ) | 683.9 |
.NET Podcast | .NET 6 ( Early preview ) | 1299.9 |
This is Pixel 5 Average operation on the equipment 10 The results obtained this time . About how these figures were obtained , Please refer to our maui-profiling file .
Our goal is to make .NET MAUI More than its predecessor Xamarin faster . Obviously , We are .NET MAUI I also have some work to do .dotnet new android The publishing speed of the template has exceeded Xamarin.Android, Mainly because .NET 6 In the new BCL and Mono Runtime .
new .NET maui The template has not been used yet Shell Navigation mode , But the plan is to make it .NET maui Default navigation mode for . When we adopt this change , We know that it will affect the performance in the template .
The cooperation of several different teams has made today's achievements . We improved Microsoft.Extensions , The use of dependency injection ,AOT compile ,Java interoperability ,XAML,.NET MAUI Code , And so on .
When the dust has settled , We have reached a better stage :
Applications | frame | Starting time (ms) |
Xamarin.Android | Xamarin | 306.5 |
Xamarin.Forms | Xamarin | 498.6 |
Xamarin.Forms (Shell) | Xamarin | 817.7 |
dotnet new android | .NET 6 (MAUI GA) | 182.8 |
dotnet new maui (No Shell**) | .NET 6 (MAUI GA) | 464.2 |
dotnet new maui (Shell) | .NET 6 (MAUI GA) | 568.1 |
.NET Podcast App (Shell) | .NET 6 (MAUI GA) | 814.2 |
** - It's primitive dotnet new maui Templates , Not used Shell.
The following details , Enjoy it !
.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/
.NET Podcast App (Shell)
https://github.com/microsoft/dotnet-podcasts
primary coverage
Improved startup performance
Analysis on mobile devices
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#profiling-on-mobile
Measure over time
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#measuring-over-time
Profiled AOT
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#profiled-aot
Single file assembly memory
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#single-file-assembly-stores
Spanify.RegisterNativeMembers
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#spanify-registernativemembers
System.Reflection.Emit And constructor
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#systemreflectionemit-and-constructors
System.Reflection.Emit And methods
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#systemreflectionemit-and-methods
The updated Java.Interop APIs
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#multi-dimensional-java-arrays
Multidimensional Java Array
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#multi-dimensional-java-arrays
by android Image use Glide
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#multi-dimensional-java-arrays
Reduce Java Interop calls
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#reduce-java-interop-calls
take android XML Migration to Java
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#port-android-xml-to-java
Delete Microsoft.Extensions.Hosting
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#remove-microsoftextensionshosting
Reduce... At startup Shell initialization
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#less-shell-initialization-on-startup
Fonts should not use temporary files
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#fonts-should-not-use-temporary-files
Calculate on the platform at compile time
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#compute-onplatform-at-compile-time
stay XAML Using the compiler converter in
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#use-compiled-converters-in-xaml
Optimize color resolution
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#optimize-color-parsing
Do not use culture - aware string comparisons
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#dont-use-culture-aware-string-comparisons
Lazily create logs
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#create-loggers-lazily
Using factory methods for dependency injection
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#use-factory-methods-for-dependency-injection
Load idly ConfigurationManager
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#create-loggers-lazily
Default VerifyDependencyInjectionOpenGenericServiceTrimmability
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#default-verifydependencyinjectionopengenericservicetrimmability
Improved built-in AOT The configuration file
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#improve-the-built-in-aot-profile
Enable AOT Delayed loading of images
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#enable-lazy-loading-of-aot-images
Delete System.Uri Encoding objects not used in
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#remove-unused-encoding-object-in-systemuri
Application size improvements
Fix the default MauiImage size
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#fix-defaults-for-mauiimage-sizes
Delete Application.Properties and DataContractSerializer
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#remove-applicationproperties-and-datacontractserializer
Trim unused HTTP Realization
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#trim-unused-http-implementations
.NET Podcast Improvements in the example (https://github.com/microsoft/dotnet-podcasts)
Delete Microsoft.Extensions.Http usage
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#remove-microsoftextensionshttp-usage
Delete Newtonsoft.Json Use
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#remove-newtonsoftjson-usage
Run the first network request in the background
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#run-first-network-request-in-background
Experimental or advanced options
trim Resource.designer.cs
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#trimming-resourcedesignercs
R8 Java Code shrinker
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#r8-java-code-shrinker
AOT everything
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#aot-everything
AOT and LLVM
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#aot-and-llvm
Record custom AOT The configuration file
https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#record-a-custom-aot-profile
Improved startup performance
Analysis on mobile devices
I must mention what is available on mobile platforms .NET Diagnostic tools , Because it is what we make .NET MAUI Faster first 0 Step .
analysis .NET 6 android The application needs to use a program called dotnet-dsrouter Tools for . This tool makes dotnet Track connections to a running mobile application in android, iOS etc. . This may be what we use to analyze .NET MAUI The most influential tool of .
To start using dotnet trace and dsrouter, First, through adb Configure some settings and start dsrouter:
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
Next, start dotnet track , Such as :
dotnet-trace collect --diagnostic-port /tmp/maui-app --format speedscope
When starting a use -c Release and -p:androidEnableProfiler=true Built android After the application , When dotnet trace When the output , You will notice the connection :
Press <Enter> or <Ctrl+C> to exit...812 (KB)
After your application is fully started , Just press the enter Key to get a saved in the current directory *.speedscope. You can https://speedscope.app Open this file on the , Learn more about the time each method spends during application startup :
stay android Used in applications dotnet More details about tracking , Please refer to our documentation . I suggest that android Analysis on equipment Release edition , To get the best performance in the real world .
dotnet-dsrouter
https://docs.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dsrouter
Our documents
https://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/tracing.md
Measure over time
We are .NET Friends of the basic team set up a pipeline to track .NET MAUI Performance scenarios , for example :
Bag size
Disk size ( non-compressed )
Single file classification
Application startup
as time goes on , This allows us to see the impact of improvement or regression , notice dotnet/maui The number of each submission of the repurchase . We can also determine whether this difference is caused by xamarin-android、xamarin-macios or dotnet/runtime Caused by a change in .
for example , In Physics Pixel 4a Running on the device dotnet new maui Start time of the template ( In Milliseconds ) chart :
Be careful ,Pixel 4a Than Pixel 5 It's much slower .
We can accurately point out that in dotnet/maui Regressions and improvements that occur in . This is very useful for tracking our goals .
similarly , We can be in the same Pixel 4a See... On the device .NET Podcast Application progress over time :
This chart is our real focus , Because it's a “ Real applications ”, It is similar to what developers see in their mobile applications .
As for application size , It is a more stable number —— When things get worse or better , It's easy to zero :
see also dotnet-podcasts#58, Android x# 520 and dotnet/maui#6419 Learn more about these improvements .
dotnet-podcasts#58
https://github.com/microsoft/dotnet-podcasts
Android x# 520
https://github.com/xamarin/AndroidX/pull/520
dotnet/maui#6419
https://github.com/dotnet/maui/pull/6419
alien AOT
Before we get to .NET MAUI In the initial performance test of , We see that JIT( In time ) and AOT( advance ) How the compiled code is executed :
application | JIT Time (ms) | AOT Time (ms) |
dotnet new maui | 1078.0ms | 683.9ms |
Every time you call c# Method JIT Handle , This implicitly affects the startup performance of mobile applications .
Another problem is AOT Resulting in an increase in application size . Every .NET The assembly will add one in the final application android Local library . In order to make better use of these two worlds , Start tracking or analysis AOT yes Xamarin.Android A current feature . This is a kind of AOT Mechanism of application startup path , It significantly improves startup time , Only a modest increase in application size .
stay .NET 6 In the version , This is the fully meaningful default option . in the past , Use Xamarin.Android Do any kind of AOT Need to be Android NDK( Download multiple gb). We are not installing android NDK In the case of AOT Applications , Make it possible .
We are dotnet new android, maui, and maui-blazor Template's built-in configuration file , Benefit most applications . If you want to .NET 6 Record a custom configuration file in , You can try our experimental Mono.Profiler. Android package . We are working hard in the future .NET Recording custom profiles is fully supported in version .
see xamarin-Android#6547 and dotnet/maui#4859 Understand the details of this improvement .
Start tracking or analysis AOT
https://devblogs.microsoft.com/xamarin/faster-startup-times-with-startup-tracing-on-android/
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
Single file assembly memory
Before , If you are in your favorite zip File utility Release android .apk Content , You can see .NET The assembly is located in :
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
These documents are through mmap System calls are loaded separately , This is every... In the application .NET The cost of the assembly . This is android Used in workloads C/ c++ Realized , Use Mono Callbacks provided by the runtime for assembly loading .MAUI Applications have many assemblies , So we introduced a new $(androidUseAssemblyStore) characteristic , This feature is available in Release Enabled by default in version .
After this change , You will get :
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
Now? android To start, just call mmap two : Once it was assemblies.blob, The second is architecture specific Blob. This pair has many . net The application of the assembly has a significant impact .
If you need to check the compiled android Of these assemblies in the application IL, We created an assembly store reader tool to “ Unpack ” These documents .
Another option is to disable these settings when building the application :
dotnet build -c Release -p:AndroidUseAssemblyStore=false -p:Android EnableAssemblyCompression=false
So you can use your favorite compression tool to decompress the generated .apk file , And use ILSpy Such a tool to check .NET Assembly . This is a good way to diagnose trimmers / Linker problem .
see xamarin-android#6311 Learn more about this improvement .
mmap system call
https://man7.org/linux/man-pages/man2/mmap.2.html
mmap
https://man7.org/linux/man-pages/man2/mmap.2.html
Assembly store reader
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
Spanify RegisterNativeMembers
When used Java establish c# Object time , Will call a small Java Wrappers , for example :
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);
}
The method list is a \n and : separated Java Native interface (JNI) Signature list , These signatures are in escrow c# Rewritten in code . For in c# Each... Rewritten in Java Method , You'll get one of these methods .
When practical Java onCreate() Method is called as a android Activities :
public void onCreate (Android.os.Bundle p0)
{
n_onCreate (p0);
}
private native void n_onCreate (Android.os.Bundle p0);
Through all kinds of magic and gestures ,n_onCreate Call to Mono Runtime , And call c# Medium OnCreate() Method .
Split \n and :- The code for the delimited method list is in Xamarin Early use string.Split() Compiling . so to speak ,Span It didn't exist at that time , But now we can use it ! This improves any inheritance Java Class c# Cost of class , So this is a ratio .NET MAUI Broader improvements .
You may ask ,“ Why use strings ?” Use Java Arrays seem to have a greater impact on performance than delimited strings . In our tests , call JNI To get Java Array elements , Performance worse than string .Split and Span New usage of . For how in the future .NET Build it again in version , We have some ideas .
except .NET 6 outside , For current customers Xamarin. Android This change is also included in the latest version of .
see xamarin-android#6708 Learn more about this improvement .
Java Native interface (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
xamarin-android#6708
https://github.com/xamarin/xamarin-android/pull/6708
System.Reflection.Emit And constructor
In the use of Xamarin Early stage , We have one from Java call c# A somewhat complicated method of constructor .
First , We have some reflection calls that occur at startup :
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)!;
It seems to be Mono Left over from earlier versions , And continues to this day . for example , Can be called directly RuntimeHelpers.GetUninitializedObject().
Then there are some complicated System.Reflection.Emit usage , And in System.Reflection.ConstructorInfo Pass on one cinfo example :
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 []>));
Call the returned delegate , bring IntPtr yes Java.Lang.Object Handle to subclass , The object [] Is this particular c# Any arguments to the constructor .emit There is a huge cost for using it for the first time at startup and for every subsequent call .
After careful examination , We can handle Field is set to internal , And simplify this code to :
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);
What this code does is create an object without calling the constructor , Set handle field , And then call the constructor. . This is to be c# When the constructor starts ,Handle In any Java.Lang.Object It's all effective on the Internet . Any inside the constructor Java interoperability ( For example, call other on the class Java Method ) And call any basic Java Constructors all need Handle.
The new code significantly improves the Java Call any c# Constructors , So this particular change improves more than just .NET MAUI. except .NET 6 outside , For current customers Xamarin. android This change is also included in the latest version of .
see xamarin-android#6766 Learn more about this improvement .
xamarin-android#6766
https://github.com/xamarin/xamarin-android/pull/6766
System.Reflection.Emit And methods
When you are in c# Rewrite a Java When the method is used , such as :
public class MainActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
//...
}
}
In from Java To c# In the process of conversion , We must encapsulate c# Method to handle exceptions , for example :
try
{
// Call the actual C# method here
}
catch (Exception e) when (_unhandled_exception (e))
{
androidEnvironment.UnhandledException (e);
if (Debugger.IsAttached || !JNIEnv.PropagateExceptions)
throw;
}
for example , If in OnCreate() Managed exception not handled in , This will actually cause the machine to crash ( And there is no managed c# stack trace ). We need to make sure that the debugger breaks when we attach exceptions , Otherwise... Will be recorded c# stack trace .
from Xamarin Start , The above code is through System.Reflection.Emit Generated :
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);
This code is called twice as a dotnet new android Applications , but ~58 One at a time dotnet new maui Applications !
We realized that we could actually write a strongly typed for each generic delegate type “ Fast path ”, Instead of using System.Reflection.Emit. There is a generated delegate that matches each signature :
void OnCreate(Bundle savedInstanceState);
// Maps to *JNIEnv, JavaClass, Bundle
// Internal to each assembly
internal delegate void _JniMarshal_PPL_V(IntPtr, IntPtr, IntPtr);
So we can list all the used dotnet maui Signature of the application , such as :
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)
{
// ...
}
}
The disadvantage of this method is , When using a new signature , We have to list more . We don't want to list every combination in detail , Because it can lead to IL Growth in size . We are studying how to .NET This has been improved in version .
see xamarin-android#6657 and xamarin- android #6707 Learn more about this improvement .
xamarin-android#6657
https://github.com/xamarin/xamarin-android/pull/6657
xamarin- android #6707
https://github.com/xamarin/xamarin-android/pull/6707
The updated Java.Interop APIs
Java.Interop.dll In the original Xamarin api That's true api:
JNIEnv.CallStaticObjectMethod
stay Java In the “ The new method ” Less memory per call :
JniEnvironment.StaticMethods.CallStaticObjectMethod
When built as Java Method generation c# When Binding , Update is used by default / Faster way — stay Xamarin.Android It's been a while since I was born . before ,Java Binding an item can make $(AndroidCodegenTarget) Set to XAJavaInterop1, It caches and reuses in every call jmethodID example . see also java.interop Document to get the history of this feature .
Other problems are “ Manual ” Where to bind . These methods are often used , So it's worth fixing these !
Some examples to improve this situation :
JNIEnv.FindClass() stay xamarin-android#6805
JavaList and JavaList stay xamarin-android#6812
AndroidCodegenTarget
https://docs.microsoft.com/en-us/xamarin/android/deploy-test/building-apps/build-properties#androidcodegentarget
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
Multidimensional Java Array
Direction Java Pass it back and forth c# Array time , The intermediate step must copy the array , So that the appropriate runtime can access it . This is really a developer experience , because c# Developers expect to write something like this :
var array = new int[] { 1, 2, 3, 4};
MyJavaMethod (array);
stay MyJavaMethod It will do :
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() Visit one “ Type mapping ”, To know which Java Class is used for the elements of an array .
dotnet new maui Specific... Used by the project android API There is a problem :
public ColorStateList (int[][]? states, int[]? colors)
Find a multidimensional int[][] An array can access the... Of each element “ Type mapping ”. When additional logging is enabled , We can see that , Many examples :
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)
In this case , We should be able to call JNIEnv.FindClass() once , And reuse this value for each item in the array !
We are studying how to .NET This is further improved in the version . An example of this is dotnet/maui#5654, Here we simply consider the complete use of Java To create an array .
see xamarin-android#6870 Learn more about this improvement .
dotnet/maui#5654
https://github.com/dotnet/maui/pull/5654
xamarin-android#6870
https://github.com/xamarin/xamarin-android/pull/6870
by android Image use Glide
Glide It's modern android Application recommended image loading Library . Google Docs even recommends it , Because of the built-in android Bitmap Classes can be difficult to use correctly .glidex.forms Is in Xamarin.Forms Use in Glide The prototype of the . But we will Glide Upgrade to the future in .NET MAUI Loading images in “ The way ”.
In order to reduce the JNI Interoperability overhead ,.NET MAUI Of Glide The implementation mainly uses Java Compiling , for example :
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 stay c# Subclass to handle completion in managed code . As a result, , come from web The performance of the image should be better than before in Xamarin.Forms The performance obtained in has been significantly improved .
See dotnet/maui#759 and dotnet/maui#5198.
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
Reduce Java Interop calls
Suppose you have the following Java api:
public void setFoo(int foo);
public void setBar(int bar);
The interoperability of these methods is as follows :
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);
}
So calling these two methods will call... Twice stackalloc, Two calls p/invoke. Create a small Java The wrappers will have more performance , for example :
public void setFooAndBar(int foo, int bar)
{
setFoo(foo);
setBar(bar);
}
Translated into :
public void setFooAndBar(int foo, int bar)
{
setFoo(foo);
setBar(bar);
.NET MAUI Views are essentially c# object , There are many properties that need to be specified in the Java Is set in exactly the same way . If we apply this concept to .NET MAUI Each of the android View in , We can create one ~18 The method of parameter is used for View establish . Subsequent property changes can directly invoke the standard android api.
For very simple .NET MAUI Controls , This is a significant improvement in performance :
Method | Average | error | Standard deviation | 0 generation | Already allocated |
Border(Before) | 323.2 µs | 0.82 µs | 323.2 | 0.9766 | 5 KB |
Border(After) | 242.3 µs | 1.34 µs | 1.25 µs | 0.9766 | 5 KB |
CollectionView(Before) | 354.6 µs | 2.61 µs | 2.31 µs | 1.4648 | 6 KB |
CollectionView(After) | 258.3 µs | 0.49 µs | 0.43 µs | 1.4648 | 6 KB |
see also dotnet/maui#3372 Learn more about this improvement .
dotnet/maui#3372
https://github.com/dotnet/maui/pull/3372
take android XML Migration to Java
review android Upper dotnet Trace output , We can see that reasonable time is spent on :
20.32.ms mono.andorid!Andorid.Views.LayoutInflater.Inflate
Review stack trace , Time is actually spent on android/Java Expand the layout , And in the .NET There is no work at the end .
If you look at the compiled android .apk and res/layouts/bottomtablayout. stay android Studio in ,XML It's just ordinary XML. Only a few identifiers are converted to integers . It means android Must parse XML And pass Java Reflection of api establish Java object —— It seems that we do not use XML You can get faster performance ?
standard-passing BenchmarkDotNet contrast , We found that when it comes to interoperability , Use android The layout is even better than using c# Worse :
Method | Method | error | Standard deviation | Already allocated |
Java | 338.4 µs | 4.21 µs | 3.52 µs | 744 B |
CSharp | 410.2 µs | 7.92 µs | 6.61 µs | 1,336 B |
XML | 490.0 µs | 7.77 µs | 7.27 µs | 2,321 B |
Next , We will BenchmarkDotNet Configured as a single run , To better simulate what happens at startup :
Method | The median |
Java | 4.619 ms |
CSharp | 37.337 ms |
XML | 39.364 ms |
We are .NET MAUI See a simpler layout in , Bottom tab navigation :
<?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>
We can port it to four Java In the method , for example :
@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)
This makes us in the android When creating the bottom tab navigation on the, you can only from c# Switch to Java 4 Time . It also allows android The operating system skips loading and parsing .xml Come on “ inflation ”Java object . We are dotnet/maui Implemented this idea in , Delete all at startup LayoutInflater.Inflate() call .
see also dotnet/maui#5424, dotnet/maui#5493, and dotnet/maui#5528 Learn more about these improvements
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
Delete Microsoft.Extensions.Hosting
hosting Provides a .NET Universal host , Used in .NET Manage dependency injection in the application 、 logging 、 Configure and apply the lifecycle . This has an effect on the startup time , It doesn't seem to be suitable for mobile applications .
from .NET MAUI Remove Microsoft.Extensions.Hosting Use is meaningful .. net MAUI There is no attempt to communicate with “ Universal host ” Interoperate to build DI Containers , It has its own simple implementation , It is optimized for mobile boot . Besides ,. net MAUI No logging providers will be added by default .
Through this change , We see dotnet new maui android Application startup time is reduced 5-10%. stay iOS On , It reduces the size of the same application , from 19.2 MB => 18.0 MB.
See dotnet/maui#4505 and dotnet/maui#4545.
.NET Universal host
https://docs.microsoft.com/en-us/dotnet/core/extensions/generic-host
dotnet/maui#4505
https://github.com/dotnet/maui/pull/4505
dotnet/maui#4545
https://github.com/dotnet/maui/pull/4545
Reduce... At startup Shell initialization
Xamarin. Forms Shell Is a mode of cross platform application navigation . This pattern is in .NET MAUI Proposed in , It is recommended as the default way to build applications .
When we found that we used Shell Cost of ( about Xamarin and Xamarin.form and .NET MAUI), We found a few places to optimize :
Do not resolve routes at startup —— Wait until a navigation that requires them occurs .
If no query string is provided for navigation , Just skip the code that handles the query string . This will remove overuse System.Reflection The code path of .
If the page is not visible BottomNavigationView, Then do not set menu items or any appearance elements .
see also dotnet/maui#5262 Learn more about this improvement .
Xamarin. Forms Shell
https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/
dotnet/maui#5262
https://github.com/dotnet/maui/pull/5262
Fonts should not use temporary files
A lot of time is spent on .NET MAUI The application loads fonts on :
32.19ms Microsoft.Maui!Microsoft.Maui.FontManager.CreateTypeface(System.ValueTuple`3<string, Microsoft.Maui.FontWeight, bool>)
When checking code , It does more work than it needs :
take androidAsset Save file to temporary folder .
Use android API, Typeface.CreateFromFile() To load the file .
We can actually use it directly Typeface.CreateFromAsset() android API, No temporary files at all .
see also dotnet/maui#4933 Learn more about this improvement .
dotnet/maui#4933
https://github.com/dotnet/maui/pull/4933
Calculate on the platform at compile time
{OnPlatform} Use of markup extensions :
<Label Text="Platform: " />
<Label Text="{OnPlatform Default=Unknown, android=android, iOS=iOS" />
… It can actually be computed at compile time ,net6.0-android and net6.0-ios Will get the appropriate value . In the future .NET In the version , We will be on XML Element to do the same optimization .
See dotnet/maui#4829 and dotnet/maui#5611.
dotnet/maui#4829
https://github.com/dotnet/maui/pull/4829
dotnet/maui#5611
https://github.com/dotnet/maui/pull/5611
stay XAML Using the compiler converter in
The following types are now in XAML Compile time conversion , Not at runtime :
Color :dotnet /maui# 4687
https://github.com/dotnet/maui/pull/4687
Corner radius : dotnet / maui # 5192
https://github.com/dotnet/maui/pull/5192
Font size :dotnet / maui # 5338
https://github.com/dotnet/maui/pull/5338
Grid length , Line definition , Column definition :dotnet/maui#5489
https://github.com/dotnet/maui/pull/5489
This leads to .xaml File generation is better / Faster IL.
Optimize color resolution
Microsoft.Maui.Graphics.Color.Parse() The original code can be rewritten , To better use Span And avoid string allocation .
Method | Average | error | Standard deviation | 0 generation | Already allocated |
Parse ( Before ) | 99.13 ns | 0.281 ns | 0.235 ns | 0.0267 | 168 B |
Parse ( after ) | 52.54 ns | 0.292 ns | 0.259 ns | 0.0051 | 32 B |
In the ReadonlySpan<char>dotnet/csharplang#1881 Upper use switch sentence , In the future .NET This situation is further improved in version .
notice dotnet / Microsoft.Maui.Graphics # 343 and dotnet / Microsoft.Maui.Graphics # 345 Details about this improvement .
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
Do not use culture - aware string comparisons
Review a new naui Project dotnet Trace output , You can see android The real cost of the first region awareness string comparison on :
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
actually , We don't even want to use a culture comparison in this case — It just comes from Xamarin.Forms Introduced code .
for example , If you have :
if (text.StartsWith("f"))
{
// do something
}
under these circumstances , You can simply do this :
if (text.StartsWith("f"))
{
// do something
}
If executed throughout the application ,System.Globalization.CultureInfo.CurrentCulture Can avoid being called , And it can be improved slightly If The overall speed of the statement .
To solve the whole dotnet/maui The situation of repurchase , We introduced code analysis rules to capture these :
dotnet_diagnostic.CA1307.severity = error
dotnet_diagnostic.CA1309.severity = error
see also dotnet/maui#4988 Learn more about improvements .
dotnet/maui#4988
https://github.com/dotnet/maui/pull/4988
Lazily create logs
ConfigureFonts() API It took some time at startup to do something that could be deferred to the future . We can also improve Microsoft.Extensions General usage of logging infrastructure in .
Some of the improvements we have made are as follows :
Postpone creation “ Recorder ” class , Create them until you need them .
The built-in logging infrastructure is disabled by default , Must be explicitly enabled .
Delay call android Of EmbeddedFontLoader Medium Path.GetTempPath(), Until you need it .
Do not use ILoggerFactory Create a generic recorder . But directly get ILogger service , So it is cached .
see also dotnet/maui#5103 Learn more about this improvement .
dotnet/maui#5103
https://github.com/dotnet/maui/pull/5103
Using factory methods for dependency injection
When using Microsoft.Extensions.DependencyInjection, Registration service , such as :
IServiceCollection services /* ... */;
services.TryAddSingleton<IFooService, FooService>();
Microsoft.Extensions Something must be done System.Reflection To create FooService The first example of . This is noteworthy dotnet Trace output in android On .
contrary , If you do :
// 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>()));
under these circumstances ,Microsoft.Extensions You can simply call lamdba/ Anonymous methods , Without a system . Reflection .
We're in all dotnet/maui Improved on , And used bannedapianalyzer, So that no one will accidentally use TryAddSingleton() Slower reloading .
see also dotnet/maui#5290 Learn more about this improvement .
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
Default VerifyDependencyInjectionOpenGenericServiceTrimmability
.NET Podcast The sample cost 4-7ms Time for :
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallsiteFactory.ValidateTrimmingAnnotations()
MSBuild attribute $(verifydependencyinjectionopengenericservicetrimability) Trigger the method to run . This feature switch ensures that dynamallyaccessedmembers Correctly applied to open generic types in dependency injection .
On the basis of .NET SDK in , When publishtrim =true when , The switch will be enabled . However ,android The application is Debug There is no setting in version publishtrim =true, So the developer missed the validation .
contrary , In published applications , We don't want to pay the cost of this verification . So this feature switch should be in Release Close in version .
see xamarin-android#6727 and xamarin-macios#14130 Learn more about this improvement .
.NET Podcast
https://github.com/dotnet/runtime/pull/65326
xamarin-android#6727
https://github.com/xamarin/xamarin-android/pull/6727
xamarin-macios#14130
https://github.com/xamarin/xamarin-macios/pull/14130
Load idly ConfigurationManager
configurationmanager Not used by many mobile applications , And creating one is very expensive !( for example , stay android It's about 7.59ms)
stay .NET MAUI in , One ConfigurationManager It is created by default at startup , We can use Lazy Delay its creation , So it will not be created , Unless you ask .
see also dotnet/maui#5348 Learn more about this improvement .
dotnet/maui#5348
https://github.com/dotnet/maui/pull/5348
Improved built-in AOT The configuration file
Mono The runtime has a for each method JIT Time report ( See our documentation ), for example :
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)
This is a use of Profiled AOT Is under construction .NET Podcast Top level in the example jit Time choice . These seem to be what developers want in . net MAUI Common used in applications api.
To ensure that these methods are used in AOT In profile , We are dotnet/maui These are used in 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}" />
Calling these methods in the test application ensures that they are in the built-in . net MAUI AOT In profile .
After this change , We saw an updated JIT The report :
_ = 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}" />
This leads to further additions :
var split = "foo;bar".Split(';');
var x = int.Parse("999");
x.ToString();
We are right. Color.Parse()、Connectivity Made similar changes .NETworkAccess DeviceInfo. Idiom ,AppInfo..NET MAUI Should be used frequently in applications requestdtheme.
see also dotnet/maui#5559, dotnet/maui#5682, and dotnet/maui#6834 Learn more about these improvements .
If you want to .NET 6 Record a custom AOT The configuration file , You can try our lab pack Mono.Profiler.Android. We are working hard in the future .NET Recording custom profiles is fully supported in version .
See our documentation
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
Enable AOT Delayed loading of images
before ,Mono The runtime will load all... At startup AOT Images , To verify hosting .NET Assembly ( for example Foo.dll) Of MVID Whether or not AOT Images (libFoo.dll.so) matching . In most .NET In the application , some AOT The image may need to be loaded later .
Mono A new ——aot-lazy-assembly-load or mono_opt_aot_lazy_assembly_load Set up ,android Workloads can be selected . We found that this would dotnet new maui The project in Pixel 6 Pro The startup time on has been improved by about 25ms.
This is enabled by default , But if you need to , You can be in your .csproj This setting is disabled in the following ways :
<AndroidAotEnableLazyLoad>false</AndroidAotEnableLazyLoad>
see dotnet/runtime#67024 and xamarin-android #6940 Learn more about these improvements .
dotnet/runtime#67024
https://github.com/dotnet/runtime/pull/67024
xamarin-android #6940
https://github.com/xamarin/xamarin-android/pull/6940
Delete System.Uri Encoding objects not used in
One MAUI Application's dotnet Trace output , Displays approximately 7ms It took a while to load UTF32 and Latin1 The first system of coding . Use Uri api:
<AndroidAotEnableLazyLoad>false</AndroidAotEnableLazyLoad>
This field was accidentally left in place . Just delete s_noFallbackCharUTF8 Field , You can improve any use System.Uri Or related api Of . net Application launch .
See dotnet/runtime#65326 Learn more about this improvement .
dotnet/runtime#65326
https://github.com/dotnet/runtime/pull/65326
Application size improvements
Fix the default MauiImage size
dotnet new maui The template displays a friendly " Network robot ” Image . This is done by using a .svg File as a MauiImage And content :
<svg width="419" height="519" viewBox="0 0 419 519" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- everything else -->
By default ,MauiImage Use .svg The width and height values in are used as the “ Base size ”. Review build output , These images are scaled to :
objReleasenet6.0-androidresizetizerrmipmap-xxxhdpi
appiconfg.png = 1824x1824
dotnet_bot.png = 1676x2076
This is for android It seems a little too big for the equipment ? We can simply specify in the template %(BaseSize), It also provides an example of how to choose the right size for these images :
<!-- 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" />
This results in a more suitable size :
obj\Release\net6.0-android\resizetizer\r\mipmap-xxxhdpi\
appiconfg.png = 512x512
dotnet_bot.png = 672x832
We can also modify .svg Content , But this may not be desirable , It depends on how the graphic designer uses the image in other design tools .
In another example , One 3008×5340 .jpg Images :
<MauiImage Include="Resources\Images\large.jpg" />
Upgrading to 21360×12032! Set up Resize="false" Will prevent the image from being resized , But we set this as the default option for non vector images . Next , Developers should be able to rely on defaults , Or specify as needed %( Basic size ) and %( Resize ).
These changes improve startup performance and application size . see also dotnet/maui#4759 and dotnet/maui#6419 Understand the details of these improvements .
dotnet/maui#4759
https://github.com/dotnet/maui/pull/4759
dotnet/maui#6419
https://github.com/dotnet/maui/pull/6419
Delete Application.Properties and DataContractSerializer
Xamarin.Forms There is one API, Used by Application.Properties Dictionary persistent key value pairs . This is used internally DataContractSerializer, This is not the best choice for self-contained and pruned mobile applications . come from BCL Of System.Xml The part of may be quite large , We don't want to be in every .NET MAUI All applications pay for this .
Simply delete this API And all DataContractSerializer Use , stay android Can be increased by about 855KB, stay iOS On the rise about 1MB.
see also dotnet/maui#4976 Learn more about this improvement .
dotnet/maui#4976
https://github.com/dotnet/maui/pull/4976
Trim unused HTTP Realization
System.NET.Http.UseNativeHttpHandler Not properly reducing the underlying hosting HTTP The handler (SocketsHttpHandler). By default ,androidMessageHandler and NSUrlSessionHandler Used to exploit the underlying android and iOS Network stack .
By fixing this problem , In any .NET MAUI More can be deleted from the application IL Code . In one example , A use HTTP Of android The application can completely delete several assemblies :
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
see dotnet/runtime#64852, xamarin-android#6749, and xamarin-macios#14297 Details about this improvement .
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 Improvements in the example
We made some adjustments to the sample itself , The change is considered to be “ Best practices ”.
Delete Microsoft.Extensions.Http usage
Use Microsoft.Extensions.Http Too heavy for mobile apps , And does not provide any real value in this case .
therefore ,HttpClient Don't use 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;
// ...
}
We simply create a HttpClient To use... In services :
public ShowsService(ListenLaterService listenLaterService)
{
this.httpClient = new HttpClient() { BaseAddress = new Uri(Config.APIUrl) };
// ...
}
We recommend that for every application that needs to interact web The service uses a separate HttpClient example .
see also dotnet/runtime#66863 and dotnet podcasts#44 Learn more about improvements .
dotnet/runtime#66863
https://github.com/dotnet/runtime/issues/66863
dotnet podcasts#44
https://github.com/microsoft/dotnet-podcasts/pull/44
Delete Newtonsoft.Json Use
.NET Podcast The sample uses a named MonkeyCache The library of , It depends on Newtonsoft.Json. This in itself is not a problem , It's just .NET MAUI + Blazor The application depends on some ASP.NET Core Libraries, in turn, depend on System.Text.Json. This app is actually for JSON Parsing library “ Paid twice as much ”, This has an impact on the size of the application .
We transplanted MonkeyCache 2.0 To use System.Text.Json, Unwanted Newtonsoft. This will iOS The size of the app on is from 29.3MB Reduced to 26.1MB!
See monkey-cache#109 and dotnet-podcasts#58 Learn more about improvements .
.NET Podcast
https://github.com/microsoft/dotnet-podcasts
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
Run the first network request in the background
review dotnet Trace output , Initial request at ShowsService Blocking UI Thread initializes connection .NETworkAccess Barrel.Current. obtain ,HttpClient. This can be done in the background thread - This results in a faster startup time . stay Task.Run() Encapsulate the first call in , The startup efficiency of this example can be improved to some extent .
stay Pixel 5a Average operation on the equipment 10 Time :
Before
Average(ms): 843.7
Average(ms): 847.8
After
Average(ms): 817.2
Average(ms): 812.8
For this type of change , Always suggest according to dotnet Follow up or other analysis results to make decisions , And measure changes before and after changes .
see also dotnet-podcasts#57 Learn more about this improvement .
dotnet-podcasts#57
https://github.com/microsoft/dotnet-podcasts/pull/57
Experimental or advanced options
If you want to android Further optimize your .NET MAUI Applications , Here are some advanced or experimental features , Not enabled by default .
trim Resource.designer.cs
since Xamarin Since the birth of ,android The application contains a generated Properties/Resource.designer.cs file , Used to access the androidResource Integer identifier of the file . This is a R.java Class c# / Hosted version , These identifiers are allowed as normal c# Field ( Sometimes const), Without the need for Java Do any interoperability .
In a android Studio“ library ” In the project , When you include a like res/drawable/foo.png Such a document , You will get a field like this :
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;
}
}
You can use this value , for example , stay ImageView Show this image in :
ImageView imageView = new ImageView(this);
imageView.setImageResource(R.drawable.foo);
When you build com.yourlibrary.aar when , android Of gradle The plug-in doesn't actually put this class in the package . contrary ,android The application actually knows what the value of an integer is . therefore ,R Class is in android Generated when the application is built , For each android The library generates a R class .
Xamarin.Android Took a different approach , Integer repair at runtime . use c# and MSBuild Is there really no good precedent for doing such a thing ? for example , One c# android The library may have :
public class Resource
{
public class Drawable
{
// The actual integer here is *not* final
public int foo = -1;
}
}
Then the main application will have the following code :
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;
}
}
This has worked well for some time , But unfortunately , image androidX、Material、 Google Play Services The number of resources in Google's library has begun to compound . for example , stay dotnet/maui#2606 in , Set... On startup 21497 A field ! We created a way to solve this problem , But we also have a new custom pruning step to perform the repair at build time ( During pruning ) Not at runtime .
<AndroidLinkResources>true</ AndroidLinkResources>
This will make your version version replace the case as follows :
ImageView imageView = new(this);
imageView.SetImageResource(Resource.Drawable.foo);
contrary , Direct inline integer :
ImageView imageView = new(this);
imageView.SetImageResource(1234); // The actual integer here *is* final
A known problem with this feature is :
public partial class Styleable
{
public static int[] ActionBarLayout = new int[] { 16842931 };
}
Replacement... Is not currently supported int[] value , This makes it impossible to enable it by default . Some applications will be able to turn this on ,dotnet new maui Templates , Maybe a lot .NET maui android Applications will not encounter this limitation .
In the future .NET In the version , We may enable... By default $(androidLinkResources), Or completely redesign .
see xamarin-android#5317, xamarin-android#6696, and dotnet/maui#4912 Learn more about this feature .
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 Code shrinker
R8 Is the whole program optimization 、 Shrink and shrink tools , take java Byte code conversion to optimized dex Code .R8 Use Proguard keep The rule format specifies entry points for applications . As you expected , Many applications require additional Proguard Rules to keep working .R8 May be too radical , And deleted Java Something called by reflection , wait . We don't have a good way to make it all .NET android Application defaults .
To choose to use R8 for Release edition , Please in yours. .csproj To add the following :
<!-- NOTE: not recommended for Debug builds! -->
<AndroidLinkTool Condition="'$(Configuration)' == 'Release'">r8</AndroidLinkTool>
If you start your application Release The build crashes when enabled , Check adb logcat Output , Look what's wrong .
If you see java.lang. classnotfoundexception or java.lang. You may need to add one ProguardConfiguration File into your project , such as :
<ItemGroup>
<ProguardConfiguration Include="proguard.cfg" />
</ItemGroup>
-keep class com.thepackage.TheClassYouWantToPreserve { *; <init>(...); }
We are studying in the future .NET Enabled by default in version R8 The option to .
Please refer to our D8/R8 file .
our D8/R8 file
https://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/D8andR8.md
AOT
Profiled AOT By default , Because it gives the best tradeoff between application size and startup performance . If the size of the application is independent of your application , You can consider all .NET Assembly usage AOT.
To choose to join , In your .csproj Add the following Release To configure :
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<RunAOTCompilation>true</RunAOTCompilation>
<androidEnableProfiledAot>false</androidEnableProfiledAot>
</PropertyGroup>
This will reduce what happens during application startup JIT Amount of compilation , And navigate to the back screen .
AOT and LLVM
LLVM Provides a modern optimizer independent of source and target , It can be done with Mono AOT Compiler Combined with output . As a result, , The size of the application is slightly larger , Longer release build time , Better runtime performance .
To select to LLVM be used for Release edition , Please add the following to your .csproj in :
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<RunAOTCompilation>true</RunAOTCompilation>
<EnableLLVM>true</EnableLLVM>
</PropertyGroup>
This feature can be used with Profiled AOT( or AOT-ing everything ) Use a combination of . Compare before and after the application , understand EnableLLVM Impact on application size and startup performance .
at present , One needs to be installed android NDK To use this function . If we can address this need ,EnableLLVM Will be the future .NET The default options in version .
For more information , Please refer to us about EnableLLVM Documents .
LLVM
https://llvm.org/
EnableLLVM Documents
https://docs.microsoft.com/en-us/xamarin/android/deploy-test/building-apps/build-properties
Record custom AOT The configuration file
Summary AOT By default, we use the .NET MAUI and android Provided in the workload “ built-in ” Profile , Useful for most applications . For best startup performance , Ideally, an application specific configuration file should be recorded . In this case , We have an experimental Mono.Profiler.Android package .
Record the configuration file :
dotnet add package Mono.AotProfiler.android
dotnet build -t:BuildAndStartAotProfiling
# Wait until app launches, or you navigate to a screen
dotnet build -t:FinishAotProfiling
This will generate a... In your project directory custom.aprof. To use it in future builds :
<ItemGroup>
<androidAotProfile Include="custom.aprof" />
</ItemGroup>
We are working hard in the future .NET Recording custom profiles is fully supported in version .
Mono.Profiler.Android
https://github.com/jonathanpeppers/Mono.Profiler.Android
Conclusion
I hope you like our .NET MAUI Performance discussion . Please try .NET MAUI And you can http://dot.net/maui Learn more about !
边栏推荐
- Debezium同步之Debezium架构详解
- 关于在云服务器上(这里用腾讯云)安装mysql8.0并使本地可以远程连接的方法
- verilog设计抢答器【附源码】
- 浙江大学周亚金:“又破又立”的顶尖安全学者,好奇心驱动的行动派
- 请查收.NET MAUI 的最新学习资源
- Antd select selector drop-down box follows the scroll bar to scroll through the solution
- 对比学习之 Unsupervised Learning of Visual Features by Contrasting Cluster Assignments
- [untitled]
- 软件设计之——“高内聚低耦合”
- The use of list and Its Simulation Implementation
猜你喜欢
随机推荐
The use of list and Its Simulation Implementation
科普达人丨一文弄懂什么是云计算?
STM32入门开发 编写DS18B20温度传感器驱动(读取环境温度、支持级联)
Case study of Jinshan API translation function based on retrofit framework
.NET MAUI 性能提升
Go slice comparison
数据库同步工具 DBSync 新增对MongoDB、ES的支持
Vuthink正确安装过程
The post-90s resigned and started a business, saying they would kill cloud database
Force buckle 1002 Find common characters
How to use cherry pick?
Excel公式知多少?
基于DE2 115开发板驱动HC_SR04超声波测距模块【附源码】
使用引用
About the application of writing shell script JSON in JMeter
测试优惠券要怎么写测试用例?
Vscode 尝试在目标目录创建文件时发生一个错误:拒绝访问【已解决】
Activity lifecycle
oracle常见锁表处理方式
[untitled]