当前位置:网站首页>. 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 :

1dafd69a51f8ec64f3f928b3a898d41b.png

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 :

733230e221099ace6e718ef1840e78b7.png

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 :

349fdff314a09f2798aaf75852007e58.png

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 :

03ef0ab357124ca092c4d2d15f1a6d48.png

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 :

  1. take androidAsset Save file to temporary folder .

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

原网站

版权声明
本文为[Dotnet cross platform]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/188/202207070934505483.html