Xamarin.iOS 中的后台传输和 NSURLSession

后台传输是通过配置后台 NSURLSession 和排队上传或下载任务来启动的。 如果在应用程序处于后台、暂停或终止状态时完成任务,iOS 将通过调用应用程序 AppDelegate 中的完成事件处理器来通知应用程序。 下图演示了此操作的实际应用:

后台传输是通过配置后台 NSURLSession 和将上传或下载任务排队来启动的

配置后台会话

要创建后台会话,请使用 NSUrlSessionConfiguration 对象创建新的 NSUrlSession 并对其进行配置。

配置对象决定了会话可以执行的操作及可运行的任务类型。 使用 CreateBackgroundSessionConfiguration 方法配置的会话将在单独的进程中运行,并执行任意 (WiFi) 传输以保护数据和电池寿命。 下面的代码示例演示了如何使用 CreateBackgroundSessionConfiguration 方法和唯一字符串标识符正确设置后台传输会话:

public partial class SimpleBackgroundTransferViewController : UIViewController
{
  NSUrlSession session = null;

  NSUrlSessionConfiguration configuration =
      NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration ("com.SimpleBackgroundTransfer.BackgroundSession");
  session = NSUrlSession.FromConfiguration
      (configuration, new MySessionDelegate(), new NSOperationQueue());

}

除了配置对象以外,会话还需要会话委托和队列。 队列确定了任务完成的顺序。 会话委托则监督传输过程,并处理身份验证、缓存和其他与会话相关的问题。

使用任务和委托

配置后台会话后,让我们启动任务来处理传输。 可以使用名为会话委托的 NSUrlSessionDelegate 实例跟踪这些任务。 会话委托负责在后台唤醒已终止或挂起的应用程序来处理身份验证、错误或完成传输。

提供了 NSUrlSessionDelegate 以下用于检查传输状态的基本方法:

  • DidFinishEventsForBackgroundSession - 完成所有任务且传输完成时将调用此方法。
  • DidReceiveChallenge - 在需要授权时调用此方法以请求凭据。
  • DidBecomeInvalidWithError - 如果 NSURLSession 失效,则调用它。

后台会话需要更专门的委托,具体取决于正在运行的任务类型。 后台会话限制为两种类型的任务:

  • 上传任务 - 类型为 NSUrlSessionUploadTask 任务将会使用实现 INSUrlSessionDelegateINSUrlSessionTaskDelegate 接口。 这提供了用于跟踪上传进度、处理 HTTP 重定向等的其他方法。
  • 下载任务 - 类型为 NSUrlSessionDownloadTask 的任务将会使用实现 INSUrlSessionDelegateINSUrlSessionTaskDelegateINSUrlSessionDownloadDelegate 接口。 这提供了用于上传任务的所有方法,以及特定于下载的方法,可用于跟踪下载进度并确定下载任务何时恢复或完成。

以下代码定义了可用于从 URL 下载图像的任务。 该任务通过调用后台会话上的 CreateDownloadTask 并传入 URL 请求来启动:

const string DownloadURLString = "http://xamarin.com/images/xamarin.png"; // or other hosted file
public NSUrlSessionDownloadTask downloadTask;

NSUrl downloadURL = NSUrl.FromString (DownloadURLString);
NSUrlRequest request = NSUrlRequest.FromUrl (downloadURL);
downloadTask = session.CreateDownloadTask (request);

接下来,创建新的会话下载委托,以跟踪此会话中的所有下载任务。 委托类应继承 NSObject 并实现所需的接口:

public class MySessionDelegate : NSObject, INSUrlSessionDownloadDelegate
{
  public void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)
  {
    Console.WriteLine (string.Format ("DownloadTask: {0}  progress: {1}", downloadTask, progress));
    InvokeOnMainThread( () => {
      // update UI with progress bar, if desired
    });
  }
  ...
}

要了解下载任务的进度,请替代跟踪进度 DidWriteData 方法,甚至更新 UI。 如果应用程序位于前台,或将在下次打开应用程序时等待用户,则 UI 更新将立即显示。

会话委托 API 提供广泛的工具包来与任务交互。 有关会话委托方法的完整列表,请参阅 NSUrlSessionDelegate API 文档。

重要

后台会话在后台线程上启动,因此必须通过调用 InvokeOnMainThread 在 UI 线程上显式运行更新 UI 的任何调用,以避免 iOS 终止应用。

处理传输完成

最后一步是告知应用程序与会话关联的所有任务都已完成,并处理新内容。

AppDelegate 中订阅 HandleEventsForBackgroundUrl 事件。 当应用程序进入后台且传输会话正在运行时,将会调用此方法,并且系统将向我们传递完成事件处理器:

public System.Action backgroundSessionCompletionHandler;

public void HandleEventsForBackgroundUrl (UIApplication application, string sessionIdentifier, System.Action completionHandler)
{
  this.backgroundSessionCompletionHandler = completionHandler;
}

使用完成事件处理器告知 iOS 应用程序何时完成处理。

回想一下,会话可以生成多个任务来处理传输。 最后一个任务完成后,挂起或终止的应用程序将重新启动到后台。 然后,应用程序将使用唯一会话标识符重新连接到 NSURLSession,并调用会话委托上的 DidFinishEventsForBackgroundSession。 此方法是应用程序处理新内容的机会,包括更新 UI 以反映传输结果:

public void DidFinishEventsForBackgroundSession (NSUrlSession session) {
  // Handle new information, update UI, etc.
}

完成新内容处理后,调用完成事件处理器,以告知系统可以安全地拍摄应用程序的快照并返回到睡眠状态:

public void DidFinishEventsForBackgroundSession (NSUrlSession session) {
  var appDelegate = UIApplication.SharedApplication.Delegate as AppDelegate;

  // Handle new information, update UI, etc.

  // call completion handler when you're done
  if (appDelegate.backgroundSessionCompletionHandler != null) {
    NSAction handler = appDelegate.backgroundSessionCompletionHandler;
    appDelegate.backgroundSessionCompletionHandler = null;
    handler.Invoke ();
  }
}

本演练介绍了在 iOS 7 及更高版本中实现后台传输服务的基本步骤。