Universal App - 使用 BackgroundDownloader
過去希望在 WP 手機裡有背景下載或上傳檔案,可藉由 BackgroundTransferRequest 類別來實作,
之前有寫過相似的文件說明<Windows Phone 7 – Background File Transfer>。
本篇將參考<Transferring data in the background (XAML)>介紹在開發 Universal App 時如何在背景
上傳/下載大型檔案時可改用 BackgroundDownloader 類別。
[注意]
1. Background Transfer 主要目的是支援需要長時間傳輸的運作,例如:video、music 或是大圖像;
2. 如果是相對短時間傳輸的操作(例如:KB),請使用 Windows.Web.Http namespace 下的元件;
3. 要使用背景傳輸的機制,需要先使用 BackgroundDownloader 或 BackgroundUploader 請求設定與初始化物件;
4. 每一個 transfer operation 都是獨立分開交由系統負責處理,而且每一個 App 呼叫也是分開的;
5. 針對 transfer operation 可進行 pause、resume、cancel 或讀取目前處理的進度與資料;
6. 當 App 被 suspend 、resume 或 launch 時,要記得更新或終止未完成的項目,以免造成 exception;
7. 如何處理當網路狀況改變或是未預期的關閉呢?
藉由 Quickstart: Retrieving network connection information (Windows Store apps using JavaScript and HTML) 可註冊網路狀態改變的事件。
藉由 BackgroundTransferCostPolicy enumeration 指定在何種網路情境下是否要繼續完成任務,列舉值有:
- Default:0,Allow transfers on metered networks.
- UnrestrictedOnly:1,Do not allow transfers on metered networks.
- Always:2,Always download regardless of network cost. (e.g. even while a user is roaming)
This behavior is based only on network cost policy, and doesn't affect other app scenarios, like system suspension.
Windows Phone 具有功能讓用戶可以設定與監控資料流量,這些會影響背景傳輸,即使設定 BackgroundTransferCostPolicy enumeration 也是無用。
下表補充設定的值對於手機狀態的影響:
Phone State | UnrestrictedOnly | Default | Always |
Connected to WiFi | Allow | Allow | Allow |
Metered Connection, not roaming, under data limit, on track to stay under limit | Deny | Allow | Allow |
Metered Connection, not roaming, under data limit, on track to exceed limit | Deny | Deny | Allow |
Metered Connection, roaming, under data limit | Deny | Deny | Allow |
Metered Connection, over data limit. This state only occurs when the user enables "Restrict background data in the Data Sense UI. | Deny | Deny | Deny |
然後 RequestUnconstrainedDownloadsAsync 與 RequestUnconstrainedUploadsAsync 不支援 Windows Phone,如果呼叫會得到 E_NOT_IMPL exception。
》Windows.Networking.BackgroundTransfer namespace:
該命名空間主要定義了可在背景傳輸的相關類別、列舉、介面、與 Structures。詳細請參考上述連結,以下列出幾個常用的元素:
Class | Description |
BackgroundDownloader | Used to configure downloads prior to the actual creation of the download operation using CreateDownload. |
DownloadOperation | Performs an asynchronous download operation. The Background Transfer sample demonstrates this functionality. |
ResponseInformation | Represents data that is returned by a server response. |
BackgroundTransferCompletionGroup | Represents a set of background transfer operations (DownloadOperation or UploadOperation objects) that trigger a background task once all the operations are done (if the operations completed successfully) or fail with an error. |
BackgroundTransferCompletionGroupTriggerDetails | Contains information about a BackgroundTransferCompletionGroup that can be only accessed from the Run method on the IBackgroundTask. |
BackgroundTransferContentPart |
Represents a content part of a multi-part transfer request. Each BackgroundTransferContentPart object can represent either a single string of text content or a single file payload, but not both. |
BackgroundTransferError | Used to provide errors encountered during a transfer operation. |
BackgroundTransferGroup |
A named group used to associate multiple download or upload operations. This class makes it easy for your app to create these groups and to complete downloads and uploads simultaneously, in serial, or based on priority. |
BackgroundUploader | Used to configure upload prior to the actual creation of the upload operation using CreateUpload. |
UploadOperation | Performs an asynchronous upload operation. |
ContentPrefetcher | Provides properties for specifying web resources to be prefetched. Windows will use heuristics to attempt to download the specified resources in advance of your app being launched by the user. |
UnconstrainedTransferRequestResult | Represents the result a request for unconstrained transfers from a BackgroundDownloader or BackgroundUploader object. |
其他相關的還有 Enumerations 與 Interfaces,還有 Structure:
Structure | Description |
BackgroundDownloadProgress | Contains status information about the download operation. |
BackgroundUploadProgress | Contains status information about the upload operation. |
以下針對 BackgroundDownloader 與 DownloadOperation 加以說明。
負責建立與管理目前仍在處理中的 DownloadOperation。重要方法如下:
Type | Name | Description |
Method | CreateDownload(Uri, IStorageFile) | Initializes a DownloadOperation object that contains the specified Uri and the file that the response is written to. |
CreateDownload(Uri, IStorageFile, IStorageFile) | Initializes a DownloadOperation object with the resource Uri, the file that the response is written to, and the request entity body. | |
CreateDownloadAsync | Creates an asynchronous download operation that includes a URI, the file that the response will be written to, and the IInputStream object from which the file contents are read. | |
GetCurrentDownloadsAsync() | Returns a collection of pending downloads that are not associated with a group. | |
GetCurrentDownloadsAsync(String) | Returns a collection of pending downloads for a specific Group. | |
RequestUnconstrainedDownloadsAsync | Used to request an unconstrained download operation. When this method is called the user is provided with a UI prompt that they can use to indicate their consent for an unconstrained operation. | |
SetRequestHeader | Used to set an HTTP request header. | |
Properties |
CostPolicy | Read/write. Gets or sets the cost policy for the background download operation. |
FailureTileNotification | Read/write. Gets or sets the TileNotification used to define the visuals, identification tag, and expiration time of a tile notification used to update the app tile when indicating failure of a download to the user. | |
FailureToastNotification | Read/write. Gets or sets the ToastNotification that defines the content, associated metadata, and events used in a toast notification to indicate failure of a download to the user. | |
Method | Read/write. Gets or sets the HTTP method used for the background download | |
ProxyCredential | Read/write. Gets or sets the proxy credentials for the background transfer. | |
SuccessTileNotification | Read/write. Gets or sets the TileNotification used to define the visuals, identification tag, and expiration time of a tile notification used to update the app tile when indicating success of a download to the user. | |
SuccessToastNotification | Read/write. Gets or sets the ToastNotification that defines the content, associated metadata, and events used in a toast notification to indicate success of a download to the user. | |
ServerCredential | Read/write. Gets or sets the credentials to use to authenticate with the origin server. |
在操作 BackgroundDownloader 時,要注意:
a. 如果 App 被 terminate 後再回到 App 時,App 應利用 GetCurrentDownloadsAsync 方法取得所有仍存在的 DownloadOperation。
=>Windows Store app 使用 background transfer 被系統結束了,未完成的 downloads 會仍然保存在背景中;
=>如果重新返回 App 時沒有處理完這些未完成的 download (例如:重新連接或下載),這些 downloads 會仍存在背景佔資源;
=>如果 app 被移除了那佇列的 operations 自動被清掉;
b. Background transfer 不支援同時下載相同的 Uri,所以同一個 Uri 要下載,請分成二次下載。
c. 如果 Server 支援 rang-requests ,那暫停或未完成的 downloads 將可以繼續被下載;
d. 要考慮 timeout 的機制:
d-1. 如果連結對象是 TCP/SSL,在 5 分鐘內無法建立成功就算 timeout;
d-2. 如果連結對象為 HTTP request,在 2 分鐘內未收到 response 就該 abort 這次請求;
e. 如果有其他 App 或是元件也要使用 BackgroundDownloader 時,建議在操作時給予一個 group name 來建立,避免其他 App 看到你正在下載的項目。
可以藉由 GetCurrentDownloadsAsync(String) 取得指定名稱的 downloadoperation,有給予 name 在其他地方用 GetCurrentDownloadsAsync 就看不到。
f. 對於 FTP 下載的話需要在 URI 內提供驗證認證,例如:ftp://user:password@server/file.txt。
g. 如果下載作業需要輸入帳號密碼,使用 WinINet 機制的話,請使用 ServerCredential 或 ProxyCredential 屬性;
如果不支援 WinINet 的話,可藉由 HttpClient 先實作驗證與取得下載專案的權仗(Cookies),並將它加入 header 中,例如:SetRequestHeader。
h. handling exceptions:
進行下載作業時可能發生一些 exceptions,例如,連線中斷、連線失敗和其他 HTTP 錯誤) 隨時都可能發生。 這些造成例外狀況擲回的錯誤。
如果無法處理您的應用程式,例外狀況可能會讓整個應用程式在執行階段結束。
利用從造成 crash 的 exception 中取得的 HRESULT 加以轉換成 WebErrorStatus 來了解錯誤的原因,可藉由 BackgroundTransferError.GetStatus 取得。
詳細可參考<Handling exceptions in network apps>。
i. Debugging guideline:偵錯和測試 Windows 市集應用程式。
負責執行非同步下載的執行。重要屬性與方法:
Type | Name | Description |
Method | AttachAsync | Returns an asynchronous operation that can be used to monitor progress and completion of the attached download. Calling this method allows an app to attach download operations that were started in a previous app instance. |
GetResponseInformation | Gets the response information. | |
GetResultStreamAt | Gets the partially downloaded response at the specified position. | |
Pause | Pauses a download operation. | |
Resume | Resumes a paused download operation. | |
StartAsync | Starts an asynchronous download operation. | |
Property | CostPolicy | Read/write. Gets and sets the cost policy for the download. |
Group | Read-only. Gets a string value indicating the group the transfer belongs to. | |
Guid |
Read-only. This is a unique identifier for a specific download operation. A GUID associated to a download operation will not change for the duration of the download. |
|
Method | Read-only. Gets the method to use for the download. | |
Priority | Read/write. Gets or sets the transfer priority of this download operation when within a BackgroundTransferGroup. Possible values are defined by BackgroundTransferPriority. | |
Progress | Read-only. Gets the current progress of the upload operation. | |
RequestedUri | Read-only. Gets the URI from which to download the file. | |
ResultFile | Read-only. Returns the IStorageFile object provided by the caller when creating the DownloadOperation object using CreateDownload. | |
TransferGroup | Read-only. Gets the group that this download operation belongs to. |
藉由簡單的範程代碼來看怎麼操作 BackgroundDownloader 與 DownloadOperation:
private async void StartDownlaod(String url, String fileName)
{
try
{
Uri source = new Uri(url);
StorageFile destinationFile = await KnownFolders.PicturesLibrary.CreateFileAsync(
fileName, CreationCollisionOption.GenerateUniqueName);
// 宣告 BackgroundDownloader 與 建立 DownlaodOperation
BackgroundDownloader downloader = new BackgroundDownloader();
DownloadOperation download = downloader.CreateDownload(source, destinationFile);
download.StartAsync();
}
catch (Exception ex)
{
LogException("Download Error", ex);
}
}
可得知操作 DownloadOperation 需要先指定好對應的 URL 與 StorageFile,目的是支援在 App 被 suspend 後,BackgroundDownloader 仍可以繼續下載檔案。
[範例]
上述介紹了相關 BackgroundDownloader 的觀念之後,下面藉由程式範例來說明幾個重要的處理:
情境:實作了一個檔案下載佇列,按下開始下載後將所有的檔案加入 BackgroundDownloader 所建立的 DownloadOperation,並且註冊 ProgressChanged
的事件來更新 UI 中的 ProgressBar 控制項。
預期畫面如下:
A. 先取得 BackgroundDownloadOperator;
private BackgroundDownloader downloader;
private CancellationTokenSource cts;
const String DWGroupId = "BgDownloadTransfer";
public MainPageViewModel()
{
this.DownloadItemCollection = new ObservableCollection<DownloadItem>();
downloader = new BackgroundDownloader();
// 如果希望自己下載的項目不要被看到,可以指定 Group Id;
//downloader.Group = DWGroupId;
cts = new CancellationTokenSource();
}
建議可以在取得 BackgroundDownloader 之後設定 Group 屬性,避免其他 App 使用 BackgroundDownloader.GetCurrentDownloadsAsync() 取得您正在下載的檔案。
B. 為所有下載的檔案建立或取得已加入 BackgroundDownloader 的 DownloadOperations,並且註冊下載進度的事件並更新畫面;
public async void StartDownload()
{
// 事先準備好要下載的檔案
foreach (var item in this.DownloadItemCollection)
{
// 取得目前已排入 BackgroundDownloader 的下載項目
var existDownload = await BackgroundDownloader.GetCurrentDownloadsAsync();
// 檢查是否已經有被排入的
var activeItem = existDownload.Where(x => x.RequestedUri.AbsoluteUri == item.URL).FirstOrDefault();
if (activeItem != null)
{
// 存在不要重新建立
await HandleDownloadAsync(activeItem, false);
}
else
{
// 不存在則重新建立
item.Operator = downloader.CreateDownload(new Uri(item.URL, UriKind.RelativeOrAbsolute), await item.GetFile());
await HandleDownloadAsync(item.Operator, true);
}
}
}
private async Task HandleDownloadAsync(DownloadOperation download, bool start)
{
try
{
Progress<DownloadOperation> progressCallback = new Progress<DownloadOperation>(DownloadProgress);
if (start)
{
// 代表是重新建立的 operation ,並加上 progress handler
await download.StartAsync().AsTask(cts.Token, progressCallback);
}
else
{
// 代表 operation 已存在,則用 attch 的方式加入 progress handler
await download.AttachAsync().AsTask(cts.Token, progressCallback);
}
ResponseInformation response = download.GetResponseInformation();
// 可以加上如果下載完畢要做的事件
}
catch (TaskCanceledException)
{
}
catch (Exception)
{
}
finally
{
// 下載成功或失敗要處理的任務
}
}
private void DownloadProgress(DownloadOperation download)
{
double percent = 100;
if (download.Progress.TotalBytesToReceive > 0)
{
percent = download.Progress.BytesReceived * 100 / download.Progress.TotalBytesToReceive;
}
dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
var downloadItem = this.DownloadItemCollection.Where(x => x.URL == download.RequestedUri.AbsoluteUri).FirstOrDefault();
if (downloadItem != null)
{
downloadItem.DownloadPercent = percent;
}
});
}
這個部分要特別注意是否有上一次未下載完的項目,由於同一個下載的 URL 不能重覆,所以要加以檢查。由於在 Windows Store App 當 App 被 suspended 後,
未完成的 BackgroundDownloadOperation 會被保存著直到下一次被啟動,所以利用 GetCurrentDownloadsAsync() 判斷目前仍存在的項目是否要繼續,否則記得全部清除。
該範例使用了 MVVM 的方式 Binding View 上的 ProgressBar,更多詳細的操作方式可下載範例程式。
[範例程式]
[補充]
a. 官方的範例影片;
======
BackgroundDownloader 讓我想到在 WP 8 的 BackgroundTrasnferRequest 這個類別,二者用法有些接近,
但是 BackgroundDownloader 提供更完整處理檔案背景的任務,更提供多線下載與管理,也支援背景
操作與前景返回時可以管理這些 Download 單位的狀態,非常不錯的類別。介紹給大家。
References:
〉Background Transfer sample (重要範例)
〉Background downloader windows 8 Multiple files
〉Transferring data in the background (XAML) (必讀)
〉Windows.Networking.BackgroundTransfer namespace
〉Transferring data in the background (XAML) (重要)