共用方式為


背景傳輸

使用背景傳輸 API,透過網路可靠地複製檔案。 背景傳輸 API 提供進階的上傳和下載功能,可在應用程式暫停期間於背景中執行,並在應用程式終止後仍繼續保留。 API 會監視網路狀態,並在連線中斷時自動暫停及繼續傳輸,且傳輸功能具有數據用量感應感知和電池感應感知能力,表示下載活動會根據您目前的連線能力和裝置電池狀態進行調整。 API 非常適合使用 HTTP(S) 上傳及下載大型檔案, 也支援 FTP,但僅適用於下載。

背景傳輸會與呼叫應用程式分開執行,主要針對視訊、音樂和大型影像等資源的長期傳輸作業。 背景傳輸對於這類情況至關重要,因為即使應用程式暫停,下載仍會繼續進行。

如果是下載能快速完成的小型資源,則應使用 HttpClient API,而非背景傳輸。

使用 Windows.Networking.BackgroundTransfer

背景傳輸功能如何運作?

應用程式使用背景傳輸開始傳輸時,會使用 BackgroundDownloader BackgroundUploader 類別物件來設定並初始化要求。 系統會個別處理每項傳輸作業,並且與呼叫應用程式分開執行。 您可以透過應用程式的 UI 為使用者顯示進度資訊,且即使資料正在傳輸,應用程式也可暫停、繼續、取消,或甚至讀取資料。 系統如此處理傳輸作業,可改善智慧電源使用量,並在連網應用程式遇到應用程式暫停、終止或網路狀態突然變更等事件時,防止可能發生的問題。

注意

由於每個應用程式的資源限制,因此應用程式在任何時候都不應有超過 200 個傳輸 (DownloadOperations + UploadOperations)。 超過該限制可能會使得應用程式的傳輸佇列處於無法復原狀態。

應用程式在啟動時,必須在所有現有的 DownloadOperationUploadOperation物件上呼叫 AttachAsync。 若未這麼做,將導致已完成的傳輸外洩,最後使得您所用的背景傳輸功能變得沒有用處。

使用背景傳輸執行已驗證的檔案要求

背景傳輸可支援基本伺服器和 Proxy 認證、Cookie,並透過 SetRequestHeader 針對每個傳輸作業使用自訂 HTTP 標頭。

此功能如何因應網路狀態變更或非預期關機?

網路狀態發生變更時,背景傳輸功能可透過智慧運用連線能力功能提供的連線能力,以及電信業者行動數據方案狀態資訊,維護各項傳輸作業的一致體驗。 若要定義不同網路狀況的行為,應用程式會使用 backgroundTransferCostPolicy 定義的值,為每個作業設定成本原則。

例如,根據為作業定義的成本原則,當裝置使用計量付費網路時,應自動暫停作業; 連線至「不受限制」的網路時,則會自動恢復傳輸 (或重新開機)。 關於如何定義網路成本,詳細資訊請參閱 NetworkCostType

雖然針對如何處理網路狀態變更,背景傳輸功能有自己的一套機制,連網應用程式還有其他一般連線考量。 如需詳細資訊,請參閱運用可用的網路連線資訊

注意 行動裝置上的應用程式可能包含某些功能,可讓使用者根據連線類型、漫遊狀態和使用者的通話方案,監視與限制傳輸資料量。 因此,即使 BackgroundTransferCostPolicy 表示傳輸應繼續,手機的背景傳輸仍可能暫停。

下表說明根據手機的狀態,每個 BackgroundTransferCostPolicy 值何時允許手機進行背景傳輸。 您可使用 ConnectionCost 類別來判斷手機的目前狀態。

裝置狀態 僅限不受限制的網路 預設 永遠
連接至 WiFi 允許 允許 允許
計量付費連線、非漫遊、低於資料傳輸量上限、追蹤以保持低於上限 拒絕 允許 允許
計量付費連線,非漫遊、超過資料傳輸量上限、追蹤確認是否超過上限 拒絕 拒絕 允許
計量付費連線、非漫遊、低於資料傳輸量上限 拒絕 拒絕 允許
計量付費連線、超過資料傳輸量上限。 只有當使用者啟用「限制數據用量感知 UI 的背景資料」時,才會發生此狀態。 拒絕 拒絕 拒絕

上傳檔案

使用背景傳輸時,上傳將採取 UploadOperation 的形式,並可公開多個用於重新開機或取消作業的控制方法。 系統會對每個 UploadOperation 自動處理 app 事件 (例如暫停或終止) 和連線變更;在 app 暫停期間上傳仍將繼續,或在 app 終止之後會暫停或持續下去。 此外,藉由設定 CostPolicy 屬性,可指出在使用計量付費網路作為網路連線時,應用程式是否會進行上傳作業。

下列範例將逐步引導您建立並初始化基本上傳,以及如何列舉和重新引入先前應用程式工作階段保存的作業。

上傳單一檔案

若想建立上傳,須從 BackgroundUploader 開始, 此類別可幫助應用程式先設定上傳,再建立結果 UploadOperation。 下列範例說明如何使用必要的 UriStorageFile 物件來執行此動作。

識別上傳的檔案和目的地

在開始建立 UploadOperation 前,必須先識別要上傳的位置 URI,以及要上傳的檔案。 在下列範例中,將使用 UI 輸入的字串填入 uriString 值,填入 file 值的方式則是使用 PickSingleFileAsync 作業傳回的 StorageFile 物件。

function uploadFile() {
    var filePicker = new Windows.Storage.Pickers.FileOpenPicker();
    filePicker.fileTypeFilter.replaceAll(["*"]);

    filePicker.pickSingleFileAsync().then(function (file) {
        if (!file) {
            printLog("No file selected");
            return;
        }

        var upload = new UploadOp();
        var uriString = document.getElementById("serverAddressField").value;
        upload.start(uriString, file);

        // Store the upload operation in the uploadOps array.
        uploadOperations.push(upload);
    });
}

建立和初始化上傳作業

上一個步驟的 uriStringfile 值已傳遞至下一個範例 UploadOp 的執行個體,這兩個值將用來設定並啟動新的上傳作業。 首先,剖析 uriString,以建立必要的 Uri 物件。

接下來,BackgroundUploader 會使用 StorageFile 的屬性 (file) 填入要求標頭,並使用 StorageFile 物件設定 sourceFile 屬性。 接著會呼叫 SetRequestHeader 方法,插入字串形式的檔案名與 StorageFile.Name 屬性。

最後,BackgroundUploader 會建立 UploadOperation (upload)。

function UploadOp() {
    var upload = null;
    var promise = null;

    this.start = function (uriString, file) {
        try {
        
            var uri = new Windows.Foundation.Uri(uriString);
            var uploader = new Windows.Networking.BackgroundTransfer.BackgroundUploader();

            // Set a header, so the server can save the file (this is specific to the sample server).
            uploader.setRequestHeader("Filename", file.name);

            // Create a new upload operation.
            upload = uploader.createUpload(uri, file);

            // Start the upload and persist the promise to be able to cancel the upload.
            promise = upload.startAsync().then(complete, error, progress);
        } catch (err) {
            displayError(err);
        }
    };
    // On application activation, reassign callbacks for a upload
    // operation persisted from previous application state.
    this.load = function (loadedUpload) {
        try {
            upload = loadedUpload;
            promise = upload.attachAsync().then(complete, error, progress);
        } catch (err) {
            displayError(err);
        }
    };
}

請注意,非同步方法呼叫是由 JavaScript 的 Promise 所定義。 請見上個範例的其中一行:

promise = upload.startAsync().then(complete, error, progress);

非同步方法呼叫後面會接著一個 then 陳述式,此陳述式代表應用程式定義的方法,會在傳回非同步方法呼叫的結果時加以呼叫。 關於此程式設計模式的詳細資料,請參閱使用 Promise 在 JavaScript 進行非同步程式設計

上傳多個檔案

識別上傳的檔案和目的地

在使用單一 UploadOperation 傳輸多個檔案的情況中,與一般程序相同,須首先提供所需的目的地 URI 和本機檔案資訊。 與上一節的範例類似,使用者會提供字串形式的 URI,而 FileOpenPicker 可用來透過使用者介面指出檔案。 不過,在此情況中,應用程式應改呼叫 PickMultipleFilesAsync 方法,以透過 UI 選取多個檔案。

function uploadFiles() {
       var filePicker = new Windows.Storage.Pickers.FileOpenPicker();
       filePicker.fileTypeFilter.replaceAll(["*"]);

       filePicker.pickMultipleFilesAsync().then(function (files) {
          if (files === 0) {
             printLog("No file selected");
                return;
          }

          var upload = new UploadOperation();
          var uriString = document.getElementById("serverAddressField").value;
          upload.startMultipart(uriString, files);

          // Persist the upload operation in the global array.
          uploadOperations.push(upload);
       });
    }

針對提供的參數建立物件

以下兩個範例使用單一範例方法 startMultipart 的程式碼,並已在上個步驟的結尾進行呼叫。 為清楚說明,已將建立 BackgroundTransferContentPart 物件陣列方法中的程式碼與建立結果 UploadOperation 的程式碼分開。

首先,使用者提供的 URI 字串會初始化為 Uri。 接下來,將反覆執行傳遞至此方法的 IStorageFile 物件陣列 (files),每個物件都會用來建立新的 BackgroundTransferContentPart 物件,並放置在 contentParts 陣列中。

    upload.startMultipart = function (uriString, files) {
        try {
            var uri = new Windows.Foundation.Uri(uriString);
            var uploader = new Windows.Networking.BackgroundTransfer.BackgroundUploader();

            var contentParts = [];
            files.forEach(function (file, index) {
                var part = new Windows.Networking.BackgroundTransfer.BackgroundTransferContentPart("File" + index, file.name);
                part.setFile(file);
                contentParts.push(part);
            });

建立和初始化多部分上傳作業

在 contentParts 陣列中填入所有 BackgroundTransferContentPart 物件,代表要上傳的 IStorageFile 後,就可以使用 Uri 呼叫 CreateUploadAsync 來表示傳送要求的目的地。

        // Create a new upload operation.
            uploader.createUploadAsync(uri, contentParts).then(function (uploadOperation) {

               // Start the upload and persist the promise to be able to cancel the upload.
               upload = uploadOperation;
               promise = uploadOperation.startAsync().then(complete, error, progress);
            });

         } catch (err) {
             displayError(err);
         }
     };

重新啟動中斷的上傳作業

完成或取消 UploadOperation 時,會釋放所有相關聯的系統資源。 不過,如果您的應用程式在發生上述動作前已終止,則不只任何進行中的作業將暫停,且仍會佔用每個作業相關的資源。 如果未列舉這些作業並重新導入下一個應用程式工作階段,作業將不會完成,且繼續佔用裝置資源。

  1. 在定義列舉持續作業的函式之前,必須建立陣列來包含預定傳回的 UploadOperation 物件:

    var uploadOperations = [];
    
  2. 接下來會定義列舉持續作業的函式,並將其儲存在陣列中。 請注意,將回呼重新指派給 UploadOperationload 方法,應在應用程式終止期間持續,並位於我們稍後會在本節定義的 UploadOp 類別中。

    function Windows.Networking.BackgroundTransfer.BackgroundUploader.getCurrentUploadsAsync() {
        .then(function (uploads) {
            for (var i = 0; i < uploads.size; i++) {
                var upload = new UploadOp();
                upload.load(uploads[i]);
                uploadOperations.push(upload);
            }
        }
    };
    

下載檔案

使用背景傳輸時,每項上傳將採取 DownloadOperation 的形式,並可公開許多用於暫停、繼續、重新開機或取消作業的控制方法。 系統會對每個 DownloadOperation 自動處理 app 事件 (例如暫停或終止) 和連線變更;在 app 暫停期間下載仍將繼續,或在 app 終止之後會暫停或持續下去。 針對行動網路案例,藉由設定 CostPolicy 屬性,可指出在使用計量付費網路作為網路連線時,應用程式是否會進行上傳作業。

如果是下載能快速完成的小型資源,則應使用 HttpClient API,而非背景傳輸。

下列範例將逐步引導您建立並初始化基本下載,以及如何列舉和重新引入先前應用程式工作階段保存的作業。

設定並啟動背景傳輸檔案下載

下列範例說明如何使用代表 URI 和檔案名的字串建立 Uri 物件,以及包含所需檔案的 StorageFile。 在此範例中,新檔案會自動放置於預先定義的位置。 或者,您可以使用 FileSavePicker 讓使用者指出裝置上儲存檔案的位置。 請注意,將回呼重新指派給 DownloadOperationload 方法,應在應用程式終止期間持續,並位於我們稍後會在本節定義的 DownloadOp 類別中。

function DownloadOp() {
    var download = null;
    var promise = null;
    var imageStream = null;

    this.start = function (uriString, fileName) {
        try {
            // Asynchronously create the file in the pictures folder.
            Windows.Storage.KnownFolders.picturesLibrary.createFileAsync(fileName, Windows.Storage.CreationCollisionOption.generateUniqueName).done(function (newFile) {
                var uri = Windows.Foundation.Uri(uriString);
                var downloader = new Windows.Networking.BackgroundTransfer.BackgroundDownloader();

                // Create a new download operation.
                download = downloader.createDownload(uri, newFile);

                // Start the download and persist the promise to be able to cancel the download.
                promise = download.startAsync().then(complete, error, progress);
            }, error);
        } catch (err) {
            displayException(err);
        }
    };
    // On application activation, reassign callbacks for a download
    // operation persisted from previous application state.
    this.load = function (loadedDownload) {
        try {
            download = loadedDownload;
            printLog("Found download: " + download.guid + " from previous application run.<br\>");
            promise = download.attachAsync().then(complete, error, progress);
        } catch (err) {
            displayException(err);
        }
    };
}

請注意,非同步方法呼叫是由 JavaScript 的 Promise 所定義。 請見上個程式碼的第 17 行:

promise = download.startAsync().then(complete, error, progress);

非同步方法呼叫後面會接著一個 Then 陳述式,此陳述式代表應用程式定義的方法,會在傳回非同步方法呼叫的結果時加以呼叫。 關於此程式設計模式的詳細資料,請參閱使用 Promise 在 JavaScript 進行非同步程式設計

新增其他作業控制方法

藉由執行其他 DownloadOperation 方法,可以增加控制層級。 例如將下列程式碼新增至上述範例,可導入取消下載的功能。

// Cancel download.
this.cancel = function () {
    try {
        if (promise) {
            promise.cancel();
            promise = null;
            printLog("Canceling download: " + download.guid + "<br\>");
            if (imageStream) {
                imageStream.close();
            }
        }
        else {
            printLog("Download " + download.guid + " already canceled.<br\>");
        }
    } catch (err) {
        displayException(err);
    }
};

在啟動時列舉保存的作業

完成或取消 DownloadOperation 時,會釋放所有相關聯的系統資源。 不過,如果您的應用程式在發生上述動作前已終止,下載作業將會暫停,並保存於背景中。 下列範例說明如何將保存的下載重新導入新的應用程式工作階段。

  1. 在定義列舉持續作業的函式之前,必須建立陣列來包含預定傳回的 DownloadOperation 物件:

    var downloadOps = [];
    
  2. 接下來會定義列舉持續作業的函式,並將其儲存在陣列中。 請注意,將回呼重新指派給持續 DownloadOperationload 方法,會位於我們稍後將在本節定義的 DownloadOp 範例中。

    // Enumerate outstanding downloads.
    Windows.Networking.BackgroundTransfer.BackgroundDownloader.getCurrentDownloadsAsync().done(function (downloads) {
    
        for (var i = 0; i < downloads.size; i++) {
            var download = new DownloadOp();
            download.load(downloads[i]);
            downloadOps.push(download);
        }
    });
    
  3. 您現在可以使用填入的清單來重新啟動擱置作業。

後續處理

Windows 10 的新功能是能夠在背景傳輸完成時 (即使應用程式未執行) 執行應用程式程式碼。 例如,您可能希望電影下載完成後,應用程式能更新可用的電影清單,而不是在每次啟動時掃描是否有新電影。 或者,您希望應用程式能使用不同的伺服器或連接埠,再次嘗試處理失敗的檔案傳輸。 成功和失敗的傳輸都可叫用後續處理,因此您可以藉此執行自訂錯誤處理並重試邏輯。

後續處理以現有的背景工作為基礎結構, 您可在開始傳輸前,先建立關聯的背景工作。 接著,傳輸會在背景中執行,並在完成時呼叫背景工作來執行後續處理。

後續處理會使用 BackgroundTransferCompletionGroup 此一新類別。 類似於現有的 BackgroundTransferGroup,此類別有助於將背景傳輸結成群組,但 BackgroundTransferCompletionGroup 新增一項功能,可以在傳輸完成後指定要執行的背景工作。

您可按照以下步驟,使用後續處理來起始背景傳輸。

  1. 建立一個 BackgroundTransferCompletionGroup 物件。 然後,建立一個 BackgroundTaskBuilder 物件。 將建立器物件的 Trigger 屬性設定為完成群組物件,並將建立器的 TaskEntryPoint 屬性設定為應在傳輸完成時執行之背景工作的進入點。 最後,呼叫 BackgroundTaskBuilder.Register 方法來登錄背景工作。 請注意,多個完成群組可共用一個背景工作進入點,但每個背景工作登錄只能有一個完成群組。
var completionGroup = new BackgroundTransferCompletionGroup();
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();

builder.Name = "MyDownloadProcessingTask";
builder.SetTrigger(completionGroup.Trigger);
builder.TaskEntryPoint = "Tasks.BackgroundDownloadProcessingTask";

BackgroundTaskRegistration downloadProcessingTask = builder.Register();
  1. 接下來,建立背景傳輸與完成群組的關聯。 建立所有傳輸後,請啟用完成群組。
BackgroundDownloader downloader = new BackgroundDownloader(completionGroup);
DownloadOperation download = downloader.CreateDownload(uri, file);
Task<DownloadOperation> startTask = download.StartAsync().AsTask();

// App still sees the normal completion path
startTask.ContinueWith(ForegroundCompletionHandler);

// Do not enable the CompletionGroup until after all downloads are created.
downloader.CompletionGroup.Enable();
  1. 背景工作中的程式碼會從觸發程序詳細資料擷取作業清單,然後您的程式碼可以檢查每個操作的詳細資料,並針對每個作業執行適當的後續處理。
public class BackgroundDownloadProcessingTask : IBackgroundTask
{
    public async void Run(IBackgroundTaskInstance taskInstance)
    {
    var details = (BackgroundTransferCompletionGroupTriggerDetails)taskInstance.TriggerDetails;
    IReadOnlyList<DownloadOperation> downloads = details.Downloads;

    // Do post-processing on each finished operation in the list of downloads
    }
}

後續處理工作是一般背景工作, 屬於所有背景工作的集區,而且受限於與所有背景工作相同的資源管理原則。

此外,請注意後續處理不會代替前景完成處理常式。 如果您的應用程式定義前景完成處理常式,且檔案完成傳輸時您的應用程式正在執行,則會一併呼叫前景與後景的完成處理常式, 前景和背景工作的呼叫順序並不一定。 如果您同時定義兩者,必須確定這兩項工作會正常執行,且在同時執行時不會互相干擾。

要求逾時

必須考量的主要連線逾時情況有兩種:

  • 建立傳輸的新連線時,如果未在五分鐘內建立連線要求,將中止此要求。

  • 建立連線之後,如果未在兩分鐘內收到回應,將中止 HTTP 要求訊息。

注意 在任一情況下,假設有網際網路連線,背景傳輸最多會自動重試要求三次。 如果未偵測到網際網路連線,其他要求將等候至偵測到連線為止。

偵錯指引

在 Microsoft Visual Studio 中停止偵錯工作階段,即相當於關閉您的應用程式,此時將暫停 PUT 上傳,並終止 POST 上傳。 即使在偵錯時,您的應用程式也應進行列舉,並重新啟動或取消任何保留的上傳。 例如,如果偵錯工作階段與過去作業無關,您可在應用程式啟動時,讓應用程式取消已列舉的保留上傳作業。

如果偵錯工作階段與過去作業無關,您可在偵錯工作階段期間,於應用程式啟動並列舉下載/上傳時,讓應用程式取消此動作。 請注意,如果 Visual Studio 專案有所更新,例如變更應用程式資訊清單,且應用程式已解除安裝並重新部署,則 GetCurrentUploadsAsync 無法列舉使用先前應用程式部署所建立的作業。

在開發期間使用背景傳輸時,可能會發生作用中與已完成傳輸作業的內部快取無法同步的情況。這可能會導致無法啟動新的傳輸作業,或無法與現有的作業和 BackgroundTransferGroup 物件進行互動。 在某些情況下,嘗試與現有作業互動可能會導致當機。 如果 TransferBehavior 屬性設定為 Parallel,就會發生這類結果。 此問題只會在開發期間的特定情況下發生,並不會影響應用程式的終端使用者。

使用 Visual Studio 時,有四種情況會導致這個問題。

  • 建立新專案時,使用與現有專案相同的應用程式名稱,但不同的語言 (例如從 C++ 變更為 C#)。
  • 變更現有專案中的目標架構 (例如從 x86 變更為 x64)。
  • 變更現有專案中的文化特性 (例如從中性變更為 en-US)。
  • 在現有專案中的封裝資訊清單中新增或移除功能 (例如新增企業驗證)。

在應用程式的終端使用者部署中,一般的應用程式服務,包括新增或移除功能的資訊清單更新,不會引發此問題。 若要解決此問題,請解除安裝應用程式的所有版本,並使用新的語言、架構、文化特性或功能重新部署。 此操作可以透過 [開始] 畫面或使用 PowerShell 和 Remove-AppxPackage cmdlet 完成。

Windows.Networking.BackgroundTransfer 中的例外狀況

當統一資源識別元 (URI) 的無效字串傳遞至 Windows.Foundation.Uri 物件的建構函式時擲回例外狀況。

.NET:Windows.Foundation.Uri 類型在 C# 和 VB 中會顯示為 System.Uri

在 C# 和 Visual Basic 中,避免此錯誤的方法是:在建構 URI 之前,先使用 .NET 4.5 中的 System.Uri 類別及其中一個 System.Uri.TryCreate 方法,來測試從應用程式使用者接收的字串。

在 C++ 中,沒有方法可嘗試並將字串剖析為 URI。 如果應用程式取得使用者對 Windows.Foundation.Uri 的輸入,則建構函式應該位於 try/catch 區塊中。 如果發生例外狀況,應用程式可通知使用者並要求新的主機名稱。

Windows.Networking.backgroundTransfer 命名空間具有方便的協助程式方法,可在 Windows.Networking.Sockets 命名空間中使用列舉來處理錯誤, 對於在應用程式中以不同方式處理特定網路例外狀況大有幫助。

如果 Windows.Networking.backgroundTransfer 命名空間中發生非同步方法發生錯誤,會以 HRESULT 值的形式傳回。 透過 BackgroundTransferError.GetStatus 方法,則可將背景傳輸作業的網路錯誤轉換為 WebErrorStatus 列舉值。 大多數 WebErrorStatus 列舉值可對應原始 HTTP 或 FTP 用戶端作業傳回的錯誤。 應用程式可以篩選特定 WebErrorStatus 列舉值,依據例外狀況的發生原因來修改應用程式行為。

針對參數驗證錯誤,應用程式也可使用來自例外狀況的 HRESULT,深入瞭解造成例外狀況之錯誤的詳細資訊。 可能的 HRESULT 值會列在 Winerror.h 標頭檔中。 針對大多數的參數驗證錯誤,傳回的 HRESULTE_INVALIDARG

重要 API