NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier : 应用程序被推入后台后下载停止

Posted

技术标签:

【中文标题】NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier : 应用程序被推入后台后下载停止【英文标题】:NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier : Download stops after the app is pushed into background 【发布时间】:2017-11-27 16:44:18 【问题描述】:

    此方法设置背景对象。

    - (void) downloadWithURL: (NSMutableArray *)urlArray
                 pathArr: (NSMutableArray *)pathArr
               mediaInfo: (MediaInfo *)mInfo
    
        bgDownloadMediaInfo = mInfo;
        reqUrlCount = urlArray.count;
        dict = [NSDictionary dictionaryWithObjects:pathArr
                                       forKeys:urlArray];
        mutableDictionary = [dict mutableCopy];
        backgroundConfigurationObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"XXXXX"];
        backgroundConfigurationObject.sessionSendsLaunchEvents = YES;
        backgroundConfigurationObject.discretionary = YES;
        backgroundSession = [NSURLSession sessionWithConfiguration: backgroundConfigurationObject
                                                      delegate: self delegateQueue: [NSOperationQueue currentQueue]];
        self.requestUrl = [urlArray objectAtIndex:0];
        download = [backgroundSession downloadTaskWithURL:self.requestUrl];
        [download resume];
    
    
    

    这些是完成处理程序。

    #pragma Mark - NSURLSessionDownloadDelegate
    
    - (void)URLSession: (NSURLSession *)session
          downloadTask: (NSURLSessionDownloadTask *)downloadTask
    didFinishDownloadingToURL: (NSURL *)location
    
      LogDebug(@"Download complete for request url (%@)", downloadTask.currentRequest.URL);
      NSString *temp = [mutableDictionary objectForKey:downloadTask.currentRequest.URL];
      NSString *localPath = [NSString stringWithFormat: @"%@",temp];
      NSFileManager *fileManager = [NSFileManager defaultManager];
      NSURL *destinationURL = [NSURL fileURLWithPath: localPath];
      NSError *error = nil;
      [fileManager moveItemAtURL:location toURL:destinationURL error:&error];
      LogDebug(@"Moving download file at url : (%@) to : (%@)", downloadTask.currentRequest.URL, destinationURL);
      reqUrlCount --;
      downloadSegment ++;
      // Handover remaining download requests to the OS
      if ([finalUrlArr count] != 0) 
        // remove the request from the array that got downloaded.
        [finalUrlArr removeObjectAtIndex:0];
        [finalPathArr removeObjectAtIndex:0];
        if ([finalUrlArr count] > 0) 
          // proceed with the next request on top.
          self.requestUrl = [finalUrlArr objectAtIndex:0];
          download = [backgroundSession downloadTaskWithURL:self.requestUrl];
          [download resume];
        
      
      if ([adsArray count] > 0) 
        adsArrayCount --;
        // delegate back once all the ADs segments have been downloaded.
        if (adsArrayCount == 0) 
          for (int i = 0; i < [adsArray count]; i++) 
            NSArray *ads = [adsArray objectAtIndex: i];
            for (int j = 0; j < [ads count]; j++) 
              MediaInfo *ad = [ads objectAtIndex: j];
              [self setDownloadComplete: ad];
              // skip sending downloadFinish delegate if the media is marked as downloadDone
              if (!ad.downloadDone) 
                [delegate MediaDownloadDidFinish: ad.mediaId error: NO];
              
              ad.downloadDone = YES;
            
          
          downloadSegment = 0;
        
      
    
      // delegate back once all the main media segments have been downloaded.
      if (reqUrlCount == 0) 
        [self setDownloadComplete: mediaInfo];
        state = DownloadState_Done;
        // skip sending downloadFinish delegate if the media is marked as downloadDone
        if (!bgDownloadMediaInfo.downloadDone) 
          [delegate MediaDownloadDidFinish: bgDownloadMediaInfo.mediaId error: NO];
        
        bgDownloadMediaInfo.downloadDone = YES;
        [urlArr release];
        [pathArr release];
        [finalUrlArr release];
        [finalPathArr release];
        // invalidate the NSURL session once complete
        [backgroundSession invalidateAndCancel];
      
    
    
    
    
    - (void)URLSession: (NSURLSession *)session
                  task: (NSURLSessionTask *)task
    didCompleteWithError: (NSError *)error
    
      if (error) 
        NSLog(@"Failure to download request url (%@) with error (%@)", task.originalRequest.URL, error);
      
    
    
    - (void)URLSession:(NSURLSession *)session
          downloadTask:(NSURLSessionDownloadTask *)downloadTask
          didWriteData:(int64_t)bytesWritten
     totalBytesWritten:(int64_t)totalBytesWritten
    totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
    
      // save the total downloaded size
      [self downloaderDidReceiveData:bytesWritten];
      // enable the log only for debugging purpose.
      // LogDebug(@"totalBytesExpectedToWrite %llu, totalBytesWritten %llu, %@", totalBytesExpectedToWrite, totalBytesWritten, downloadTask.currentRequest.URL);
    
    

    如果没有此代码(beginBackgroundTaskWithExpirationHandler),下载会在应用程序被推入后台时停止。

    //  AppDelegate_Phone.m
    - (void)applicationDidEnterBackground: (UIApplication *)application
    
        NSLog(@"applicationDidEnterBackground");
        UIApplication  *app = [UIApplication sharedApplication];
        UIBackgroundTaskIdentifier bgTask;
    
        bgTask = [app beginBackgroundTaskWithExpirationHandler:^
            [app endBackgroundTask:bgTask];
        ];
    
    

【问题讨论】:

你的问题是......? 顺便说一句,后台会话的实例化并不真正属于downloadWithURL:pathArr:mediaInfo:。如果您尝试下载两个文件怎么办?您不希望实例化两个后台会话。此外,完全不相关,将[NSOperationQueue currentQueue] 用于委托队列似乎是不谨慎的(因为您不知道您是从哪个队列调用它的)。如果您碰巧从某个并发队列中调用它怎么办?离开这个nil 并让它使用自己的串行队列进行后台会话的委托调用可能更安全。 didFinishDownloadingToURL 引用 finalUrlArr 有点担心。您是否将这个finalUrlArr 存储在持久存储中,然后在每次应用程序重新启动时重新加载它?请记住,在调用委托方法时,应用程序可能已经终止并重新启动。此外,您似乎一次下载一个文件,但您通常只是继续并开始后台会话的所有下载,而不是在每次下载结束时重新唤醒您的应用程序以开始下一次下载。这样更高效(电池电量和速度)。 @prokhorov - 预期行为:当应用程序被推到后台时下载应该继续(按主页按钮)实际:下载工作正常,只要应用程序在前台但下载停止按下主页按钮后。我可以通过使用 beginBackgroundTaskWithExpirationHandler 来解决问题,但根据苹果指南,我们不需要它来支持下载。 @Rob - 正如你所建议的,我尝试将 NSOperationQueue 设置为 nil。但这也无济于事。 【参考方案1】:

您是否在您的应用委托中实现了application:handleEventsForBackgroundURLSession:completionHa‌​ndler:?这应该保存完成处理程序并使用指定的标识符启动后台会话。

如果您不实施该方法,则在应用暂停(或随后在正常应用生命周期过程中终止)后下载是否完成时不会通知您的应用。因此,即使下载完成了,下载也可能看起来没有完成。

(顺便说一句,请注意,如果用户强制退出应用程序,这不仅会终止下载,而且显然不会通知您的应用程序下载已终止,直到用户稍后手动重新启动应用程序并且您的应用会重新实例化后台会话。这是一个次要问题,在主后台处理工作之前您可能不会担心,但需要注意这一点。)

此外,您的 URLSessionDidFinishEventsForBackgroundURLSession: 必须调用已保存的完成处理程序(并将其分派到主队列)。


此外,您的设计看起来一次只会发出一个请求。 (我不建议这样做,但我们假设它就像您在上面概述的那样。)因此,让我们假设您已经发出第一个请求,并且应用程序在完成之前被暂停。然后,当下载完成后,应用程序在后台重新启动并调用handleEventsForBackgroundURLSession。假设您已修复该问题以确保它重新启动后台会话,以便可以调用各种委托方法。确保当您发出第二次下载请求时,您使用现有的后台会话,而不是实例化新的。每个标识符只能有一个后台会话。最重要的是,后台会话的实例化应该与downloadWithURL:pathArr:mediaInfo: 分离。只实例化一次后台会话。

【讨论】:

【参考方案2】:

在您的 .plist 中添加“必需的背景模式”

在那里,添加“应用从网络下载内容”项

【讨论】:

以上是关于NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier : 应用程序被推入后台后下载停止的主要内容,如果未能解决你的问题,请参考以下文章

测试是不是遵守 NSURLSessionConfiguration 设置

NSURLSessionConfiguration 不接受 HTTPAdditionalHeaders 中的“内容类型”

通过NSURLSessionConfiguration对类属性property(class)的思考

NSURLSessionConfiguration 不返回 URLRequest 数据

iOS开发之网络编程--6NSURLSessionConfiguration笔记

iOS NSURLSessionConfiguration Reference