共用方式為


最佳化您的 XAML 標記

剖析 XAML 標記以建構記憶體中的物件,對於複雜的 UI 來說相當耗時。 以下是您可以執行的一些動作,以改善 XAML 標記的剖析和載入時間,以及應用程式的記憶體效率。

在應用程式啟動時,將載入的 XAML 標記限制為僅您初始 UI 所需的內容。 檢查初始頁面中的標記(包括頁面資源),並確認您不會立即載入不需要的額外元素。 這些元素可能來自各種來源,例如資源字典、最初收合的元素,以及繪製在其他元素上的元素。

優化 XAML 以提高效率需要取捨;不一定是每個情況的單一解決方案。 在這裡,我們將探討一些常見問題,並提供可用來為您的應用程式進行適當取捨的指導方針。

最小化項目計數

雖然 XAML 平臺能夠顯示大量的元素,但您可以使用最少的元素數目來達成您想要的視覺效果,讓您的 app 配置和轉譯更快。

您在如何設定 UI 控制件時所做的選擇會影響應用程式啟動時所建立的 UI 元素數目。 如需優化版面設定的詳細資訊,請參閱 優化 XAML 設定

元素的數量在數據模板中非常重要,因為每個數據項會重新創建元素。 如需減少清單或方格中元素計數的資訊,請參閱 listView 和 GridView UI 優化 一文 每個專案的元素縮減

在這裡,我們將探討一些其他方式,以減少應用程式在啟動時必須載入的項目數目。

延遲項目建立

如果您的 XAML 標記包含您未立即顯示的元素,您可以延遲載入這些元素,直到它們被顯示為止。 例如,您可以延遲建立非可見的內容,例如類似索引標籤的UI中的次要索引標籤。 預設情況下,您可能會在方格檢視中顯示項目,但提供選項讓使用者改為查看數據的清單視圖。 您可以延遲載入清單,直到需要為止。

使用 x:Load 屬性,而不是 Visibility 屬性來控制何時顯示元素。 當項目的可見性設定為 Collapsed時,則會在轉譯階段期間略過它,但您仍會在記憶體中支付物件實例成本。 當您改用 x:Load 時,架構在需要之前不會建立對象實例,因此記憶體成本甚至更低。 缺點是當使用者介面(UI)未載入時,您會承受一定的記憶體額外開銷(大約 600 個字節)。

備註

您可以使用 x:Loadx:DeferLoadStrategy 屬性來延遲載入元素。 從 Windows 10 Creators Update (版本 1703 SDK 組建 15063) 開始,即可使用 x:Load 屬性。 Visual Studio 專案的目標最小版本必須是 Windows 10 Creators Update (10.0, 組建 15063),才能使用 x:Load。 若要以舊版為目標,請使用 x:DeferLoadStrategy。

下列範例顯示當使用不同技術來隱藏UI元素時,元素計數和記憶體使用量的差異。 ListView 與包含相同項目的 GridView 被放置在一個頁面的根 Grid 中。 ListView 不可見,但會顯示 GridView。 這些範例中的每個 XAML 都會在畫面上產生相同的 UI。 我們使用 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 E X E 數據表的螢幕快照,其中顯示 ListView 及其子系已載入記憶體中。

選項 2 - 更好

在這裡,ListView 的 Visibility 會設定為隱藏(其他 XAML 與原始 XAML 相同)。 ListView 是在可視化樹狀結構中建立,但其子專案不是。 不過,它們會載入記憶體中,因此記憶體使用方式與先前的範例相同。

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

即時可視化已折疊的 ListView 樹狀結構。 頁面的元素計數總計為 46。

具有折疊清單檢視之可視化樹狀結構的螢幕快照。

ListView 及其子系會載入記憶體中。

已更新的 Managed Memory Test App 1.exe 表格的螢幕快照,顯示 ListView 及其子項已載入記憶體中。

選項 3 - 最有效率

在這裡,ListView 的 x:Load 屬性設定為 False (另一個 XAML 與原始 XAML 相同)。 ListView 不會在可視化樹狀結構中建立,也不會在啟動時載入記憶體中。

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

ListView 未載入時的即時視覺樹。 頁面的元素計數總計為 45。

未載入清單檢視的可視化樹狀結構

ListView 及其子系不會載入記憶體中。

清單檢視的視覺化樹狀結構

備註

這些範例中的元素計數和記憶體使用非常小,而且只會示範概念。 在這些範例中,使用 x:Load 的額外負荷大於節省記憶體,因此應用程式不會受益。 您應該使用應用程式上的分析工具來判斷您的應用程式是否會受益於延後載入。

使用版面屬性

版面配置面板具有 Background 屬性,因此不需要將 Rectangle 放在面板前面,即可將它著色。

效率不佳

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

有效率

<Grid Background="Black"/>

版面配置面板也有內建框線屬性,因此您不需要在版面配置面板周圍放置 Border 元素。 如需詳細資訊和範例,請參閱 優化 XAML 配置

使用影像取代向量型元素

如果您重複使用相同的向量型元素足夠時間,則改用 Image 元素會變得更有效率。 以向量為基礎的元素可能更昂貴,因為 CPU 必須個別建立每個個別元素。 映像檔只需要譯碼一次。

優化資源和資源字典

您通常會使用 資源字典,以某種全域層級儲存您想要在應用程式中多個位置參考的資源。 例如,樣式、筆刷、範本等等。

一般而言,我們已經改進 ResourceDictionary,除非需要資源,否則不會實例化資源。 但在某些情況下,您應該避免以免資源被不必要地具現化。

具有 x:Name 的資源

使用 x:Key 屬性 來參考您的資源。 任何具有 x:Name 屬性的資源 都無法受益於平台優化;相反地,它會在建立 ResourceDictionary 時立即具現化。 這是因為 x:Name 會告訴平臺您的應用程式需要此資源的欄位存取權,因此平臺必須生成用於參考的對象。

UserControl 中的 ResourceDictionary

UserControl 內定義的 ResourceDictionary 會受到處罰。 平臺會為每個 UserControl 實例建立這類 ResourceDictionary 的複本。 如果您有大量使用的UserControl,請將ResourceDictionary從UserControl移出,並將它放在頁面層級。

Resource 和 ResourceDictionary 範圍

如果頁面參考不同檔案中定義的使用者控件或資源,則架構也會剖析該檔案。

在這裡,因為 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.xamlInitialPageTextBrush 可以保留在 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 來控制其狀態,或將 Visibility 設定為 Collapsed,並在適當的狀態中將其值變更為 Visible。 此啟發性原則會有例外狀況:一般而言,屬性在大多數視覺狀態中的值應該在元素本地設定。

複合元素

使用複合元素,而不是分層多個元素來建立效果。 在此範例中,結果是雙色圖形,其中上半部為黑色(來自 網格背景),而下半部為灰色(由半透明白色 矩形網格的黑色背景 alpha 混合而成)。 正在填入達到結果所需的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 元素來繪製物件周圍的框線。 在此範例中,Grid 被用作 文本框的臨時邊框。 但中央儲存格中的所有像素都會重繪。

效率不佳。

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

頁邊距

請注意邊距。 如果負邊界延伸到另一個渲染界限並造成過度繪製,相鄰的兩個元素可能會(意外地)重疊。

快取靜態內容

由許多重疊元素組成的圖形是造成過度繪製的另一個原因。 如果您將包含複合圖形的 UIElementCacheMode 設定為 BitmapCache,則平台會將該元素轉譯成位圖一次,然後在每幀中使用該位圖,而不是重複繪製。

效率不佳。

<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>

維恩圖與三個實心圓

上圖是結果,但以下是透支區域的地圖。 紅色越深表示過度繪製的數量越高。

顯示重迭區域的 Venn 圖表

有效率。

<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 頁面時,沒有堆積記憶體使用量。 此外,它可減少appx中儲存 XAML 頁面的磁碟使用量。 XBF2 是更精簡的表示法,最多可以降低 50%比較 XAML/XBF1 檔案的磁碟使用量。 例如,在轉換成 XBF2 之後,內建的 Photos 應用程式在轉換成 XBF2 後,大約在 60% 減少,從大約 1 mb 的 XBF1 資產降至大約 400 kb 的 XBF2 資產。 我們也看到應用程式從 CPU 中的 15 到 20%,以及 Win32 堆積中的 10 到 15%。

架構所提供的 XAML 內建控件和字典已經完全啟用 XBF2。 針對您自己的應用程式,請確定您的項目檔宣告 TargetPlatformVersion 8.2 或更新版本。

若要檢查您是否有 XBF2,請在二進位編輯器中開啟您的應用程式;如果您有 XBF2,則第 12 個和 13 個字節是 00 02。