次の方法で共有


XAML マークアップを最適化する

XAML マークアップを解析してメモリ内にオブジェクトを構築することは、複雑な UI では時間がかかります。 XAML マークアップの解析と読み込み時間、およびアプリのメモリ効率を向上させるために実行できる操作をいくつか次に示します。

アプリの起動時に、読み込まれる XAML マークアップを、最初の UI に必要なものだけに制限します。 最初のページ (ページ リソースを含む) のマークアップを調べて、すぐに必要のない追加の要素を読み込んでいないことを確認します。 これらの要素は、リソース ディクショナリ、最初に折りたたまれた要素、他の要素より上に描画される要素など、異なるソースから取得できます。

効率のために XAML を最適化するには、トレードオフを行う必要があります。すべての状況に対してソリューションが 1 つ存在するとは限りません。 ここでは、いくつかの一般的な問題を見て、アプリの適切なトレードオフを行うために使用できるガイドラインを提供します。

要素数を最小限に抑える

XAML プラットフォームでは多数の要素を表示できますが、必要なビジュアルを実現するために最も少ない数の要素を使用して、アプリのレイアウトとレンダリングを高速化できます。

UI コントロールをレイアウトする方法で行う選択は、アプリの起動時に作成される UI 要素の数に影響します。 レイアウトの最適化の詳細については、「XAML レイアウトを最適化する」を参照してください。

データ 項目ごとに各要素が再度作成されるため、要素数はデータ テンプレートで非常に重要です。 リストまたはグリッドの要素数を減らす方法については、「ListView と GridView UI の最適化」 記事の 項目ごとの要素数の削減 参照してください。

ここでは、起動時にアプリが読み込む必要がある要素の数を減らす他のいくつかの方法について説明します。

アイテムの作成を延期する

すぐに表示しない要素が XAML マークアップに含まれている場合は、表示されるまでそれらの要素の読み込みを延期できます。 たとえば、タブに似た UI のセカンダリ タブなど、表示されないコンテンツの作成を遅らせることができます。 または、既定ではグリッド ビューに項目を表示できますが、ユーザーがリスト内のデータを表示するオプションを指定することもできます。 必要になるまでリストの読み込みを遅らせることができます。

Visibility プロパティの代わりに、x:Load 属性 を使用して、要素が表示されるタイミングを制御します。 要素の可視性が 折りたたまれたに設定されている場合、レンダー パス中はスキップされますが、オブジェクト インスタンスのコストはメモリ内で引き続き支払われます。 代わりに x:Load を使用する場合、フレームワークは必要になるまでオブジェクト インスタンスを作成しないため、メモリ コストはさらに低くなります。 欠点は、UI が読み込まれていないときに小さなメモリ オーバーヘッド (約 600 バイト) を支払うという点です。

x:Load または x:DeferLoadStrategy 属性 使用して、要素の読み込みを遅延させることができます。 x:Load 属性は、Windows 10 Creators Update (バージョン 1703、SDK ビルド 15063) 以降で使用できます。 x:Load を使用するには、Visual Studio プロジェクトの対象となる最小バージョンが Windows 10 Creators Update (10.0、ビルド 15063) 必要があります。 以前のバージョンを対象にするには、x:DeferLoadStrategy を使用します。

次の例は、UI 要素を非表示にするためにさまざまな手法を使用する場合の要素数とメモリ使用量の違いを示しています。 ListView と GridView は同じ項目を含み、ページのルート Grid に配置されています。 ListView は表示されませんが、GridView が表示されます。 これらの各例の XAML では、画面上に同じ UI が生成されます。 Visual Studio の ツールを使用して、プロファイリングとパフォーマンスの を行い、要素の数とメモリ使用量を確認します。

オプション 1 - 非効率的

ここでは ListView が読み込まれますが、Width が 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 とその子オブジェクトはメモリに読み込まれます。

メモリに読み込まれている ListView とその子を示す Managed Memory Test App 1.EXE のテーブルのスクリーンショット。

オプション 2 - より良い

ここでは、ListView の可視性が折りたたまれている状態に設定されています (他の XAML は元の XAML と同じです)。 ListView はビジュアル ツリーに作成されますが、その子要素は作成されません。 ただし、メモリに読み込まれるため、メモリの使用は前の例と同じです。

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

ListView が折りたたまれているライブ ビジュアル ツリー。 ページの要素数の合計は 46 です。

ja-JP: 折りたたまれたリストビューを含むビジュアルツリーのスクリーンショット。

ListView とその子オブジェクトはメモリに読み込まれます。

ListView とその子がメモリに読み込まれているマネージド メモリ テスト アプリ 1 ドット E X E テーブルの更新されたスクリーンショット。

オプション 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 が個々の要素を個別に作成する必要があるため、コストが高くなる可能性があります。 イメージ ファイルは 1 回だけデコードする必要があります。

リソースとリソース ディクショナリを最適化する

通常、リソース ディクショナリ を使用して、アプリ内の複数の場所で参照するリソースを、ややグローバル レベルで格納します。 たとえば、スタイル、ブラシ、テンプレートなどです。

一般に、要求されない限り、リソースをインスタンス化しないようにResourceDictionary を最適化しました。 ただし、リソースが不必要にインスタンス化されないように、避ける必要がある状況があります。

x:Name のリソース

リソースを参照するには、x:Key 属性 を使用します。 x:Name 属性 を持つリソースは、プラットフォームの最適化の恩恵を受けなくなります。代わりに、ResourceDictionary が作成されるとすぐにインスタンス化されます。 この現象は、x:Name によってアプリがこのリソースへのフィールドアクセスを必要としていることをプラットフォームに示すため、プラットフォームが参照先を作成しなければならないために生じます。

UserControl の ResourceDictionary

UserControl 内で定義された ResourceDictionary にはペナルティが課されます。 プラットフォームは、UserControl のすべてのインスタンスに対して、このような ResourceDictionary のコピーを作成します。 多く使用されている UserControl がある場合は、ResourceDictionary を UserControl から移動し、ページ レベルに配置します。

ResourceとResourceDictionaryのスコープ

ページがユーザー コントロールまたは別のファイルで定義されているリソースを参照している場合、フレームワークはそのファイルも解析します。

ここでは、InitialPage.xamlExampleResourceDictionary.xamlのリソースを 1 つ使用するため、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 はアプリの起動時に解析されるため、(そのページが最初のページでない限り) 1 つのページでのみ使用されるリソースは、ページのローカル リソースに配置する必要があります。 この例 App.xaml、1 つのページでのみ使用されるリソースを含みます (これは初期ページではありません)。 これにより、アプリの起動時間が不必要に増加します。

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>

この例をより効率的にするには、SecondPage.xaml に移動し、 を ThirdPage.xamlに移動します。 どのような場合でも、アプリケーション リソースはアプリの起動時に解析する必要があるため、 は App.xaml 残ることができます。

同じように見える複数のブラシを 1 つのリソースに統合する

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折りたたまれた に設定し、適切な状態でその値を 表示 に変更します。 このヒューリスティックには例外があります。一般に、プロパティがほとんどの表示状態で持つ値は、要素に対してローカルに設定するのが最適です。

複合要素

複数の要素を重ねて効果を作成する代わりに、複合要素を使用します。 この例では、上半分が黒(グリッドの背景から)、下半分が灰色 (半透明の白 四角形からグリッドの黒い背景にアルファブレンドされた)、2 トーンの図形になります。 ここで、結果を達成するために必要なピクセルのうち、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>

レイアウトパネル

レイアウト パネルには、領域の色付けと子要素のレイアウトの 2 つの目的があります。 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>

Grid をヒット テスト可能にする必要がある場合は、背景の値を透明に設定します。

国境

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>

余白

余白に注意してください。 負の余白が別のレンダー境界に拡張され、オーバードローが発生した場合、隣接する 2 つの要素が重なることがあります (誤って)。

静的コンテンツをキャッシュする

オーバードローのもう 1 つのソースは、多くの重なり合う要素から作られた図形です。 CacheMode を複合図形を含む UIElement で BitmapCache を に設定すると、プラットフォームは要素をビットマップに 1 回レンダリングし、オーバードローではなく各フレームでそのビットマップを使用します。

非効率的です。

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

3 つの実線を含むベン図

上の図は結果ですが、こちらはオーバードロー領域のマップです。 赤が濃いほど、過剰な描画量が多いことを示します。

重複する領域を示すベン図

効率的です。

<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 はよりコンパクトな表現であり、比較 XAML/XBF1 ファイルのディスク占有領域を最大 50%削減できます。 たとえば、組み込みのフォト アプリでは、XBF2 への変換後に約 60% 削減され、XBF1 資産の約 1 mb から最大 400 kb の XBF2 資産に減少しました。 また、アプリは CPU で 15 から 20%、Win32 ヒープでは 10 から 15% の恩恵を受けるのも見られました。

フレームワークが提供する XAML 組み込みコントロールとディクショナリは、既に完全に XBF2 対応です。 独自のアプリの場合は、プロジェクト ファイルで TargetPlatformVersion 8.2 以降が宣言されていることを確認します。

XBF2 があるかどうかを確認するには、バイナリ エディターでアプリを開きます。XBF2 がある場合、12 番目と 13 番目のバイトは 00 02 です。