建立具有平滑動畫、高幀速率和高效能媒體擷取和播放的通用 Windows 平臺 (UWP) 應用程式。
讓動畫順暢
UWP app的重要層面是順暢的互動。 這包括能夠「貼合手指」的觸控操作、流暢的轉換和動畫,以及提供輸入回饋的小動作。 在 XAML 架構中,有一個稱為組合線程的線程,專門用於應用程式視覺元素的撰寫和動畫。 由於合成線程與 UI 線程分離(UI 線程負責執行框架和開發者程式碼),因此無論佈局過程多複雜或計算多繁重,應用程式都能保持一致的幀速率和平滑的動畫效果。 本節說明如何使用組合線程,讓應用程式的動畫保持流暢。 如需動畫的詳細資訊,請參閱 動畫概觀。 若要瞭解如何在執行密集計算時增加應用程式的回應性,請參閱 讓 UI 線程保持回應。
使用獨立動畫而非相依動畫
獨立動畫可以在建立時從頭到尾計算,因為動畫屬性的變更不會影響場景中的其餘物件。 因此,獨立動畫可以在組合線程上執行,而不是UI線程。 這可確保它們保持順暢,因為組合線程會以一致的步調更新。
所有這些類型的動畫保證都是獨立的:
使用關鍵幀的物件動畫
零持續時間動畫
Canvas.Left 和 Canvas.Top 屬性的動畫
UIElement.Opacity 屬性的動畫
以 solidColorBrush.Color 子屬性
為目標時, 類型的動畫Brush 對下列 UIElement,其 屬性中的動畫,當這些傳回值類型的子屬性為目標時:
相依賴的動畫會影響版面配置,因此沒有來自UI執行緒的額外輸入,就無法計算。 依賴動畫包括對屬性如 寬度 和 高度的修改。 根據預設,相依動畫不會執行,需應用程式開發人員主動啟用。 啟用時,如果UI執行緒未被阻塞,它們就會順利執行,但如果框架或應用程式在UI執行緒上處理許多其他工作,它們就會開始出現卡頓。
XAML 架構中的所有動畫預設都是獨立的,但是您可以採取一些動作來停用此優化。 請特別注意這些案例:
- 將 EnableDependentAnimation 屬性設定為允許在 UI 線程上執行相依動畫。 將這些動畫轉換成獨立版本。 例如,動畫化 的 ScaleTransform.ScaleX 和 的 ScaleTransform.ScaleY,而不是物件的 寬度 和 高度。 不要害怕縮放影像和文字等物件。 只有在 ScaleTransform 動畫播放期間,架構才會套用雙線性縮放。 影像/文字會以最終大小重新重設,以確保一律清楚。
- 進行逐幀更新,這些更新本質上是依賴性動畫。 其中一個範例是在 CompositionTarget.Rendering 事件的處理程式中套用轉換。
- 在具有 CacheMode 屬性並設定為 BitmapCache的元素中,運行任何被視為獨立的動畫。 這視為相依,因為快取必須針對每個畫面重新點陣化。
不要動態化 WebView 或 MediaPlayerElement
XAML 架構不會直接呈現 WebView 控件中的 Web 內容,因此需要額外的工作來將其與場景的其他部分組合在一起。 這些額外的工作在螢幕上以動畫呈現控件時會累積,可能會引發一些同步問題(例如:HTML內容可能無法與頁面上其餘的XAML內容同步移動)。 當您需要讓 WebView 控件產生動畫效果時,請在動畫期間將該控件與 WebViewBrush 交換。
為 MediaPlayerElement 進行動畫化同樣是個壞主意。 除了效能損耗之外,它還可能導致播放中的影片出現影像撕裂或其他影像異常。
附注 本文中的建議適用於 MediaPlayerElement,也適用於 MediaElement。 MediaPlayerElement 僅適用於 Windows 10 版本 1607,因此如果您要為舊版 Windows 建立應用程式,則必須使用 MediaElement。
應節制使用無限動畫
大部分的動畫都會在指定的時間量執行,但將 Timeline.Duration 屬性設定為 Forever 可無限期執行動畫。 我們建議將無限動畫的使用降到最低,因為它們會持續耗用 CPU 資源,並可防止 CPU 進入低功率或閑置狀態,使其更快耗盡電源。
將處理程式新增至 CompositionTarget.Rendering 類似於執行無限動畫。 一般而言,只有在有工作要做時,UI 執行緒才會啟動,但新增此事件的處理程式會強制它執行每一幀。 當沒有工作需要時,請移除處理程式,並在再次需要時重新註冊它。
使用動畫庫
Windows.UI.Xaml.Media.Animation 命名空間包含高效能、平滑動畫的連結庫,其外觀和風格與其他 Windows 動畫一致。 相關類別的名稱中有「主題」,如 動畫概觀所述。 此連結庫支援許多常見的動畫案例,例如以動畫顯示應用程式的第一個檢視,以及建立狀態和內容轉換。 建議您盡可能使用此動畫庫來提升UWPUI的效能和一致性。
注意 動畫庫無法讓所有可能的屬性產生動畫效果。 如需動畫庫不適用的 XAML 案例,請參閱 腳本動畫。
獨立地動畫化 CompositeTransform3D 屬性
您可以獨立控制 CompositeTransform3D 的每個屬性的動畫效果,因此僅套用您需要的動畫。 如需範例和詳細資訊,請參閱 UIElement.Transform3D。 如需動畫轉換的詳細資訊,請參閱 故事板動畫 和 關鍵幀和緩動函數動畫。
優化媒體資源
音訊、視訊和影像是大部分應用程式所使用的令人信服的內容形式。 當媒體擷取速率增加,內容從標準定義移至高定義時,儲存、譯碼和播放此內容所需的資源數量會增加。 XAML 架構是以新增至 UWP 媒體引擎的最新功能為基礎,讓應用程式免費取得這些改進功能。 我們在這裡說明一些額外的技巧,可讓您在UWP app 中充分利用媒體。
釋出媒體串流
應用程式通常使用的媒體檔案是最常見且昂貴的資源。 因為媒體檔案資源可以大幅增加應用程式記憶體的佔用量,所以您必須記得在應用程式完成使用後立即釋放媒體控制代碼。
例如,如果您的 app 使用 RandomAccessStream 或 IInputStream 物件,請務必在應用程式完成使用時呼叫 物件的 close 方法,以釋放基礎物件。
盡可能顯示全螢幕視訊播放
在UWP應用程式中,一律使用 MediaPlayerElement 上的 IsFullWindow 屬性來啟用和停用完整視窗轉譯。 這可確保在媒體播放期間使用系統層級優化。
XAML 架構可以在影片內容為唯一渲染的情況下,優化其顯示效果,進而帶來更省電且幀速率更高的體驗。 對於最高效的媒體播放,請將 MediaPlayerElement 的大小設定為螢幕的寬度和高度,不顯示其他 XAML 元素。
在佔據螢幕完整寬度和高度的 MediaPlayerElement 上重疊 XAML 元素是有正當理由的,例如用於顯示隱藏式輔助字幕或短暫的傳輸控制。 務必要在不需要這些元素時隱藏它們(設定 Visibility="Collapsed"),以恢復媒體播放的最佳效率。
關閉顯示器以節省電源
若要防止不再偵測到使用者動作時停用顯示,例如當應用程式正在播放視訊時,您可以呼叫 DisplayRequest.RequestActive。
若要節省電源和電池使用時間,您應該呼叫 DisplayRequest.RequestRelease,以在不再需要顯示要求時立即釋放顯示要求。
以下是您應該解除顯示請求的一些情況:
- 影片播放暫停,原因可能是用戶操作、緩衝或因頻寬有限而調整。
- 播放停止。 例如,影片已完成播放或簡報結束。
- 發生播放錯誤。 例如,網路連線問題或損毀的檔案。
將其他元素放在內嵌視訊的一側
應用程式通常會提供內嵌檢視,其中影片會在頁面內播放。 現在您顯然失去了全螢幕優化,因為 MediaPlayerElement 不是頁面的大小,並且還有其他 XAML 物件被繪製出來。 請注意,在 MediaPlayerElement周圍繪製框線,以不小心進入此模式。
視訊處於內嵌模式時,請勿在其上繪製 XAML 元素。 如果您這麼做,框架將被迫執行一些額外的工作來組成場景。 將傳輸控件放在內嵌媒體元素下方,而不是放在視訊上方,是優化這種情況的好範例。 在此影像中,紅色列表示一組傳輸控件(播放、暫停、停止等)。
請勿將這些控件放在非全螢幕的媒體上。 相反地,將傳輸控件放在轉譯媒體的區域以外的地方。 在下一個影像中,控件會放在媒體下方。
延遲設定 MediaPlayerElement 的來源
媒體引擎是昂貴的物件,XAML 架構會延遲載入 DLL 並延後建立大型物件,盡可能拖延這些作業的時間。 MediaPlayerElement 在透過 source Source 屬性設定來源之後,強制執行這項工作。 當使用者真的準備好播放媒體時,只要盡可能多地延遲與 MediaPlayerElement 相關聯的大部分成本,就會設定此設定。
設定 MediaPlayerElement 的 PosterSource
設定 MediaPlayerElement.PosterSource,可讓 XAML 釋放否則會被使用的某些 GPU 資源。 此 API 可讓應用程式盡可能少地使用記憶體。
改善媒體清除
對於媒體平台來說,滑動功能的開發總是一項艱巨的任務,難以達到高效回應。 一般而言,人們會藉由變更 Slider 的值來完成這項作業。 以下是一些如何盡可能有效率的秘訣:
- 根據查詢 Position 的定時器,更新 滑桿 的值,這些查詢在 MediaPlayerElement.MediaPlayer上進行。 請務必為您的定時器使用合理的更新頻率。 Position 屬性只會在播放期間每 250 毫秒更新一次。
- Slider 上的步頻大小必須隨著視訊長度進行調整。
- 訂閱滑桿上的
PointerPressed PointerMoved 事件,以在使用者拖曳滑桿的拇指時,將 playbackRate 屬性PointerReleased 設定為 0。 - 在 PointerReleased 事件處理程式中,手動將媒體位置設定為滑桿位置值,以達到拖動時的最佳對齊效果。
比對視訊解析度與裝置解析度
譯碼視訊需要大量的記憶體和 GPU 週期,因此請選擇接近其顯示解析度的視訊格式。 若要將影片縮小至更小的尺寸,那麼使用資源譯碼1080影片就沒有意義。 許多應用程式在不同的解析度上並沒有相同的視訊編碼,但如果有提供,請盡量使用與顯示解析度接近的編碼。
選擇建議的格式
媒體格式選取可以是敏感性主題,而且通常是由商務決策驅動。 從UWP效能的觀點來看,我們建議H.264視訊作為主要視訊格式,而AAC和MP3是慣用的音訊格式。 針對本機檔案播放,MP4 是視訊內容的慣用檔案容器。 大部分最近的圖形硬體皆可加速 H.264 解碼。 此外,雖然 VC-1 解碼的硬體加速已經廣泛提供,但對於市場上大量的圖形硬體而言,加速在許多情況下僅限於部分硬體加速層級(或 IDCT 層級),而非完全的硬體卸載(即 VLD 模式)。
如果您完全控制影片內容產生程式,您必須瞭解如何保持壓縮效率與 GOP 結構之間的良好平衡。 相對較小的 GOP 大小與 B 幀可以增加搜尋或技巧模式的效能。
包括簡短、低延遲的音訊效果時,例如在遊戲中,使用WAV檔案搭配未壓縮的 PCM 數據,以減少壓縮音訊格式的典型處理額外負荷。
優化映像資源
將影像調整為適當的大小
影像會以非常高的解析度擷取,這可能會導致應用程式在解碼映像數據時使用更多CPU,並在從磁碟載入後佔用更多記憶體。 但是,如果只是為了顯示比原始大小更小的影像,就去解碼並在記憶體中儲存高解析度的圖像,是沒有意義的。 相反地,請使用 DecodePixelWidth 和 DecodePixelHeight 屬性,建立一個與螢幕顯示大小完全相符的影像版本。
別這樣:
<Image Source="ms-appx:///Assets/highresCar.jpg"
Width="300" Height="200"/> <!-- BAD CODE DO NOT USE.-->
相反地,請執行此動作:
<Image>
<Image.Source>
<BitmapImage UriSource="ms-appx:///Assets/highresCar.jpg"
DecodePixelWidth="300" DecodePixelHeight="200"/>
</Image.Source>
</Image>
DecodePixelWidth 和 DecodePixelHeight 單位預設為實體圖元。 DecodePixelType 屬性可用來變更此行為:將 DecodePixelType 設定為 邏輯,會自動調整譯碼大小以考量系統目前的縮放比例,類似於其他 XAML 內容。 因此,一般來說,如果您想要 DecodePixelWidth 和 DecodePixelHeight 匹配圖像控件的高度和寬度屬性,則應將 DecodePixelType 設定為 Logical。 當使用實體圖元的預設行為時,您必須自行考慮系統的目前縮放比例;此外,您應該監聽縮放變更通知,以便因應使用者變更顯示偏好設定。
如果將 DecodePixelWidth/Height 明確設置為大於影像在螢幕上顯示的尺寸,則應用程式會不必要地消耗額外的記憶體——每個像素最多可達 4 個位元組——這對大型影像來說會很快增耗資源。 影像也會使用雙線性縮放縮小,這可能會導致影像在大型縮放比例中顯得模糊。
如果 DecodePixelWidth/DecodePixelHeight 明確設定得比影像在螢幕上的顯示尺寸還要小,則影像會被放大,可能顯示得有點像素化。
在某些情況下,如果無法預先確定合適的解碼大小,且未指定明確的 DecodePixelWidth/DecodePixelHeight,您應該依賴 XAML 的自動適當大小解碼功能,該功能會盡最大努力以適當的大小解碼影像。
如果您事先知道影像內容的大小,您應該設定明確的譯碼大小。 如果提供的解碼大小與其他 XAML 元素大小相對,您也應該將 DecodePixelType 設定為 Logical。 例如,如果您使用 Image.Width 和 Image.Height 明確設定內容大小,您可以將 DecodePixelType 設定為 DecodePixelType.Logical,以使用與 Image 控件相同的邏輯圖元維度,然後明確使用 BitmapImage.DecodePixelWidth 和/或 BitmapImage.DecodePixelHeight 來控制影像大小,以達到潛在的記憶體節省。
請注意,在判斷解碼內容的大小時,應考慮 Image.Stretch 特性。
尺寸合適的解碼
如果您未設定明確的譯碼大小,XAML 會根據包含頁面的初始配置,將影像譯碼到畫面上顯示的確切大小,以盡最大努力儲存記憶體。 建議您盡可能使用這項功能的方式撰寫應用程式。 如果符合下列任何條件,此功能將會停用。
- 使用 SetSourceAsync 或
設定內容之後,UriSource 會連接到即時 XAML 樹狀結構。BitmapImage - 圖像會使用同步解碼,例如 SetSource。
- 透過將 不透明度 設定為 0,或將 可視性 設定為摺疊 ,來隱藏主影像元素、筆刷或任何父元素上的影像。
- 影像控制件或筆刷會使用 拉伸 並無 。
- 此影像被用作 NineGrid。
-
CacheMode="BitmapCache"是在圖像元素或任何父元素上設定。 - 影像筆刷是非矩形的(例如,套用至圖案或文字時)。
在上述案例中,設定明確的譯碼大小是達到節省記憶體的唯一方式。
在設定來源之前,您應該一律將 BitmapImage 附加至即時樹。 每當在標記中指定了 image 元素或筆刷時,這種情況會自動發生。 以下提供範例標題為「實時樹狀結構範例」。 您應該一律避免使用 SetSource,並在設定數據流來源時改用 SetSourceAsync。 在等待 ImageOpened 事件被觸發時,最好不要透過設置不透明度為零或摺疊可見性來隱藏影像內容。 這樣做是一個判斷決策:如果這樣做,您將不會受益於自動調整到適當大小的解碼。 如果您的 app 一開始必須隱藏影像內容,則它也應該盡可能明確地設定譯碼大小。
實體樹例
範例 1(良好)—標記中指定的統一資源識別碼(URI)。
<Image x:Name="myImage" UriSource="Assets/cool-image.png"/>
範例 2 標記 — 後台程式碼中指定的 URI。
<Image x:Name="myImage"/>
範例 2 後置程式碼 (good)— 先將 BitmapImage 連接到樹,再設定其 UriSource。
var bitmapImage = new BitmapImage();
myImage.Source = bitmapImage;
bitmapImage.UriSource = new URI("ms-appx:///Assets/cool-image.png", UriKind.RelativeOrAbsolute);
範例 2 程式後置代碼(不良示範)—先設定 BitmapImage 的 UriSource,再將其連接到樹狀結構。
var bitmapImage = new BitmapImage();
bitmapImage.UriSource = new URI("ms-appx:///Assets/cool-image.png", UriKind.RelativeOrAbsolute);
myImage.Source = bitmapImage;
快取優化
快取優化適用於使用 UriSource 從應用程式套件或 Web 載入內容的影像。 URI 用來唯一識別基礎內容,而 XAML 架構內部不會多次下載或譯碼內容。 相反地,它會使用快取的軟體或硬體資源多次顯示內容。
這項優化的例外是,如果影像在不同的解析度下多次顯示(可以明確指定或透過自動調整大小解碼)。 每個快取專案也會儲存影像的解析度,如果 XAML 找不到來源 URI 符合所需解析度的影像,則會以該解析度解碼新版本。 不過,它不會再次下載編碼的影像數據。
因此,您應該在從應用程式套件載入影像時使用 UriSource,避免在不需要時使用檔案數據流和 SetSourceAsync。
虛擬化面板中的影像(例如 ListView)
如果圖像已從樹狀結構中移除,因為應用程式已明確移除它,或因為該圖像位於現代化的虛擬面板中,且在滾動出視圖時隱含地被移除,那麼 XAML 會通過釋放該圖像的硬體資源來優化記憶體使用,因為這些資源已不再需要。 記憶體不會立即釋放,而是會在影像元素脫離樹狀結構達一秒後的畫面更新期間釋放。
因此,您應該努力使用現代化虛擬化面板來裝載影像內容清單。
軟體處理的點陣影像
當影像用於非矩形筆刷或 NineGrid時,影像會採用軟體點陣化路徑,並且不會進行任何縮放。 此外,它必須將映像的複本儲存在軟體和硬體記憶體中。 例如,如果影像作為橢圓形的筆刷使用,則潛在的大型完整影像會在內部儲存兩次。 使用 NineGrid 或非矩形筆刷時,應用程式應預先將圖片調整至接近最終呈現的大小。
背景線程影像載入
XAML 具有內部優化,可讓它以異步方式將影像的內容譯碼為硬體記憶體中的表面,而不需要軟體記憶體中的中繼介面。 這樣可減少尖峰記憶體使用量和轉譯延遲。 如果符合下列任何條件,此功能將會停用。
- 此影像被用作 NineGrid。
-
CacheMode="BitmapCache"是在圖像元素或任何父元素上設定。 - 影像筆刷是非矩形的(例如,套用至圖案或文字時)。
SoftwareBitmapSource
SoftwareBitmapSource 類別會交換不同 WinRT 命名空間之間的互通未壓縮影像,例如 BitmapDecoder、相機 API 和 XAML。 這個類別會用 WriteableBitmap來抹除一份額外的複本,這有助於降低尖峰記憶體和來源到屏幕延遲。
提供來源資訊的SoftwareBitmap 也可以設定為使用自訂 IWICBitmap,以提供可重載的後援存儲,讓應用程式能夠視需要重新映射記憶體。 這是進階C++使用案例。
您的應用程式應該使用 SoftwareBitmap 和 SoftwareBitmapSource 來與其他產生和取用影像的 WinRT API 互通。 您的應用程式在載入未壓縮的影像數據時,應該使用 SoftwareBitmapSource,而不是使用 WriteableBitmap。
使用 GetThumbnailAsync 來取得縮圖
調整影像的其中一個使用案例是建立縮圖。 雖然您可以使用 DecodePixelWidth 和 DecodePixelHeight 來提供小尺寸影像,但 UWP 提供更有效率的 APIs 來擷取縮圖。 GetThumbnailAsync 提供已被文件系統快取的影像的縮圖。 這提供比 XAML API 更好的效能,因為映像不需要開啟或譯碼。
FileOpenPicker picker = new FileOpenPicker();
picker.FileTypeFilter.Add(".bmp");
picker.FileTypeFilter.Add(".jpg");
picker.FileTypeFilter.Add(".jpeg");
picker.FileTypeFilter.Add(".png");
picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
StorageFile file = await picker.PickSingleFileAsync();
StorageItemThumbnail fileThumbnail = await file.GetThumbnailAsync(ThumbnailMode.SingleItem, 64);
BitmapImage bmp = new BitmapImage();
bmp.SetSource(fileThumbnail);
Image img = new Image();
img.Source = bmp;
Dim picker As New FileOpenPicker()
picker.FileTypeFilter.Add(".bmp")
picker.FileTypeFilter.Add(".jpg")
picker.FileTypeFilter.Add(".jpeg")
picker.FileTypeFilter.Add(".png")
picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary
Dim file As StorageFile = Await picker.PickSingleFileAsync()
Dim fileThumbnail As StorageItemThumbnail = Await file.GetThumbnailAsync(ThumbnailMode.SingleItem, 64)
Dim bmp As New BitmapImage()
bmp.SetSource(fileThumbnail)
Dim img As New Image()
img.Source = bmp
解碼圖像一次
若要防止影像多次譯碼,請從Uri指派 Image.Source 屬性,而不是使用記憶體數據流。 XAML 架構可以將多個位置中的相同 URI 與一個譯碼的影像產生關聯,但無法對包含相同數據的多個記憶體數據流執行相同的動作,併為每個記憶體數據流建立不同的譯碼影像。