Background Transfer

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014084081/article/details/84314840

Background Transfer

参考文章:

在前面的文章笔记中,Networking with URLSession一文也讲到了后台下载的问题,上面的文章也讲到如何在后台传输,虽说大致相同,但还是有些许差别

在官方文档Downloading Content in the Background一文中,是这样解释的:

When downloading files, apps should use an NSURLSession object to start the downloads so that the system can take control of the download process in case the app is suspended or terminated. When you configure an NSURLSession object for background transfers, the system manages those transfers in a separate process and reports status back to your app in the usual way. If your app is terminated while transfers are ongoing, the system continues the transfers in the background and launches your app (as appropriate) when the transfers finish or when one or more tasks need your app’s attention.

To support background transfers, you must configure your NSURLSession object appropriately. To configure the session, you must first create a NSURLSessionConfiguration object and set several properties to appropriate values. You then pass that configuration object to the appropriate initialization method of NSURLSession when creating your session.

The process for creating a configuration object that supports background downloads is as follows:

  1. Create the configuration object using the backgroundSessionConfigurationWithIdentifier: method of NSURLSessionConfiguration.
  2. Set the value of the configuration object’s sessionSendsLaunchEvents property to YES.
  3. if your app starts transfers while it is in the foreground, it is recommend that you also set the discretionary property of the configuration object to YES.
  4. Configure any other properties of the configuration object as appropriate.
  5. Use the configuration object to create your NSURLSession object.

Once configured, your NSURLSession object seamlessly hands off upload and download tasks to the system at appropriate times. If tasks finish while your app is still running (either in the foreground or the background), the session object notifies its delegate in the usual way. If tasks have not yet finished and the system terminates your app, the system automatically continues managing the tasks in the background. If the user terminates your app, the system cancels any pending tasks.

When all of the tasks associated with a background session are complete, the system relaunches a terminated app (assuming that the sessionSendsLaunchEvents property was set to YES and that the user did not force quit the app) and calls the app delegate’s application:handleEventsForBackgroundURLSession:completionHandler: method. (The system may also relaunch the app to handle authentication challenges or other task-related events that require your app’s attention.) In your implementation of that delegate method, use the provided identifier to create a new NSURLSessionConfiguration and NSURLSession object with the same configuration as before. The system reconnects your new session object to the previous tasks and reports their status to the session object’s delegate.

如果task完成的时候app还在运行(在foreground或者background),session对象会正常的通知它的代理。如果task还没有完成,系统终止了app,系统会继续自动的在后台管理task。如果用户终止了app,系统会取消任何正在pending的task

在代理方法的实现中,使用提供的identifier,创建一个新的和以前配置一样的NSURLSessionConfigurationNSURLSession对象。系统会重新把你新的session对象和以前的task连接在一起,向session对象的代理报告它们的状态

一些疑问点

1.applicationWillTerminate(_:)方法的调用时机

关于applicationWillTerminate(_:)方法的描述:

For apps that do not support background execution or are linked against iOS 3.x or earlier, this method is always called when the user quits the app. For apps that support background execution, this method is generally not called when the user quits the app because the app simply moves to the background in that case. However, this method may be called in situations where the app is running in the background (not suspended) and the system needs to terminate it for some reason.

Introduction to iOS 9 Background Execution and Fetch一文中,有如下的描述:

The user force quitting from the multitasking UI can be the source of unreproducible crashes. It’s possible for an app to be killed without any notification at all. For example, if the app is suspended and the operating system terminates it due to low memory, no notification will be sent. Only when iOS wants to terminate an app that is not suspended and in the background state will applicationWillTerminate be called.
调用时机

2.应用被用户杀掉后,如何恢复之前的下载?

通常我们是使用downloadsSession.downloadTask(withResumeData: resumeData)方法来实现继续下载的,该方法需要传递一个resumeData

但如果在下载的过程中,用户强制退出了app之后,该如何获取resumeData呢?

iOS使用NSURLSession进行下载(包括后台下载,断点下载)中有如下的说明:

在应用被杀掉前,iOS系统保存应用下载sesson的信息,在重新启动应用,并且创建和之前相同identifier的session时(苹果通过identifier找到对应的session数据),iOS系统会对之前下载中的任务进行依次回调URLSession:task:didCompleteWithError:方法,之后可以使用上面提到的下载失败时的处理方法进行恢复下载

也可参考:

我自己试了下,如果在正在下载的过程中,强制退出程序,再次运行时,的确先会调用URLSession:task:didCompleteWithError:方法,通过erroruserInfo字典,可以获取相关的信息,userInfo字典的内容可能如下:

Printing description of userInfo:
▿ 6 elements
  ▿ 0 : 2 elements
    - key : "NSURLErrorBackgroundTaskCancelledReasonKey"
    - value : 0
  ▿ 1 : 2 elements
    - key : "NSErrorFailingURLStringKey"
    - value : https://audio-ssl.itunes.apple.com/apple-assets-us-std-000001/AudioPreview128/v4/c3/1d/f4/c31df48f-98b8-b184-8246-34d16abeac57/mzaf_1404680065846767728.plus.aac.p.m4a
  ▿ 2 : 2 elements
    - key : "NSErrorFailingURLKey"
    - value : https://audio-ssl.itunes.apple.com/apple-assets-us-std-000001/AudioPreview128/v4/c3/1d/f4/c31df48f-98b8-b184-8246-34d16abeac57/mzaf_1404680065846767728.plus.aac.p.m4a
  ▿ 3 : 2 elements
    - key : "NSURLSessionDownloadTaskResumeData"
    - value : <62706c69 73743030 d4010203 04050641 42582476 65727369 6f6e5824 6f626a65 63747359 24617263 68697665 
    .......
    a00ba511 c411c612 6f128e12 9312b812 ba12da12 df12f512 f9130f13 1c132e13 31134f00 00000000 00020100 00000000 00004500 00000000 00000000 00000000 001351>
  ▿ 4 : 2 elements
    - key : "_NSURLErrorRelatedURLSessionTaskErrorKey"
    ▿ value : 2 elements
      - 0 : BackgroundDownloadTask <844D6FB2-6B29-4CDB-A85A-BEDA310CC199>.<1>
      - 1 : LocalDownloadTask <844D6FB2-6B29-4CDB-A85A-BEDA310CC199>.<1>
  ▿ 5 : 2 elements
    - key : "_NSURLErrorFailingURLSessionTaskErrorKey"
    - value : BackgroundDownloadTask <844D6FB2-6B29-4CDB-A85A-BEDA310CC199>.<1>

包含的key有:

  • NSURLErrorBackgroundTaskCancelledReasonKey
  • NSErrorFailingURLStringKey
  • NSErrorFailingURLKey
  • NSURLSessionDownloadTaskResumeData
  • _NSURLErrorRelatedURLSessionTaskErrorKey
  • _NSURLErrorFailingURLSessionTaskErrorKey

其中的NSURLSessionDownloadTaskResumeData即为可以恢复下载的数据

有文章自定义 NSURLSessionDownloadTask Resume Data 内容说,resumeData其实是个plist文件,我也尝试了将resumeData写入一个文件,其结构形式如下(与上面的文章中有点不一样,难道我写错了?求指点):

resumedata

所以可以实现-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error方法,实现继续下载,如iOS7-Background-Transfer-Service所示:

-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
	//TODO: Check response code to report server side errors
	if (error == nil)
	{
		[_resumeData removeObjectForKey:task.originalRequest.URL.absoluteString];
	}
	else
	{
		//??: Linker cannot seem to find NSURLErrorBackgroundTaskCancelledReasonKey const, even though I see it in header.
		//id cancelReason = [error.userInfo objectForKey:NSURLErrorBackgroundTaskCancelledReasonKey];
		
		NSData *resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
		if (resumeData != nil)
		{
			dispatch_async(dispatch_get_main_queue(), ^{
				[_resumeData setObject:resumeData forKey:task.originalRequest.URL.absoluteString];
				[_delegate downloadErrorForFile:task.originalRequest.URL.absoluteString];
			});
		}
	
	}
}

也可参考如下开源项目的实现:

另外,在iOS10中,NSURLSession的resumeData貌似有点问题,可参考如下链接解决:

猜你喜欢

转载自blog.csdn.net/u014084081/article/details/84314840
今日推荐