Странное поведение NSURLSessionDownloadTask по сотовому (а не Wi-Fi)
Я включил фоновые режимы с задачами удаленного уведомления, чтобы загрузить небольшой файл (100kb) в фоновом режиме, когда приложение получает push-уведомление. Я настроил сеанс загрузки, используя
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];
Теперь все работает правильно, только если я подключен через Wi-Fi или через Cellular, но с iPhone, подключенным к кабелю к xCode, если я отключу iPhone и получаю push-уведомление по сотовой сети, остановите код в [self.downloadTask resume];
line без вызова 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
Я попытался вставить строку отладки с UILocalNotification
(представленную "сейчас") после [self.downloadTask resume]
но вызывается через 5 минут и говорит, что self.downloadTask.state
"приостановлено",
Что из-за этого странного поведения?
Ответы
Ответ 1
Документация для NSURLSessionConfiguration Class Reference здесь:
https://developer.apple.com/Library/ios/documentation/Foundation/Reference/NSURLSessionConfiguration_class/Reference/Reference.html#//apple_ref/occ/instp/NSURLSessionConfiguration/discretionary
Говорит: для дискреционного имущества:
обсуждение
Когда этот флаг установлен, передача будет более вероятной при подключении к сети и Wi-Fi. По умолчанию это значение ложно.
Это свойство используется, только если объект конфигурации сеанса изначально был сконструирован путем вызова метода backgroundSessionConfiguration: и только для задач, запущенных, когда приложение находится на переднем плане. Если задача запущена, когда приложение находится в фоновом режиме, эта задача обрабатывается, как если бы дискреционные были истинными, независимо от фактического значения этого свойства. Для сеансов, созданных на основе других конфигураций, это свойство игнорируется.
Это, по-видимому, подразумевает, что при запуске загрузки в фоновом режиме ОС всегда имеет право на то, будет ли и когда будет выполняться загрузка. Кажется, что ОС всегда ждет подключения Wi-Fi до завершения этих задач.
Мой опыт подтверждает эту гипотезу. Я обнаружил, что могу отправлять несколько уведомлений для загрузки, пока устройство находится на сотовой сети. Они остаются застрявшими. Когда я переключаю устройство на Wi-Fi, все они проходят.
Ответ 2
У меня такая же проблема, наконец, я установил
configuration.discretionary = NO;
все работает отлично, для backgroundConfiguration
, discretionary = YES
умолчанию, кажется, задача начинается в связи с WIFI
и аккумулятором. надеяться на помощь
Ответ 3
Что вы делаете в
- (void): (UIApplication *) приложение didReceiveRemoteNotification: (NSDictionary *) userInfo fetchCompletionHandler: (void (^) (UIBackgroundFetchResult)) completeHandler {}
Вы вызываете завершениеHandler сразу перед загрузкой? Я считаю, что это не влияет на работу в режиме Wi-Fi или при подключении к Xcode. Но почему-то, когда в фоновом режиме на Cellular это делает загрузку, пока вы не перейдете на Wi-Fi.
Ответ 4
Единственный реальный способ - сбросить NSURLSession, когда приложение находится в фоновом режиме и использовать сокеты CF. Я могу успешно выполнять HTTP-запросы по сотовой сети, когда приложение находится в фоновом режиме, если я использую CFStreamCreatePairWithSocketToHost
для открытия 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