Поделиться через


Оптимизация разметки XAML

Анализ разметки XAML для создания объектов в памяти занимает много времени для сложного пользовательского интерфейса. Ниже приведены некоторые действия, которые можно сделать, чтобы улучшить синтаксический анализ и время загрузки разметки XAML и эффективность памяти приложения.

При запуске приложения ограничьте разметку XAML, загруженную только тем, что вам нужно для начального пользовательского интерфейса. Проверьте разметку на начальной странице (включая ресурсы страницы) и убедитесь, что вы не загружаете дополнительные элементы, которые не нужны сразу. Эти элементы могут поступать из различных источников, таких как словари ресурсов, элементы, которые изначально свернуты, и элементы, расположенные поверх других элементов.

Оптимизация XAML для повышения эффективности требует компромиссов; Для каждой ситуации не всегда существует единое решение. Здесь мы рассмотрим некоторые распространенные проблемы и укажите рекомендации, которые можно использовать, чтобы сделать правильные компромиссы для вашего приложения.

Свести к минимуму число элементов

Несмотря на то, что платформа XAML может отображать большое количество элементов, вы можете ускорить компоновку и рендеринг приложения, используя наименьшее количество элементов для достижения нужного визуального эффекта.

Выбранные варианты в том, как вы размещаете элементы управления пользовательским интерфейсом, влияют на количество элементов пользовательского интерфейса, создаваемых при запуске приложения. Дополнительные сведения об оптимизации макета см. в статье "Оптимизация макета XAML".

Число элементов крайне важно в шаблонах данных, так как каждый элемент создается повторно для каждого элемента данных. Для получения информации о сокращении количества элементов в списке или сетке см. раздел Сокращение элементов на элемент статьи в статье Оптимизация пользовательского интерфейса ListView и GridView.

Здесь мы рассмотрим некоторые другие способы уменьшения количества элементов, которые нужно загрузить при запуске.

Отложить создание элемента

Если разметка XAML содержит элементы, которые не отображаются сразу, можно отложить загрузку этих элементов, пока они не будут показаны. Например, можно отложить создание невидимого содержимого, такого как вторичная вкладка в интерфейсе, напоминающем вкладки. Кроме того, можно отобразить элементы в представлении сетки по умолчанию, но предоставить пользователю возможность просматривать данные в списке. Вы можете отложить загрузку списка до тех пор, пока это не потребуется.

Используйте атрибут x:Load вместо свойства Видимости , чтобы управлять отображением элемента. Если для элемента задано значение Свернутый, он пропускается во время процесса отрисовки, но вы по-прежнему оплачиваете затраты памяти на экземпляр объекта. Если вместо этого используется x:Load, платформа не создает экземпляр объекта до тех пор, пока не потребуется, поэтому затраты на память еще ниже. Недостаток заключается в том, что вы платите небольшую нагрузку на память (около 600 байт), если пользовательский интерфейс не загружен.

Замечание

Вы можете отложить загрузку элементов с помощью атрибута x:Load или x:DeferLoadStrategy. Атрибут x:Load доступен начиная с Windows 10 Creators Update (версия 1703, сборка ПАКЕТА SDK 15063). Минимальная версия, предназначенная для проекта Visual Studio, должна быть Windows 10 Creators Update (10.0, сборка 15063) для использования x:Load. Чтобы нацелиться на более ранние версии, используйте x:DeferLoadStrategy.

В следующих примерах показано различие в количестве элементов и памяти, используемых при использовании различных методов для скрытия элементов пользовательского интерфейса. ListView и GridView, содержащие идентичные элементы, помещаются в корневую сетку страницы. ListView не виден, но GridView показан. XAML в каждом из этих примеров создает один и тот же пользовательский интерфейс на экране. Мы используем средства Visual Studio для профилирования и оптимизации, чтобы проверить количество элементов и использование памяти.

Вариант 1 — неэффективный

Здесь объект ListView загружается, но не отображается, так как ширина равна 0. ListView и каждый из дочерних элементов создаются в визуальном дереве и загружаются в память.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE.-->
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <ListView x:Name="List1" Width="0">
        <ListViewItem>Item 1</ListViewItem>
        <ListViewItem>Item 2</ListViewItem>
        <ListViewItem>Item 3</ListViewItem>
        <ListViewItem>Item 4</ListViewItem>
        <ListViewItem>Item 5</ListViewItem>
        <ListViewItem>Item 6</ListViewItem>
        <ListViewItem>Item 7</ListViewItem>
        <ListViewItem>Item 8</ListViewItem>
        <ListViewItem>Item 9</ListViewItem>
        <ListViewItem>Item 10</ListViewItem>
    </ListView>

    <GridView x:Name="Grid1">
        <GridViewItem>Item 1</GridViewItem>
        <GridViewItem>Item 2</GridViewItem>
        <GridViewItem>Item 3</GridViewItem>
        <GridViewItem>Item 4</GridViewItem>
        <GridViewItem>Item 5</GridViewItem>
        <GridViewItem>Item 6</GridViewItem>
        <GridViewItem>Item 7</GridViewItem>
        <GridViewItem>Item 8</GridViewItem>
        <GridViewItem>Item 9</GridViewItem>
        <GridViewItem>Item 10</GridViewItem>
    </GridView>
</Grid>

Динамическое визуальное дерево с загруженным ListView. Общее число элементов для страницы равно 89.

Снимок экрана: визуальное дерево с представлением списка.

ListView и его дочерние элементы загружаются в память.

снимок экрана таблицы Managed Memory Test App 1 dot EXE, показывающей ListView и его дочерние элементы, загруженные в память.

Вариант 2 . Лучше

Здесь видимость ListView устанавливается в состояние "скрытая" (остальная часть XAML идентична исходной). ListView создается в визуальном дереве, но его дочерние элементы не создаются. Однако они загружаются в память, поэтому использование памяти идентично предыдущему примеру.

<ListView x:Name="List1" Visibility="Collapsed">

Живое визуальное дерево со свернутым элементом ListView. Общее число элементов для страницы равно 46.

снимок экрана древовидной структуры со свернутым представлением списка.

ListView и его дочерние элементы загружаются в память.

Обновленный снимок экрана: приложение для тестирования управляемой памяти 1 dot E X E, в котором отображается загрузка ListView и его дочерних элементов в память.

Вариант 3 — наиболее эффективный

Здесь ListView имеет атрибут x:Load, равный False (другой XAML идентичен исходному коду). ListView не создается в визуальном дереве или загружается в память при запуске.

<ListView x:Name="List1" Visibility="Collapsed" x:Load="False">

Динамическое визуальное дерево без загрузки ListView. Общее количество элементов для страницы равно 45.

визуальное дерево с представлением списка не загружено

ListView и его дочерние элементы не загружаются в память.

Визуальное дерево с представлением списка

Замечание

Количество элементов и использование памяти в этих примерах очень малы и показаны только для демонстрации концепции. В этих примерах затраты на использование x:Load больше экономии памяти, поэтому приложение не будет пользоваться преимуществами. Вы должны использовать средства профилирования в приложении, чтобы определить, будет ли ваше приложение использовать отложенную загрузку.

Использование свойств панели макета

Панели макета имеют свойство фона , поэтому нет необходимости помещать перед панелью прямоугольник только для его окраски.

неэффективный

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid>
    <Rectangle Fill="Black"/>
</Grid>

эффективное

<Grid Background="Black"/>

Панели макета также имеют встроенные свойства границы, поэтому не нужно помещать элемент Border на панель макета. Для получения дополнительных сведений и примеров, см. раздел об оптимизации макета XAML.

Использование изображений вместо векторных элементов

Если вы многократно используете один и тот же элемент на основе векторов, вместо этого становится более эффективно использовать элемент изображения. Элементы на основе векторов могут быть дороже, так как ЦП должен создавать каждый отдельный элемент отдельно. Файл изображения должен быть декодирован только один раз.

Оптимизация ресурсов и словарей ресурсов

Как правило, вы используете словари ресурсов для хранения ресурсов на относительно глобальном уровне, чтобы вы могли ссылаться на них в нескольких местах вашего приложения. Например, стили, кисти, шаблоны и т. д.

Как правило, мы оптимизировали ResourceDictionary, чтобы создавать экземпляры ресурсов только по запросу. Но существуют ситуации, которые следует избежать, чтобы ресурсы не создавались без необходимости.

Ресурсы с x:Name

Используйте атрибут x:Key для ссылки на ресурсы. Любой ресурс с атрибутом x:Name не будет пользоваться оптимизацией платформы; Вместо этого экземпляр создается сразу после создания ResourceDictionary. Это происходит из-за того, что x:Name сообщает платформе, что приложению требуется доступ к этому ресурсу, поэтому платформа должна создать ссылку на нее.

ResourceDictionary в элементе управления UserControl

ResourceDictionary, определенный внутри UserControl, несет штраф. Платформа создает копию такого ResourceDictionary для каждого экземпляра UserControl. Если у вас есть UserControl, который используется много, переместите ResourceDictionary из UserControl и поместите его на уровень страницы.

Область ресурса и области видимости словаря ресурсов

Если страница ссылается на пользовательский элемент управления или ресурс, определенный в другом файле, платформа также анализирует этот файл.

Здесь, так как InitialPage.xaml использует один ресурс из ExampleResourceDictionary.xaml, весь пример ExampleResourceDictionary.xaml должен быть проанализирован при запуске.

InitialPage.xaml.

<Page x:Class="ExampleNamespace.InitialPage" ...>
    <Page.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="ExampleResourceDictionary.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Page.Resources>

    <Grid>
        <TextBox Foreground="{StaticResource TextBrush}"/>
    </Grid>
</Page>

ExampleResourceDictionary.xaml.

<ResourceDictionary>
    <SolidColorBrush x:Key="TextBrush" Color="#FF3F42CC"/>

    <!--This ResourceDictionary contains many other resources that
        are used in the app, but are not needed during startup.-->
</ResourceDictionary>

Если вы используете ресурс на многих страницах вашего приложения, то хранение его в App.xaml будет хорошей практикой и позволит избежать дублирования. Но App.xaml анализируется при запуске приложения, поэтому любой ресурс, используемый только на одной странице (если эта страница не является начальной страницей), должен быть помещен в локальные ресурсы страницы. В этом примере показан App.xaml, содержащий ресурсы, используемые только одной страницей, которая не является начальной страницей. Это не обязательно увеличивает время запуска приложения.

App.xaml

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Application ...>
     <Application.Resources>
        <SolidColorBrush x:Key="DefaultAppTextBrush" Color="#FF3F42CC"/>
        <SolidColorBrush x:Key="InitialPageTextBrush" Color="#FF3F42CC"/>
        <SolidColorBrush x:Key="SecondPageTextBrush" Color="#FF3F42CC"/>
        <SolidColorBrush x:Key="ThirdPageTextBrush" Color="#FF3F42CC"/>
    </Application.Resources>
</Application>

InitialPage.xaml.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Page x:Class="ExampleNamespace.InitialPage" ...>
    <StackPanel>
        <TextBox Foreground="{StaticResource InitialPageTextBrush}"/>
    </StackPanel>
</Page>

SecondPage.xaml.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Page x:Class="ExampleNamespace.SecondPage" ...>
    <StackPanel>
        <Button Content="Submit" Foreground="{StaticResource SecondPageTextBrush}"/>
    </StackPanel>
</Page>

Чтобы сделать этот пример более эффективным, перейдите SecondPageTextBrush в SecondPage.xaml и перейдите ThirdPageTextBrush в ThirdPage.xaml. InitialPageTextBrush может оставаться в App.xaml , так как ресурсы приложения должны анализироваться при запуске приложения в любом случае.

Объедините несколько кистей, которые выглядят одинаково, в один ресурс

Платформа XAML пытается кэшировать часто используемые объекты, чтобы их можно было повторно использовать как можно чаще. Но XAML не может легко определить, объявленная кистью в одной части разметки, совпадает с кистью, объявленной в другом. В этом примере для демонстрации используется SolidColorBrush, однако гораздо чаще и важнее это бывает с GradientBrush. Также проверьте наличие кистей, использующих предопределенные цвета; например, "Orange" и "#FFFFA500" один и тот же цвет.

Неэффективный.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Page ... >
    <StackPanel>
        <TextBlock>
            <TextBlock.Foreground>
                <SolidColorBrush Color="#FFFFA500"/>
            </TextBlock.Foreground>
        </TextBlock>
        <Button Content="Submit">
            <Button.Foreground>
                <SolidColorBrush Color="#FFFFA500"/>
            </Button.Foreground>
        </Button>
    </StackPanel>
</Page>

Чтобы исправить дублирование, определите кисть как ресурс. Если элементы управления на других страницах используют ту же кисть, переместите её в App.xaml.

Эффективность.

<Page ... >
    <Page.Resources>
        <SolidColorBrush x:Key="BrandBrush" Color="#FFFFA500"/>
    </Page.Resources>

    <StackPanel>
        <TextBlock Foreground="{StaticResource BrandBrush}" />
        <Button Content="Submit" Foreground="{StaticResource BrandBrush}" />
    </StackPanel>
</Page>

Свести к минимуму переполнение

Перезарисовка происходит, когда несколько объектов рисуется в одном и том же пикселях экрана. Обратите внимание, что иногда существует компромисс между этим руководством и желанием свести к минимуму количество элементов.

Используйте DebugSettings.IsOverdrawHeatMapEnabled как визуальный диагностический инструмент. Вы можете обнаружить, что некоторые объекты прорисовываются, о которых вы не знали, что они в сцене.

Прозрачные или скрытые элементы

Если элемент не отображается, так как он прозрачный или скрытый за другими элементами, и он не вносит вклад в макет, а затем удалите его. Если элемент не отображается в исходном визуальном состоянии, но отображается в других визуальных состояниях, используйте x:Load для управления его состоянием или установите для параметра Видимость значение Свернуто непосредственно на элементе и измените это значение на Видимость в соответствующих состояниях. Существуют исключения для этой эвристики: как правило, значение свойства в большинстве визуальных состояний лучше всего задать локально на элементе.

Составные элементы

Используйте составной элемент вместо слоя нескольких элементов для создания эффекта. В этом примере результатом является двухцветная фигура, где верхняя половина черная (из-за фона сетки ), а нижняя половина серая (из-за полупрозрачного белого прямоугольника , альфа-смешанного с черным фоном сетки ). Здесь заполняются 150 пикселей цвета%, необходимых для достижения результата.

Неэффективный.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid Background="Black">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Rectangle Grid.Row="1" Fill="White" Opacity=".5"/>
</Grid>

Эффективность.

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Rectangle Fill="Black"/>
    <Rectangle Grid.Row="1" Fill="#FF7F7F7F"/>
</Grid>

Панели расположения

Панель макета может иметь два назначения: цвет области и выделение дочерних элементов. Если элемент, находящийся дальше в z-порядке, уже закрашивает область, то передней панели макета не нужно красить эту область: вместо этого она может просто сосредоточиться на расположении своих дочерних элементов. Вот пример.

Неэффективный.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<GridView Background="Blue">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid Background="Blue"/>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

Эффективность.

<GridView Background="Blue">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid/>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

Если Сетка должна быть восприимчивой к касаниям, установите фон прозрачным.

Границы

Используйте элемент Border для рисования границы вокруг объекта. В этом примере сетка используется как импровизированная граница вокруг TextBox. Но все пиксели в центральной ячейке перерисованы.

Неэффективный.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid Background="Blue" Width="300" Height="45">
    <Grid.RowDefinitions>
        <RowDefinition Height="5"/>
        <RowDefinition/>
        <RowDefinition Height="5"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="5"/>
        <ColumnDefinition/>
        <ColumnDefinition Width="5"/>
    </Grid.ColumnDefinitions>
    <TextBox Grid.Row="1" Grid.Column="1"></TextBox>
</Grid>

Эффективность.

<Border BorderBrush="Blue" BorderThickness="5" Width="300" Height="45">
    <TextBox/>
</Border>

Поля

Помните о полях. Два соседних элемента будут перекрываться (возможно, случайно), если отрицательные поля расширяются в границы отрисовки другого и вызывают перезарисовку.

Кэширование статического содержимого

Другой источник перезарисовки — это форма, созданная из множества перекрывающихся элементов. Если вы установите CacheMode в BitmapCache на UIElement, содержащий составную фигуру, платформа отрисует элемент в растровое изображение один раз, а затем будет использовать это растровое изображение для каждого кадра вместо повторной отрисовки.

Неэффективный.

<Canvas Background="White">
    <Ellipse Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Left="21" Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Top="13" Canvas.Left="10" Height="40" Width="40" Fill="Blue"/>
</Canvas>

Схема Венна с тремя твердыми кругами

Изображение выше является результатом, а вот карта перерисованных регионов. Темный красный цвет указывает на более высокие объемы передравов.

Схема Венна с перекрывающимися областями

Эффективность.

<Canvas Background="White" CacheMode="BitmapCache">
    <Ellipse Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Left="21" Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Top="13" Canvas.Left="10" Height="40" Width="40" Fill="Blue"/>
</Canvas>

Обратите внимание на использование CacheMode. Не используйте этот метод, если какие-либо из подфигур анимируются, так как кэш растровых изображений, скорее всего, необходимо будет пересоздавать каждый кадр, что сведет на нет всю пользу.

Использование XBF2

XBF2 — это двоичное представление разметки XAML, которая позволяет избежать всех затрат на анализ текста во время выполнения. Он также оптимизирует двоичный код для ускоренной загрузки и создания дерева и позволяет использовать "быстрый путь" для типов XAML, чтобы уменьшить затраты на создание кучи и объектов, например, VSM, ResourceDictionary, Styles и т. д. Он полностью отображается в памяти, поэтому загрузка и чтение страницы XAML не требуют использования памяти кучи. Кроме того, это сокращает объем дискового пространства хранимых страниц XAML в appx. XBF2 — это более компактное представление, и это может сократить объем дискового пространства сравнительного файла XAML/XBF1 до 50%. Например, встроенное приложение "Фотографии" составило около 60% сокращение после преобразования в XBF2 с около 1 мб активов XBF1 до ~400 кб активов XBF2. Мы также видели, что приложения получают преимущества в диапазоне от 15 до 20% в ЦП и от 10 до 15% в Win32 heap.

Встроенные элементы управления и словари XAML, которые предоставляет платформа, уже полностью включены в XBF2. Для собственного приложения убедитесь, что файл проекта объявляет TargetPlatformVersion 8.2 или более поздней версии.

Чтобы проверить наличие XBF2, откройте приложение в двоичном редакторе; 12-й и 13-й байты — 00 02, если у вас есть XBF2.