Przycinanie aplikacji MAUI platformy .NET
Podczas kompilowania aplikacji interfejs użytkownika aplikacji wieloplatformowej platformy .NET (.NET MAUI) może używać konsolidatora wywoływanego ILLink w celu zmniejszenia ogólnego rozmiaru aplikacji przy użyciu techniki znanej jako przycinanie. ILLink zmniejsza rozmiar, analizując kod pośredni utworzony przez kompilator. Usuwa nieużywane metody, właściwości, pola, zdarzenia, struktury i klasy, aby utworzyć aplikację zawierającą tylko zależności kodu i zestawu niezbędne do uruchomienia aplikacji.
Aby zapobiec zmianom zachowania podczas przycinania aplikacji, platforma .NET zapewnia statyczną analizę zgodności przycinania za pomocą ostrzeżeń dotyczących przycinania. Trimmer generuje ostrzeżenia dotyczące przycinania, gdy znajdzie kod, który może nie być zgodny z przycinaniem. Jeśli istnieją ostrzeżenia dotyczące przycinania, należy je naprawić, a aplikacja powinna być dokładnie przetestowana po przycinaniu, aby upewnić się, że nie ma żadnych zmian w zachowaniu. Aby uzyskać więcej informacji, zobacz Wprowadzenie do ostrzeżeń dotyczących przycinania.
Zachowanie przycinania
Zachowanie przycinania można kontrolować, ustawiając właściwość kompilacji $(TrimMode)
na partial
wartość lub full
:
<PropertyGroup>
<TrimMode>full</TrimMode>
</PropertyGroup>
Ważne
Właściwość $(TrimMode)
kompilacji nie powinna być warunkowa przez konfigurację kompilacji. Dzieje się tak, ponieważ przełączniki funkcji są włączone lub wyłączone na podstawie wartości $(TrimMode)
właściwości kompilacji, a te same funkcje powinny być włączone lub wyłączone we wszystkich konfiguracjach kompilacji, aby kod zachowywał się identycznie.
Tryb full
przycinania usuwa dowolny kod, który nie jest używany przez aplikację. Tryb partial
przycinania przycina bibliotekę klas bazowych (BCL), zestawy dla bazowych platform (takich jak Mono.Android.dll i Microsoft.iOS.dll) oraz wszelkie inne zestawy, które zdecydowały się na przycinanie $(TrimmableAsssembly)
elementu kompilacji:
<ItemGroup>
<TrimmableAssembly Include="MyAssembly" />
</ItemGroup>
Jest to równoważne ustawieniu [AssemblyMetadata("IsTrimmable", "True")]
podczas kompilowania zestawu.
Uwaga
Nie trzeba ustawiać $(PublishTrimmed)
właściwości kompilacji na true
wartość w pliku projektu aplikacji, ponieważ jest ona domyślnie ustawiona.
Aby uzyskać więcej opcji przycinania, zobacz Opcje przycinania.
Przycinanie wartości domyślnych
Domyślnie kompilacje Android i Mac Catalyst używają częściowego przycinania, gdy konfiguracja kompilacji jest ustawiona na kompilację wydania. System iOS używa częściowego przycinania dla wszystkich kompilacji urządzeń, niezależnie od konfiguracji kompilacji i nie używa przycinania dla kompilacji symulatora.
Przycinanie niezgodności
Następujące funkcje programu .NET MAUI są niezgodne z pełnym przycinaniem i zostaną usunięte przez trymer:
- Wyrażenia powiązań, w których ta ścieżka powiązania jest ustawiona na ciąg. Zamiast tego użyj skompilowanych powiązań. Aby uzyskać więcej informacji, zobacz Skompilowane powiązania.
- Niejawne operatory konwersji podczas przypisywania wartości niezgodnego typu do właściwości w języku XAML lub gdy dwie właściwości różnych typów używają powiązania danych. Zamiast tego należy zdefiniować element TypeConverter dla typu i dołączyć go do typu przy użyciu elementu TypeConverterAttribute. Aby uzyskać więcej informacji, zobacz Define a TypeConverter to replace an implicit conversion operator (Definiowanie operatora konwersji niejawnej).
- Ładowanie kodu XAML w czasie wykonywania za LoadFromXaml pomocą metody rozszerzenia. Ten kod XAML można bezpiecznie przycinać, dodając adnotacje do wszystkich typów, które można załadować w czasie wykonywania za pomocą atrybutu
DynamicallyAccessedMembers
lub atrybutuDynamicDependency
. Jest to jednak bardzo podatne na błędy i nie jest zalecane. - Odbieranie danych nawigacji przy użyciu elementu QueryPropertyAttribute. Zamiast tego należy zaimplementować IQueryAttributable interfejs dla typów, które muszą akceptować parametry zapytania. Aby uzyskać więcej informacji, zobacz Przetwarzanie danych nawigacji przy użyciu jednej metody.
- Właściwość
SearchHandler.DisplayMemberName
. Zamiast tego należy podać element , ItemTemplate aby zdefiniować wygląd SearchHandler wyników. Aby uzyskać więcej informacji, zobacz Definiowanie wyglądu elementu wyników wyszukiwania. - Kontrolka HybridWebView ze względu na użycie funkcji serializacji dynamicznej
System.Text.Json
.
Alternatywnie można użyć przełączników funkcji, aby trimmer zachowywał kod dla tych funkcji. Aby uzyskać więcej informacji, zobacz Przycinanie przełączników funkcji.
W przypadku niezgodności przycinania platformy .NET zobacz Znane przycinanie niezgodności.
Definiowanie klasy TypeConverter w celu zastąpienia niejawnego operatora konwersji
Nie można polegać na niejawnych operatorach konwersji podczas przypisywania wartości niezgodnego typu do właściwości w języku XAML lub gdy dwie właściwości różnych typów używają powiązania danych, gdy jest włączone pełne przycinanie. Wynika to z faktu, że metody operatorów niejawnych mogą zostać usunięte przez trymer, jeśli nie są one używane w kodzie języka C#. Aby uzyskać więcej informacji na temat niejawnych operatorów konwersji, zobacz Jawne i niejawne operatory konwersji zdefiniowane przez użytkownika.
Rozważmy na przykład następujący typ, który definiuje niejawne operatory konwersji między SizeRequest
i Size
:
namespace MyMauiApp;
public struct SizeRequest : IEquatable<SizeRequest>
{
public Size Request { get; set; }
public Size Minimum { get; set; }
public SizeRequest(Size request, Size minimum)
{
Request = request;
Minimum = minimum;
}
public SizeRequest(Size request)
{
Request = request;
Minimum = request;
}
public override string ToString()
{
return string.Format("{{Request={0} Minimum={1}}}", Request, Minimum);
}
public bool Equals(SizeRequest other) => Request.Equals(other.Request) && Minimum.Equals(other.Minimum);
public static implicit operator SizeRequest(Size size) => new SizeRequest(size);
public static implicit operator Size(SizeRequest size) => size.Request;
public override bool Equals(object? obj) => obj is SizeRequest other && Equals(other);
public override int GetHashCode() => Request.GetHashCode() ^ Minimum.GetHashCode();
public static bool operator ==(SizeRequest left, SizeRequest right) => left.Equals(right);
public static bool operator !=(SizeRequest left, SizeRequest right) => !(left == right);
}
Po włączeniu pełnego przycinania operatory konwersji niejawnej między elementami SizeRequest
i Size
mogą zostać usunięte przez trymer, jeśli nie są one używane w kodzie języka C#.
Zamiast tego należy zdefiniować element TypeConverter dla typu i dołączyć go do typu przy użyciu elementu TypeConverterAttribute:
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
namespace MyMauiApp;
[TypeConverter(typeof(SizeRequestTypeConverter))]
public struct SizeRequest : IEquatable<SizeRequest>
{
public Size Request { get; set; }
public Size Minimum { get; set; }
public SizeRequest(Size request, Size minimum)
{
Request = request;
Minimum = minimum;
}
public SizeRequest(Size request)
{
Request = request;
Minimum = request;
}
public override string ToString()
{
return string.Format("{{Request={0} Minimum={1}}}", Request, Minimum);
}
public bool Equals(SizeRequest other) => Request.Equals(other.Request) && Minimum.Equals(other.Minimum);
public static implicit operator SizeRequest(Size size) => new SizeRequest(size);
public static implicit operator Size(SizeRequest size) => size.Request;
public override bool Equals(object? obj) => obj is SizeRequest other && Equals(other);
public override int GetHashCode() => Request.GetHashCode() ^ Minimum.GetHashCode();
public static bool operator ==(SizeRequest left, SizeRequest right) => left.Equals(right);
public static bool operator !=(SizeRequest left, SizeRequest right) => !(left == right);
private sealed class SizeRequestTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
=> sourceType == typeof(Size);
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
=> value switch
{
Size size => (SizeRequest)size,
_ => throw new NotSupportedException()
};
public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType)
=> destinationType == typeof(Size);
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
{
if (value is SizeRequest sizeRequest)
{
if (destinationType == typeof(Size))
return (Size)sizeRequest;
}
throw new NotSupportedException();
}
}
}
Przycinanie przełączników funkcji
Program .NET MAUI zawiera dyrektywy trimmer znane jako przełączniki funkcji, które umożliwiają zachowanie kodu dla funkcji, które nie są bezpieczne. Te dyrektywy trimmer mogą być używane, gdy $(TrimMode)
właściwość kompilacji jest ustawiona na full
, a także dla natywnej AOT:
Właściwość MSBuild | opis |
---|---|
MauiEnableVisualAssemblyScanning |
W przypadku ustawienia wartości true program .NET MAUI będzie skanować zestawy pod kątem typów implementowanych IVisual i dla [assembly:Visual(...)] atrybutów oraz będzie rejestrować te typy. Domyślnie ta właściwość kompilacji jest ustawiona na false po włączeniu pełnego przycinania. |
MauiShellSearchResultsRendererDisplayMemberNameSupported |
Po ustawieniu false wartości SearchHandler.DisplayMemberName na wartość wartość zostanie zignorowana. Zamiast tego należy podać element , ItemTemplate aby zdefiniować wygląd SearchHandler wyników. Domyślnie ta właściwość kompilacji jest ustawiana na false wartość , gdy jest włączone pełne przycinanie lub natywna funkcja AOT. |
MauiQueryPropertyAttributeSupport |
W przypadku ustawienia false [QueryProperty(...)] wartości atrybuty nie będą używane do ustawiania wartości właściwości podczas nawigowania. Zamiast tego należy zaimplementować IQueryAttributable interfejs w celu akceptowania parametrów zapytania. Domyślnie ta właściwość kompilacji jest ustawiana na false wartość , gdy jest włączone pełne przycinanie lub natywna funkcja AOT. |
MauiImplicitCastOperatorsUsageViaReflectionSupport |
W przypadku ustawienia false wartości program .NET MAUI nie będzie szukać niejawnych operatorów konwersji podczas konwertowania wartości z jednego typu na inny. Może to mieć wpływ na powiązania między właściwościami z różnymi typami i ustawienie wartości właściwości obiektu możliwego do powiązania z wartością innego typu. Zamiast tego należy zdefiniować element TypeConverter dla typu i dołączyć go do typu przy użyciu atrybutu TypeConverterAttribute . Domyślnie ta właściwość kompilacji jest ustawiana na false wartość , gdy jest włączone pełne przycinanie lub natywna funkcja AOT. |
_MauiBindingInterceptorsSupport |
W przypadku ustawienia wartości false program .NET MAUI nie przechwytuje żadnych wywołań metod SetBinding i nie spróbuje ich skompilować. Domyślnie ta właściwość kompilacji jest ustawiona na true wartość . |
MauiEnableXamlCBindingWithSourceCompilation |
W przypadku ustawienia wartości true program .NET MAUI skompiluje wszystkie powiązania, w tym te, w których Source jest używana właściwość. Jeśli włączysz tę funkcję, upewnij się, że wszystkie powiązania mają poprawne x:DataType , aby były kompilowane lub czyściły typ x:Data={x:Null}} danych, jeśli powiązanie nie powinno być kompilowane. Domyślnie ta właściwość kompilacji jest ustawiana na true wartość , gdy jest włączone pełne przycinanie lub natywna funkcja AOT. |
MauiHybridWebViewSupported |
Po ustawieniu false wartości na kontrolka HybridWebView nie będzie dostępna. Domyślnie ta właściwość kompilacji jest ustawiana na false wartość , gdy jest włączone pełne przycinanie lub natywna funkcja AOT. |
Te właściwości programu MSBuild mają również równoważne AppContext przełączniki:
- Właściwość
MauiEnableVisualAssemblyScanning
MSBuild ma równoważny AppContext przełącznik o nazwieMicrosoft.Maui.RuntimeFeature.IsIVisualAssemblyScanningEnabled
. - Właściwość
MauiShellSearchResultsRendererDisplayMemberNameSupported
MSBuild ma równoważny AppContext przełącznik o nazwieMicrosoft.Maui.RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported
. - Właściwość
MauiQueryPropertyAttributeSupport
MSBuild ma równoważny AppContext przełącznik o nazwieMicrosoft.Maui.RuntimeFeature.IsQueryPropertyAttributeSupported
. - Właściwość
MauiImplicitCastOperatorsUsageViaReflectionSupport
MSBuild ma równoważny AppContext przełącznik o nazwieMicrosoft.Maui.RuntimeFeature.IsImplicitCastOperatorsUsageViaReflectionSupported
. - Właściwość
_MauiBindingInterceptorsSupport
MSBuild ma równoważny AppContext przełącznik o nazwieMicrosoft.Maui.RuntimeFeature.AreBindingInterceptorsSupported
. - Właściwość
MauiEnableXamlCBindingWithSourceCompilation
MSBuild ma równoważny AppContext przełącznik o nazwieMicrosoft.Maui.RuntimeFeature.MauiEnableXamlCBindingWithSourceCompilationEnabled
. - Właściwość
MauiHybridWebViewSupported
MSBuild ma równoważny AppContext przełącznik o nazwieMicrosoft.Maui.RuntimeFeature.IsHybridWebViewSupported
.
Najprostszym sposobem korzystania z przełącznika funkcji jest umieszczenie odpowiedniej właściwości MSBuild w pliku projektu aplikacji (*.csproj), co powoduje przycięcie powiązanego kodu z zestawów .NET MAUI.
Zachowaj kod
Gdy używasz trymeru, czasami usuwa kod, który mógł być wywoływany dynamicznie, nawet pośrednio. Możesz poinstruować trimmer, aby zachować elementy członkowskie, dodając adnotacje do atrybutu DynamicDependency
. Ten atrybut może służyć do wyrażania zależności od typu i podzestawu elementów członkowskich lub w określonych elementach członkowskich.
Ważne
Każdy element członkowski w liście BCL, który nie może być statycznie określany jako używany przez aplikację, podlega usunięciu.
Atrybut DynamicDependency
można zastosować do konstruktorów, pól i metod:
[DynamicDependency("Helper", "MyType", "MyAssembly")]
static void RunHelper()
{
var helper = Assembly.Load("MyAssembly").GetType("MyType").GetMethod("Helper");
helper.Invoke(null, null);
}
W tym przykładzie zapewnia DynamicDependency
, że Helper
metoda jest przechowywana. Bez atrybutu przycinanie spowoduje całkowite usunięcie Helper
z MyAssembly
lub usunięcie MyAssembly
, jeśli nie odwołuje się do niego w innym miejscu.
Atrybut określa element członkowski, który ma być zachowany za pośrednictwem string
atrybutu DynamicallyAccessedMembers
lub . Typ i zestaw są niejawne w kontekście atrybutu lub jawnie określone w atrybucie (przez Type
, lub przez string
s dla typu i nazwy zestawu).
Ciągi typów i składowych używają odmiany formatu ciągu identyfikatora komentarza dokumentacji języka C# bez prefiksu elementu członkowskiego. Ciąg składowy nie powinien zawierać nazwy typu deklarowanego i może pominąć parametry, aby zachować wszystkie elementy członkowskie określonej nazwy. W poniższych przykładach pokazano prawidłowe zastosowania:
[DynamicDependency("Method()")]
[DynamicDependency("Method(System,Boolean,System.String)")]
[DynamicDependency("MethodOnDifferentType()", typeof(ContainingType))]
[DynamicDependency("MemberName")]
[DynamicDependency("MemberOnUnreferencedAssembly", "ContainingType", "UnreferencedAssembly")]
[DynamicDependency("MemberName", "Namespace.ContainingType.NestedType", "Assembly")]
// generics
[DynamicDependency("GenericMethodName``1")]
[DynamicDependency("GenericMethod``2(``0,``1)")]
[DynamicDependency("MethodWithGenericParameterTypes(System.Collections.Generic.List{System.String})")]
[DynamicDependency("MethodOnGenericType(`0)", "GenericType`1", "UnreferencedAssembly")]
[DynamicDependency("MethodOnGenericType(`0)", typeof(GenericType<>))]
Zachowywanie zestawów
Można określić zestawy, które powinny zostać wykluczone z procesu przycinania, jednocześnie zezwalając na przycinanie innych zestawów. Takie podejście może być przydatne, gdy nie można łatwo użyć atrybutu DynamicDependency
lub nie kontrolować kodu, który jest przycinany.
Po przycinaniu wszystkich zestawów można poinformować trymer o pominięcie zestawu, ustawiając TrimmerRootAssembly
element MSBuild w pliku projektu:
<ItemGroup>
<TrimmerRootAssembly Include="MyAssembly" />
</ItemGroup>
Uwaga
Rozszerzenie .dll
nie jest wymagane podczas ustawiania TrimmerRootAssembly
właściwości MSBuild.
Jeśli trimmer pomija zestaw, jest uważany za zakorzeniony, co oznacza, że i wszystkie jego statycznie zrozumiałe zależności są przechowywane. Dodatkowe zestawy można pominąć, dodając więcej TrimmerRootAssembly
właściwości programu MSBuild do elementu <ItemGroup>
.
Zachowywanie zestawów, typów i elementów członkowskich
Można przekazać trimmer plik opisu XML, który określa, które zestawy, typy i elementy członkowskie należy zachować.
Aby wykluczyć element członkowski z procesu przycinania podczas przycinania wszystkich zestawów, ustaw TrimmerRootDescriptor
element MSBuild w pliku projektu na plik XML, który definiuje elementy członkowskie do wykluczenia:
<ItemGroup>
<TrimmerRootDescriptor Include="MyRoots.xml" />
</ItemGroup>
Następnie plik XML używa formatu deskryptora trimmer do zdefiniowania elementów członkowskich do wykluczenia:
<linker>
<assembly fullname="MyAssembly">
<type fullname="MyAssembly.MyClass">
<method name="DynamicallyAccessedMethod" />
</type>
</assembly>
</linker>
W tym przykładzie plik XML określa metodę, która jest dynamicznie uzyskiwana przez aplikację, która jest wykluczona z przycinania.
Gdy zestaw, typ lub element członkowski jest wymieniony w kodzie XML, domyślna akcja to zachowywanie, co oznacza, że niezależnie od tego, czy trimmer uważa, że jest używany, czy nie, jest zachowywany w danych wyjściowych.
Uwaga
Tagi zachowywania są niejednoznacznie inkluzywne. Jeśli nie podasz następnego poziomu szczegółów, zostaną uwzględnione wszystkie elementy podrzędne. Jeśli zestaw jest wymieniony bez żadnych typów, wszystkie typy i składowe zestawu zostaną zachowane.
Oznacz zestaw jako bezpieczny przycinanie
Jeśli masz bibliotekę w projekcie lub jesteś deweloperem biblioteki wielokrotnego użytku i chcesz, aby trymer traktować zestaw jako przycinany, możesz oznaczyć zestaw jako bezpieczny przycinanie, dodając IsTrimmable
właściwość MSBuild do pliku projektu dla zestawu:
<PropertyGroup>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
Oznacza to zestaw jako "możliwość przycinania" i włącza ostrzeżenia dotyczące przycinania dla tego projektu. "Przycinanie" oznacza, że zestaw jest uznawany za zgodny z przycinaniem i nie powinien mieć ostrzeżeń dotyczących przycinania podczas kompilowania zestawu. W przypadku użycia w przyciętej aplikacji nieużywane elementy członkowskie zestawu są usuwane w końcowych danych wyjściowych.
IsTrimmable
Ustawienie właściwości MSBuild na true
w pliku projektu wstawia AssemblyMetadata
atrybut do zestawu:
[assembly: AssemblyMetadata("IsTrimmable", "True")]
Alternatywnie możesz dodać AssemblyMetadata
atrybut do zestawu bez konieczności dodawania IsTrimmable
właściwości MSBuild do pliku projektu dla zestawu.
Uwaga
IsTrimmable
Jeśli właściwość MSBuild jest ustawiona dla zestawu, spowoduje to zastąpienie atrybutuAssemblyMetadata("IsTrimmable", "True")
. Dzięki temu można zdecydować się na przycinanie zestawu, nawet jeśli nie ma atrybutu, lub wyłączyć przycinanie zestawu, który ma atrybut.
Pomijanie ostrzeżeń dotyczących analizy
Gdy trimmer jest włączony, usuwa il, który nie jest statycznie osiągalny. Aplikacje korzystające z odbicia lub innych wzorców tworzących zależności dynamiczne mogą zostać przerwane w wyniku. Aby ostrzec o takich wzorcach, podczas oznaczania zestawu jako bezpiecznego przycinania autorzy bibliotek powinni ustawić SuppressTrimAnalysisWarnings
właściwość MSBuild na false
:
<PropertyGroup>
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
</PropertyGroup>
Nie pomijanie ostrzeżeń analizy przycinania będzie zawierać ostrzeżenia dotyczące całej aplikacji, w tym własny kod, kod biblioteki i kod zestawu SDK.
Pokaż szczegółowe ostrzeżenia
Analiza przycinania generuje co najwyżej jedno ostrzeżenie dla każdego zestawu pochodzącego z PackageReference
elementu , co oznacza, że elementy wewnętrzne zestawu nie są zgodne z przycinaniem. Jako autor biblioteki, po oznaczeniu zestawu jako bezpiecznego przycinania należy włączyć poszczególne ostrzeżenia dla wszystkich zestawów, ustawiając TrimmerSingleWarn
właściwość MSBuild na false
:
<PropertyGroup>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
</PropertyGroup>
To ustawienie pokazuje wszystkie szczegółowe ostrzeżenia, zamiast zwijać je do pojedynczego ostrzeżenia dla każdego zestawu.