Udostępnij za pośrednictwem


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:

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 trueprogram .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 falsewartoś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 falsewartoś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 falseprogram .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 truewartość .
MauiEnableXamlCBindingWithSourceCompilation W przypadku ustawienia wartości trueprogram .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 falsewartoś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 nazwie Microsoft.Maui.RuntimeFeature.IsIVisualAssemblyScanningEnabled.
  • Właściwość MauiShellSearchResultsRendererDisplayMemberNameSupported MSBuild ma równoważny AppContext przełącznik o nazwie Microsoft.Maui.RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported.
  • Właściwość MauiQueryPropertyAttributeSupport MSBuild ma równoważny AppContext przełącznik o nazwie Microsoft.Maui.RuntimeFeature.IsQueryPropertyAttributeSupported.
  • Właściwość MauiImplicitCastOperatorsUsageViaReflectionSupport MSBuild ma równoważny AppContext przełącznik o nazwie Microsoft.Maui.RuntimeFeature.IsImplicitCastOperatorsUsageViaReflectionSupported.
  • Właściwość _MauiBindingInterceptorsSupport MSBuild ma równoważny AppContext przełącznik o nazwie Microsoft.Maui.RuntimeFeature.AreBindingInterceptorsSupported.
  • Właściwość MauiEnableXamlCBindingWithSourceCompilation MSBuild ma równoważny AppContext przełącznik o nazwie Microsoft.Maui.RuntimeFeature.MauiEnableXamlCBindingWithSourceCompilationEnabled.
  • Właściwość MauiHybridWebViewSupported MSBuild ma równoważny AppContext przełącznik o nazwie Microsoft.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 strings 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 PackageReferenceelementu , 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.

Zobacz też