问题
I've enabled Background Modes with remote-notification tasks to download a small file (100kb) in background when the app receives a push notification. I've configured the download Session using
NSURLSessionConfiguration *backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier];
[backgroundConfiguration setAllowsCellularAccess:YES];
self.backgroundSession = [NSURLSession sessionWithConfiguration:backgroundConfiguration
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
and activate it using
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];
Now everything works correctly only if I'm connected over Wifi or over Cellular but with the iPhone connected with the cable to xCode, if I disconnect the iPhone and receive a push notification over cellular the code stop at [self.downloadTask resume];
line without call the URL.
The class that handles these operation is a NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSURLSessionTaskDelegate and so implements:
- (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
I've try to insert a debug line with a UILocalNotification
(presented 'now') after the [self.downloadTask resume]
but is called after 5 minutes and says that the self.downloadTask.state
is 'suspended'
What is due this weird behavior ?
回答1:
The documentation for NSURLSessionConfiguration Class Reference here:
https://developer.apple.com/Library/ios/documentation/Foundation/Reference/NSURLSessionConfiguration_class/Reference/Reference.html#//apple_ref/occ/instp/NSURLSessionConfiguration/discretionary
Says: for the discretionary property:
Discussion
When this flag is set, transfers are more likely to occur when plugged into power and on Wi-Fi. This value is false by default.
This property is used only if a session’s configuration object was originally constructed by calling the backgroundSessionConfiguration: method, and only for tasks started while the app is in the foreground. If a task is started while the app is in the background, that task is treated as though discretionary were true, regardless of the actual value of this property. For sessions created based on other configurations, this property is ignored.
This seems to imply that if a download is started in the background the OS always has discretion as to whether and when to proceed with the download. It seems that the OS is always waiting for a wifi connection before completing these tasks.
My experience supports this conjecture. I find that I can send several notifications for downloads while device is on cellular. They remain stuck. When I switch the device to wifi they all go through.
回答2:
I got the same problem ,finally i set
configuration.discretionary = NO;
the everything works fine,
for backgroundConfiguration
,
discretionary = YES
default, it seems the task begin in connection with WIFI
and battery both. hope helpful
回答3:
What are you doing in
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{}
Are you calling the completionHandler right away before your download completes? I believe that doing this does not affect operation in Wifi mode or when connected to Xcode. But somehow when in background on Cellular it makes the download stall until you go to Wifi.
回答4:
The only real way around this is to dump NSURLSession when the app is in the background and use CF sockets. I can successfully do HTTP requests over cellular while the app is in background if I use CFStreamCreatePairWithSocketToHost
to open CFStream
#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
来源:https://stackoverflow.com/questions/24665161/weird-nsurlsessiondownloadtask-behavior-over-cellular-not-wifi