背景傳輸

使用背景傳輸 API 在網路上可靠複製檔案。 背景傳輸 API 提供進階的上傳與下載功能,這些功能會在應用程式暫停期間背景執行,並在應用程式終止後持續存在。 API 監控網路狀態,當連線中斷時會自動暫停與恢復傳輸,傳輸同時具備資料感知與電池感知能力,也就是說下載活動會根據你目前的連線狀況和裝置電池狀態調整。 此 API 非常適合使用 HTTP(S) 上傳與下載大型檔案。 也支援 FTP,但僅限於下載。

Note

這些 Windows.Networking.BackgroundTransfer API 是 Windows 執行階段(WinRT)API,適用於 WinUI 3(Windows 應用程式 SDK)桌面應用程式以及 UWP 應用程式。 背景傳輸需要套件身份;未封裝的應用程式無法使用此 API。

背景傳輸與通話應用程式分開運作,主要設計用於影片、音樂及大型影像等資源的長期傳輸操作。 在這種情況下,使用背景轉移非常重要,因為即使應用程式暫停,下載仍會繼續進行。

如果你下載的是很可能很快就能完成的小型資源,則應使用 HttpClient API,而不是使用背景傳輸。

使用 Windows.Networking.BackgroundTransfer

背景轉移功能是如何運作的?

當應用程式使用 Background Transfer 來啟動傳輸時,請求會使用 BackgroundDownloaderBackgroundUploader 類別物件進行設定與初始化。 每個傳輸操作由系統獨立處理,且與呼叫應用程式分開處理。 如果你想在應用程式介面中向使用者提供狀態,進度資訊會提供,應用程式可以在傳輸過程中暫停、繼續、取消,甚至讀取資料。 系統處理傳輸的方式促進智慧的電力使用,並防止連接應用程式遭遇暫停、終止或網路狀態突然變更等事件時可能產生的問題。

Note

由於每個應用程式的資源限制,應用程式在任何時刻傳輸次數不應超過 200 次(DownloadOperations + UploadOperations)。 超過這個限制可能會讓應用程式的傳輸佇列陷入無法恢復的狀態。

當應用程式啟動時,必須對所有現有的 DownloadOperationUploadOperation 物件呼叫 AttachAsync。 不這麼做會導致已完成的傳輸資料外洩,最終使你無法使用背景傳輸功能。

透過背景傳輸提出已驗證的檔案請求

背景傳輸提供的方法可支援伺服器和 Proxy 的基本驗證認證、Cookie,以及在每次傳輸作業中使用自訂 HTTP 標頭(透過 SetRequestHeader)。

這個功能如何適應網路狀態變化或意外關機?

背景傳輸功能會在網路狀態發生變更時,藉由智慧地運用 連線能力 功能所提供的連線能力與電信業者數據方案狀態資訊,為每次傳輸作業維持一致的體驗。 為了定義不同網路情境的行為,應用程式會根據 BackgroundTransferCostPolicy 定義的值為每個操作設定成本政策。

例如,為某項操作定義的成本政策,可以指示當裝置使用計量網路時,該操作應自動暫停。 當建立到「無限制」網路的連線時,傳輸會自動恢復(或重新啟動)。 關於網路如何由成本定義的更多資訊,請參見 NetworkCostType

雖然背景傳輸功能有自己的處理網路狀態變更機制,但對於網路連接的應用程式來說,還有其他一般的連接性考量。 用 Windows。網路。連接 API 用於監控連線狀態與成本資訊。

Note

對於在行動裝置上運行的應用程式,有功能允許使用者根據連線類型、漫遊狀態及用戶的數據方案監控並限制傳輸的資料量。 因此,即使 BackgroundTransferCostPolicy 指出應繼續傳輸,背景傳輸在手機上仍可能會被暫停。

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

裝置狀態 無限制 預設值 永遠
連接 WiFi 允許 允許 允許
計量連線,未漫遊,資料限制內,且保持在限制範圍內 Deny 允許 允許
計量連線,未漫遊,資料限制內,預計超額 Deny Deny 允許
計量連線,漫遊,資料限制下 Deny Deny 允許
計量連線,超過數據限制。 此狀態僅在使用者啟用「限制資料感知介面中的背景資料」時發生。 Deny Deny Deny

上傳檔案

使用背景傳輸時,上傳會以 UploadOperation 形式存在,該操作會暴露出多種控制方法,用來重新啟動或取消操作。 應用程式事件(例如暫停或終止)及連線變更由系統依 UploadOperation 自動處理;上傳會在應用程式暫停期間持續,或暫停並持續存在,超過應用程式終止。 此外,設定 CostPolicy 屬性會顯示你的應用程式是否會在使用計量網路連線時開始上傳。

以下範例將引導你建立與初始化基本上傳程式,以及如何列舉並重新引入從先前應用程式會話中持續存在的操作。

上傳單一檔案

上傳的建立始於 BackgroundUploader。 此類別用於提供方法,讓您的應用程式能在建立所產生的 UploadOperation 之前先設定上傳作業。 以下範例展示了如何利用所需的 UriStorageFile 物件來實現此操作。

請確認上傳的檔案與目的地

在開始建立 UploadOperation 之前,我們首先需要確認要上傳地點的 URI 以及將上傳的檔案。 以下範例中, uriString 值使用 UI 輸入的字串填充, 檔案值則 使用 StorageFile 物件,並由 PickSingleFileAsync 操作回傳。

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);
    });
}

建立並初始化上傳操作

在前一步, uriString檔案 值會傳給下一個範例 UploadOp 的實例,用來設定並啟動新的上傳操作。 首先,解析 uriString 以建立所需的 Uri 物件。

接著,BackgroundUploader 會利用所提供的 StorageFile檔案)屬性來填充請求標頭,並將 SourceFile 屬性設定為 StorageFile 物件。 接著呼叫 SetRequestHeader 方法插入以字串形式提供的檔名及 StorageFile.Name 屬性。

最後, BackgroundUploader 會建立 UploadOperation上傳)。

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 = upload.startAsync().then(complete, error, progress);

非同步方法呼叫後會接一個 then 陳述,表示應用程式定義的方法在非同步呼叫結果回傳時會被呼叫。 欲了解更多關於此程式設計模式的資訊,請參閱 JavaScript 中使用 promises 的非同步程式設計

上傳多個檔案

請確認上傳的檔案與目的地

在涉及多個檔案以單一 UploadOperation 傳輸的情況中,流程如常般開始,首先提供所需的目的地 URI 及本地檔案資訊。 與前一節的範例類似,URI 由終端使用者以字串形式提供, FileOpenPicker 也可用來透過使用者介面指示檔案。 然而,在這種情況下,應用程式應該呼叫 PickMultipleFilesAsync 方法,讓使用者能透過介面選擇多個檔案。

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 陣列已填入所有代表各個要上傳之 IStorageFileBackgroundTransferContentPart 物件後,我們就可以使用 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);
         }
     };

重新啟動已中斷的上傳作業

UploadOperations 完成或取消時,任何相關的系統資源都會被釋放。 然而,如果你的應用程式在這些事情發生前就被終止,所有正在進行的操作都會被暫停,且與每個操作相關的資源仍然被佔用。 若這些操作未被列舉並重新引入至下一個應用程式會話,將無法完成,且持續佔用裝置資源。

  1. 在定義枚舉持久操作的函式之前,我們需要建立一個陣列,包含它會回傳的 UploadOperation 物件:

    var uploadOperations = [];
    
  2. 接著我們定義一個函式,該函式枚舉持久化的操作並將其儲存在陣列中。 請注意,若 UploadOperation 在應用程式終止後仍持續存在,則用來重新指派回調的載入方法屬於我們稍後定義的 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 自動處理;下載會在應用程式暫停期間持續進行,或暫停並持續存在,超過應用程式終止。 在行動網路情境下,設定 CostPolicy 屬性會顯示在計量網路使用時,應用程式是否會開始或繼續下載。

如果你下載的是很可能很快就能完成的小型資源,則應使用 HttpClient API,而不是使用背景傳輸。

以下範例將引導你建立與初始化基本下載,以及如何列舉並重新引入從先前應用程式工作階段持續存在的操作。

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

以下範例展示如何利用代表 URI 與檔案名稱的字串來建立 Uri 物件及包含所請求檔案的 StorageFile 。 在此範例中,新檔案會自動放置在預先定義的位置。 另外,也可以使用 FileSavePicker ,讓使用者在裝置上指定檔案儲存的位置。 請注意,若 DownloadOperation 在應用程式終止後仍持續使用,則用來重新指派回調的載入方法屬於本節後面定義的 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 承諾定義的非同步方法呼叫。 參考前一個程式碼範例中的第17行:

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

非同步方法呼叫後面接著一個 then 陳述式,用來指出由應用程式定義、在非同步方法呼叫傳回結果時會呼叫的方法。 欲了解更多關於此程式設計模式的資訊,請參閱 JavaScript 中使用 promises 的非同步程式設計

新增操作控制方法

透過實作額外的 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);
    }
};

啟動時列舉已持久化的操作

完成或取消下載 操作後,任何相關的系統資源都會被釋放。 然而,若在上述任一事件發生前終止應用程式,下載將暫停並在背景持續存在。 以下範例示範如何將持久下載重新引入新應用程式會話。

  1. 在定義枚舉持久操作的函式之前,我們需要建立一個陣列,包含它將回傳的 DownloadOperation 物件:

    var downloadOps = [];
    
  2. 接著我們定義一個函式,該函式枚舉持久化的操作並將其儲存在陣列中。 請注意,用來為已持久化的 DownloadOperation 重新指派回呼的 load 方法,就是我們在本節稍後定義的 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 屬性設為 平行,則可能發生此結果。 此問題僅在開發過程中的特定情境發生,且不適用於您的應用程式終端使用者。

使用 Visual Studio 的四個情境都可能導致這個問題。

  • 你可以建立一個新專案,使用與現有專案相同的應用程式名稱,但語言不同(例如從 C++ 到 C#)。
  • 你只需要在現有專案中更改目標架構(例如從 x86 改成 x64)。
  • 你改變現有專案的文化(例如從中立變成 en-US)。
  • 你可以在現有專案中新增或移除套件清單中的一項能力(例如新增 企業認證)。

一般的應用程式服務,包括新增或移除功能的清單更新,不會在終端使用者部署時觸發此問題。 為了解決這個問題,請完全卸載所有版本的應用程式,並重新部署到新的語言、架構、文化或功能上。 這可以透過 開始 畫面或使用 PowerShell 和 Remove-AppxPackage 指令檔來完成。

Windows.Networking.BackgroundTransfer 中的例外狀況

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

.NET:Windows。Foundation.Uri 型態在 C# 和 VB 中以 System.Uri 形式出現。

在 C# 和 Visual Basic 中,可以透過使用 .NET 4.5 中的 System.Uri 類別以及 System.Uri.TryCreate 方法來測試應用程式使用者在建構 URI 前收到的字串來避免此錯誤。

在 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