背景傳輸
使用背景傳輸 API,透過網路可靠地複製檔案。 背景傳輸 API 提供進階的上傳和下載功能,可在應用程式暫停期間於背景中執行,並在應用程式終止後仍繼續保留。 API 會監視網路狀態,並在連線中斷時自動暫停及繼續傳輸,且傳輸功能具有數據用量感應感知和電池感應感知能力,表示下載活動會根據您目前的連線能力和裝置電池狀態進行調整。 API 非常適合使用 HTTP(S) 上傳及下載大型檔案, 也支援 FTP,但僅適用於下載。
背景傳輸會與呼叫應用程式分開執行,主要針對視訊、音樂和大型影像等資源的長期傳輸作業。 背景傳輸對於這類情況至關重要,因為即使應用程式暫停,下載仍會繼續進行。
如果是下載能快速完成的小型資源,則應使用 HttpClient API,而非背景傳輸。
使用 Windows.Networking.BackgroundTransfer
背景傳輸功能如何運作?
應用程式使用背景傳輸開始傳輸時,會使用 BackgroundDownloader 或 BackgroundUploader 類別物件來設定並初始化要求。 系統會個別處理每項傳輸作業,並且與呼叫應用程式分開執行。 您可以透過應用程式的 UI 為使用者顯示進度資訊,且即使資料正在傳輸,應用程式也可暫停、繼續、取消,或甚至讀取資料。 系統如此處理傳輸作業,可改善智慧電源使用量,並在連網應用程式遇到應用程式暫停、終止或網路狀態突然變更等事件時,防止可能發生的問題。
注意
由於每個應用程式的資源限制,因此應用程式在任何時候都不應有超過 200 個傳輸 (DownloadOperations + UploadOperations)。 超過該限制可能會使得應用程式的傳輸佇列處於無法復原狀態。
應用程式在啟動時,必須在所有現有的 DownloadOperation 和 UploadOperation物件上呼叫 AttachAsync。 若未這麼做,將導致已完成的傳輸外洩,最後使得您所用的背景傳輸功能變得沒有用處。
使用背景傳輸執行已驗證的檔案要求
背景傳輸可支援基本伺服器和 Proxy 認證、Cookie,並透過 SetRequestHeader 針對每個傳輸作業使用自訂 HTTP 標頭。
此功能如何因應網路狀態變更或非預期關機?
網路狀態發生變更時,背景傳輸功能可透過智慧運用連線能力功能提供的連線能力,以及電信業者行動數據方案狀態資訊,維護各項傳輸作業的一致體驗。 若要定義不同網路狀況的行為,應用程式會使用 backgroundTransferCostPolicy 定義的值,為每個作業設定成本原則。
例如,根據為作業定義的成本原則,當裝置使用計量付費網路時,應自動暫停作業; 連線至「不受限制」的網路時,則會自動恢復傳輸 (或重新開機)。 關於如何定義網路成本,詳細資訊請參閱 NetworkCostType。
雖然針對如何處理網路狀態變更,背景傳輸功能有自己的一套機制,連網應用程式還有其他一般連線考量。 如需詳細資訊,請參閱運用可用的網路連線資訊。
注意 行動裝置上的應用程式可能包含某些功能,可讓使用者根據連線類型、漫遊狀態和使用者的通話方案,監視與限制傳輸資料量。 因此,即使 BackgroundTransferCostPolicy 表示傳輸應繼續,手機的背景傳輸仍可能暫停。
下表說明根據手機的狀態,每個 BackgroundTransferCostPolicy 值何時允許手機進行背景傳輸。 您可使用 ConnectionCost 類別來判斷手機的目前狀態。
裝置狀態 | 僅限不受限制的網路 | 預設 | 永遠 |
---|---|---|---|
連接至 WiFi | 允許 | 允許 | 允許 |
計量付費連線、非漫遊、低於資料傳輸量上限、追蹤以保持低於上限 | 拒絕 | 允許 | 允許 |
計量付費連線,非漫遊、超過資料傳輸量上限、追蹤確認是否超過上限 | 拒絕 | 拒絕 | 允許 |
計量付費連線、非漫遊、低於資料傳輸量上限 | 拒絕 | 拒絕 | 允許 |
計量付費連線、超過資料傳輸量上限。 只有當使用者啟用「限制數據用量感知 UI 的背景資料」時,才會發生此狀態。 | 拒絕 | 拒絕 | 拒絕 |
上傳檔案
使用背景傳輸時,上傳將採取 UploadOperation 的形式,並可公開多個用於重新開機或取消作業的控制方法。 系統會對每個 UploadOperation 自動處理 app 事件 (例如暫停或終止) 和連線變更;在 app 暫停期間上傳仍將繼續,或在 app 終止之後會暫停或持續下去。 此外,藉由設定 CostPolicy 屬性,可指出在使用計量付費網路作為網路連線時,應用程式是否會進行上傳作業。
下列範例將逐步引導您建立並初始化基本上傳,以及如何列舉和重新引入先前應用程式工作階段保存的作業。
上傳單一檔案
若想建立上傳,須從 BackgroundUploader 開始, 此類別可幫助應用程式先設定上傳,再建立結果 UploadOperation。 下列範例說明如何使用必要的 Uri 和 StorageFile 物件來執行此動作。
識別上傳的檔案和目的地
在開始建立 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);
});
}
建立和初始化上傳作業
上一個步驟的 uriString 和 file 值已傳遞至下一個範例 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 時,會釋放所有相關聯的系統資源。 不過,如果您的應用程式在發生上述動作前已終止,則不只任何進行中的作業將暫停,且仍會佔用每個作業相關的資源。 如果未列舉這些作業並重新導入下一個應用程式工作階段,作業將不會完成,且繼續佔用裝置資源。
在定義列舉持續作業的函式之前,必須建立陣列來包含預定傳回的 UploadOperation 物件:
var uploadOperations = [];
接下來會定義列舉持續作業的函式,並將其儲存在陣列中。 請注意,將回呼重新指派給 UploadOperation 的 load 方法,應在應用程式終止期間持續,並位於我們稍後會在本節定義的 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 讓使用者指出裝置上儲存檔案的位置。 請注意,將回呼重新指派給 DownloadOperation 的 load 方法,應在應用程式終止期間持續,並位於我們稍後會在本節定義的 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 時,會釋放所有相關聯的系統資源。 不過,如果您的應用程式在發生上述動作前已終止,下載作業將會暫停,並保存於背景中。 下列範例說明如何將保存的下載重新導入新的應用程式工作階段。
在定義列舉持續作業的函式之前,必須建立陣列來包含預定傳回的 DownloadOperation 物件:
var downloadOps = [];
接下來會定義列舉持續作業的函式,並將其儲存在陣列中。 請注意,將回呼重新指派給持續 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); } });
您現在可以使用填入的清單來重新啟動擱置作業。
後續處理
Windows 10 的新功能是能夠在背景傳輸完成時 (即使應用程式未執行) 執行應用程式程式碼。 例如,您可能希望電影下載完成後,應用程式能更新可用的電影清單,而不是在每次啟動時掃描是否有新電影。 或者,您希望應用程式能使用不同的伺服器或連接埠,再次嘗試處理失敗的檔案傳輸。 成功和失敗的傳輸都可叫用後續處理,因此您可以藉此執行自訂錯誤處理並重試邏輯。
後續處理以現有的背景工作為基礎結構, 您可在開始傳輸前,先建立關聯的背景工作。 接著,傳輸會在背景中執行,並在完成時呼叫背景工作來執行後續處理。
後續處理會使用 BackgroundTransferCompletionGroup 此一新類別。 類似於現有的 BackgroundTransferGroup,此類別有助於將背景傳輸結成群組,但 BackgroundTransferCompletionGroup 新增一項功能,可以在傳輸完成後指定要執行的背景工作。
您可按照以下步驟,使用後續處理來起始背景傳輸。
- 建立一個 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();
- 接下來,建立背景傳輸與完成群組的關聯。 建立所有傳輸後,請啟用完成群組。
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();
- 背景工作中的程式碼會從觸發程序詳細資料擷取作業清單,然後您的程式碼可以檢查每個操作的詳細資料,並針對每個作業執行適當的後續處理。
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 標頭檔中。 針對大多數的參數驗證錯誤,傳回的 HRESULT 是 E_INVALIDARG。