2

我正在使用后台传输服务下载多个视频NSURLSession。当应用程序处于后台模式时下载工作正常并且我对此感到满意。我的问题是,我想为从队列中下载的每个视频进行回调。

我期待为每个下载的视频调用以下方法:

-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier
 completionHandler:(void (^)())completionHandler

当系统在后台传输后没有更多消息要发送到我们的应用程序时,请使用以下方法:

-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session

但是,当所有下载完成时,这两种方法都会被调用。我放了 3 个视频供下载,然后将 App 放在后台。下载所有 3 个视频后调用这两种方法。


这是我在这些方法中所做的:

应用委托

-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier 
 completionHandler:(void (^)())completionHandler
{    
    self.backgroundTransferCompletionHandler = completionHandler;
}

下载视图控制器

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

    if (appDelegate.backgroundTransferCompletionHandler) 
    {
        void (^completionHandler)() = appDelegate.backgroundTransferCompletionHandler;
        appDelegate.backgroundTransferCompletionHandler = nil;
        completionHandler();
    }

    NSLog(@"All tasks are finished");
}

是否可以在下载每个视频时向用户显示本地通知?或者,我必须等到所有视频在后台完成下载?

如果答案是否定的,那么我的问题是这两个不同回调的目的是什么?是什么将它们彼此分开?

4

2 回答 2

1

这里的问题是您正在使用NSURLSessionDelegate,它为您提供有关当前下载会话的信息。但是,您想了解有关单个任务的信息,而不是整个会话。因此,您应该查看NSURLSessionTaskDelegateNSURLSessionDownloadDelegate

具体来说,使用NSURLSessionDownloadDelegate,您应该实现这个委托方法:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location

如果下载完成时应用程序处于后台模式,则不会自动调用此方法。但是,系统会发出调用application:handleEventsForBackgroundURLSession:completionHandler:,让您有机会重建会话并响应事件(例如,根据您的要求触发通知)更多信息在这里

在 iOS 中,当后台传输完成或需要凭据时,如果您的应用程序不再运行,您的应用程序会在后台自动重新启动,并且应用程序的 UIApplicationDelegate 会发送一个 application:handleEventsForBackgroundURLSession:completionHandler: 消息。此调用包含导致您的应用程序启动的会话的标识符。然后,您的应用应在创建具有相同标识符的后台配置对象并使用该配置创建会话之前存储该完成处理程序。新创建的会话会自动与正在进行的后台活动重新关联。

最后,几年前我做了一个开源项目,一个 NSURLSession 的包装器。这个项目是为 iOS 7 制作的,所以它可能使用了一些不推荐使用的方法,但是这个答案所涵盖的部分仍然有效。链接到 FLD 下载器

编辑 在罗布的回答之后,我做了一些检查。处于挂起状态的应用程序和处于终止状态的应用程序之间的行为似乎有所不同。

  • 似乎,当应用程序关闭(杀死)时,系统application:handleEventsForBackgroundURLSession:completionHandler:只会在所有下载完成后才会唤醒它。我尝试将 XCode 附加到我的 iPhone 上,它似乎是正确的。
  • 但是,在此链接中,在“后台传输注意事项”中,似乎如果应用程序处于“暂停”状态,它会显示:

如果在您的应用程序挂起时完成了任何任务,则委托的 URLSession:downloadTask:didFinishDownloadingToURL: 方法随后会使用该任务以及与之关联的新下载文件的 URL 进行调用。

编辑

最后一个断言,如果苹果文档确认的事件,似乎是错误的。我亲自检查了 iPhone 6S、iOS 9.3.2、XCode 和 Instruments。我开始了两次下载并关闭了应用程序(暂停状态,由 Istruments 活动监视器确认 - 进程仍然存在,但没有消耗 cpu 时间)但URLSession:downloadTask:didFinishDownloadingToURL:没有调用该方法。但是,当两个下载都完成application:handleEventsForBackgroundURLSession:completionHandler:时被调用。

于 2016-05-23T09:14:57.303 回答
1

是否可以在下载每个视频时向用户显示本地通知?或者,我必须等到所有视频在后台完成下载?

该应用程序只会在handleEventsForBackgroundURLSession与该会话相关的所有下载完成后在后台重新启动,而不是一个接一个地重新启动。后台会话的想法是最大程度地减少在后台运行(或重复启动然后暂停)的电池消耗,而是让后台守护进程为您执行此操作并让您知道一切何时完成。

从理论上讲,您可能能够为每个会话实例化一个单独的后台会话,但这让我觉得这是对后台会话的滥用(其目的是减少启动您的应用程序并在后台运行它所花费的时间),我不会如果 Apple 不赞成这种做法,请不要感到惊讶。它还需要更笨拙的实现(具有多个NSURLSession对象)。

如果答案是否定的,那么我的问题是这两个不同回调的目的是什么?是什么将它们彼此分开?

单独回调的目的是,一旦您的应用程序再次运行,它可以为每次下载执行所需的任何后期处理(例如,将文件从临时位置移动到最终位置)。每次下载都需要单独的回调,即使在后台模式下重新启动应用程序时它们都被快速连续调用。另外,如果该应用程序恰好已经在前台运行,您可以在完成后处理各个下载。


顺便说一句,LombaX 是正确的,handleEventsForBackgroundURLSession应该是启动后台会话。就个人而言,我为对象创建completionHandler了我的包装器的NSURLSession属性,因此handleEventsForBackgroundURLSession将实例化它(准备好调用其委托方法),并保存在completionHandler那里。这是保存完成处理程序的合乎逻辑的地方,NSURLSession无论如何您都必须实例化它及其委托,并且它URLSessionDidFinishEventsForBackgroundURLSession不需要返回到应用程序委托来获取保存的完成处理程序。

对或错,我的典型实现是将后台 NSURLSession 对象设为单例。因此,我最终得到了类似的结果:

- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {    
    [BackgroundSession sharedSession].savedCompletionHandler = completionHandler;
}

用一块石头杀死两只鸟,启动后台 NSURLSession,并保存completionHandler.

于 2016-05-23T10:22:53.800 回答