奇怪的NSURLSessionDownloadTask行为通过蜂窝(不是wifi)

我使用远程通知任务启用了后台模式,当应用程序收到推送通知时,可以在后台下载一个小文件(100kb)。 我已经使用configuration下载会话

NSURLSessionConfiguration *backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier]; [backgroundConfiguration setAllowsCellularAccess:YES]; self.backgroundSession = [NSURLSession sessionWithConfiguration:backgroundConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]]; 

并使用它来激活它

  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[hostComponents URL]]; [request setAllowsCellularAccess:YES]; NSMutableData *bodyMutableData = [NSMutableData data]; [bodyMutableData appendData:[params dataUsingEncoding:NSUTF8StringEncoding]]; [request setHTTPMethod:@"POST"]; [request setHTTPBody:[bodyMutableData copy]]; _downloadTask = [self.backgroundSession downloadTaskWithRequest:request]; [self.downloadTask resume]; 

现在一切工作正常,只有当我通过Wifi或通过蜂窝连接,但与iPhone连接到XCODO电缆,如果我断开iPhone,并通过蜂窝接收推送通知代码停止在[self.downloadTask恢复]; 没有调用URL的行。

处理这些操作的类是一个NSURLSessionDataDelegate,NSURLSessionDownloadDelegate,NSURLSessionTaskDelegate等实现:

  - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location 

我尝试在[self.downloadTask resume]之后插入带有UILocalNotification(呈现'now')的debugging行,但在5分钟后调用,并且说self.downloadTask.state是'挂起'

这是什么怪异的行为?

NSURLSessionConfiguration类参考文档在这里:

https://developer.apple.com/Library/ios/documentation/Foundation/Reference/NSURLSessionConfiguration_class/Reference/Reference.html#//apple_ref/occ/instp/NSURLSessionConfiguration/discretionary

说:对于自由裁量财产:

讨论

设置此标志时,插入电源和使用Wi-Fi时传输更有可能发生。 该值默认为false。

仅当会话的configuration对象最初通过调用backgroundSessionConfiguration:方法构造时才使用此属性,并且仅用于在应用程序处于前台时启动的任务。 如果任务在应用程序处于后台时启动,那么无论此属性的实际值如何,该任务都被视为自由裁量权为真。 对于基于其他configuration创build的会话,该属性将被忽略。

这似乎意味着如果下载在后台启动,操作系统总是有权决定是否以及何时进行下载。 在完成这些任务之前,操作系统似乎总是在等待无线连接。

我的经验支持这个猜想。 我发现我可以在设备处于蜂窝状态时发送多个通知下载。 他们仍然坚持。 当我将设备切换到无线networking时,他们都会通过。

你在做什么

  • (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void(^)(UIBackgroundFetchResult))completionHandler {}

你是否在下载完成之前立即调用completionHandler? 我相信这样做不会影响Wifi模式或连接到Xcode的操作。 但不知何故,当在蜂窝上的背景下,它会使下载失速,直到你去无线上网。

我得到了同样的问题,最后我设置了

 configuration.discretionary = NO; 

一切工作正常,为backgroundConfigurationdiscretionary = YES默认情况下,似乎任务开始连接WIFI和电池两者。 希望有帮助

唯一真正的解决方法是当应用程序在后台并使用CF套接字时转储NSURLSession。 如果我使用CFStreamCreatePairWithSocketToHost打开CFStream,我可以在应用程序处于后台的情况下成功地通过蜂窝执行HTTP请求

 #import "Communicator.h" @implementation Communicator { CFReadStreamRef readStream; CFWriteStreamRef writeStream; NSInputStream *inputStream; NSOutputStream *outputStream; CompletionBlock _complete; } - (void)setupWithCallBack:(CompletionBlock) completionBlock { _complete = completionBlock; NSURL *url = [NSURL URLWithString:_host]; //NSLog(@"Setting up connection to %@ : %i", [url absoluteString], _port); CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)[url host], _port, &readStream, &writeStream); if(!CFWriteStreamOpen(writeStream)) { NSLog(@"Error, writeStream not open"); return; } [self open]; //NSLog(@"Status of outputStream: %lu", (unsigned long)[outputStream streamStatus]); return; } - (void)open { //NSLog(@"Opening streams."); inputStream = (__bridge NSInputStream *)readStream; outputStream = (__bridge NSOutputStream *)writeStream; [inputStream setDelegate:self]; [outputStream setDelegate:self]; [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [inputStream open]; [outputStream open]; } - (void)close { //NSLog(@"Closing streams."); [inputStream close]; [outputStream close]; [inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [inputStream setDelegate:nil]; [outputStream setDelegate:nil]; inputStream = nil; outputStream = nil; } - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)event { //NSLog(@"Stream triggered."); switch(event) { case NSStreamEventHasSpaceAvailable: { if(stream == outputStream) { if (_complete) { CompletionBlock copyComplete = [_complete copy]; _complete = nil; copyComplete(); } } break; } case NSStreamEventHasBytesAvailable: { if(stream == inputStream) { //NSLog(@"inputStream is ready."); uint8_t buf[1024]; NSInteger len = 0; len = [inputStream read:buf maxLength:1024]; if(len > 0) { NSMutableData* data=[[NSMutableData alloc] initWithLength:0]; [data appendBytes: (const void *)buf length:len]; NSString *s = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; [self readIn:s]; } } break; } default: { //NSLog(@"Stream is sending an Event: %lu", (unsigned long)event); break; } } } - (void)readIn:(NSString *)s { //NSLog(@"reading : %@",s); } - (void)writeOut:(NSString *)s{ uint8_t *buf = (uint8_t *)[s UTF8String]; [outputStream write:buf maxLength:strlen((char *)buf)]; NSLog(@"Writing out the following:"); NSLog(@"%@", s); } @end