Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier : Download stops after the app is pushed into background

  1. This method sets the background object.

    - (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];
    
    }
    
  2. These are the completion handlers.

    #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);
    }
    
  3. With out this code(beginBackgroundTaskWithExpirationHandler) the download stops when the app is pushed into background.

    //  AppDelegate_Phone.m
    - (void)applicationDidEnterBackground: (UIApplication *)application
    {
        NSLog(@"applicationDidEnterBackground");
        UIApplication  *app = [UIApplication sharedApplication];
        UIBackgroundTaskIdentifier bgTask;
    
        bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
            [app endBackgroundTask:bgTask];
        }];
    }
    
like image 826
nsingh Avatar asked Nov 25 '25 17:11

nsingh


1 Answers

Have you implemented application:handleEventsForBackgroundURLSession:completionHa‌​ndler: in your app delegate? That should save the completion handler and start background session with the specified identifier.

If you don't implement that method, your app will not be informed if the download finishes after the app has been suspended (or subsequently terminated in the course of normal app lifecycle). As a result, it might look like the download didn't finish, even though it did.

(As an aside, note that if the user force-quits the app, that not only terminates the download, but obviously won't inform your app that the download was terminated until the user manually restarts the app at some later point and your app re-instantiates the background session. This is a second-order concern that you might not worry about until you get the main background processing working, but it's something to be aware of.)

Also, your URLSessionDidFinishEventsForBackgroundURLSession: must call that saved completion handler (and dispatch this to the main queue).


Also, your design looks like it will issue only one request at a time. (I'd advise against that, but let's just assume it is as you've outlined above.) So, let's imagine that you have issued the first request and the app is suspended before it's done. Then, when the download is done, the app is restarted in the background and handleEventsForBackgroundURLSession is called. Let's assume you fixed that to make sure it restarts the background session so that the various delegate methods can be called. Make sure that when you issue that second request for the second download that you use the existing background session, not instantiating a new one. You can have only one background session per identifier. Bottom line, the instantiation of the background session should be decoupled from downloadWithURL:pathArr:mediaInfo:. Only instantiate a background session once.

like image 78
Rob Avatar answered Nov 28 '25 09:11

Rob