Universal App - 使用 BackgroundDownloader

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. 要使用背景傳輸的機制,需要先使用 BackgroundDownloaderBackgroundUploader 請求設定與初始化物件;

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

然後 RequestUnconstrainedDownloadsAsyncRequestUnconstrainedUploadsAsync 不支援 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.

其他相關的還有 EnumerationsInterfaces,還有 Structure:

Structure Description
BackgroundDownloadProgress Contains status information about the download operation.
BackgroundUploadProgress Contains status information about the upload operation.

 

以下針對 BackgroundDownloader 與 DownloadOperation 加以說明。

 

BackgroundDownloader class

    負責建立與管理目前仍在處理中的 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 機制的話,請使用 ServerCredentialProxyCredential 屬性;

    如果不支援 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 市集應用程式

 

 

DownloadOperation class

    負責執行非同步下載的執行。重要屬性與方法:

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 控制項。

 

預期畫面如下:

00

 

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) (重要)