在移动数据网络下(非Wi-Fi),NSURLSessionDownloadTask 表现异常奇怪

13

我已经启用了后台模式和远程通知任务,以便在应用程序接收到推送通知时,在后台下载一个小文件(100kb)。 我使用了配置下载会话的方式:

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连接到xCode,然后断开iPhone并在蜂窝网络上接收推送通知,代码会停止在[self.downloadTask resume];这行而不调用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来进行调试,但是它会在5分钟后被调用,并说self.downloadTask.state处于“暂停”状态。

这种奇怪的行为是由什么引起的?


我在ATT网络和IOS 7.0上遇到了完全相同的问题。由于连接到Xcode时不会发生此问题,因此我将日志保存在手机上以查看何时重新启动应用程序。发现NSURLSessionDownloadTasks仍保持State = NSURLSessionTaskStateRunning,但表现为已暂停。如果在应用程序仍处于后台时连接到WiFi,则这些停滞的下载将成功完成。如果我将应用程序带入前台,则它们仍将停滞不前。 - Sani Elfishawy
从Sani Elfshishawy的回答中可以看出,“当插入电源并连接Wi-Fi时”,我认为插入XCODE会改变插入电源的状态。 - Ryan Heitner
我也遇到了同样的情况。我正在将iBeacon与后台NSURLSession后台任务结合使用,以在用户接近信标时执行一些检查。但是我发现当用户使用蜂窝网络时,下载任务不可用。你们有什么建议吗? - Tony Fung
4个回答

9

这里是NSURLSessionConfiguration类参考文档:

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

说:对于自主属性:

讨论

当设置此标志时,传输更可能发生在插入电源和Wi-Fi上时。默认情况下,此值为false。

仅当会话的配置对象最初是通过调用backgroundSessionConfiguration:方法构建的,并且仅针对在应用程序前台启动的任务时,才使用此属性。如果在应用程序后台启动任务,则该任务被视为自主为true,而不管此属性的实际值如何。对于基于其他配置创建的会话,将忽略此属性。

这似乎意味着,如果在后台启动下载,操作系统始终有自主权来确定何时进行下载。看起来操作系统总是在等待wifi连接才能完成这些任务。

我的经验支持这个推测。我发现我可以在设备使用蜂窝数据时发送多个下载通知。它们保持停滞不前。当我将设备切换到wifi时,它们都会通过。


什么鬼?!为什么苹果要这么做?当然,用户在启动下载任务时通常不会处于WiFi环境中。在我的应用程序中,当在后台且在蜂窝网络下(例如由于位置更改而导致应用程序唤醒)时,我依赖于下载非常少量的数据。这是否有可能? - blackjacx
这应该被标记为答案,非常准确。我见过几个神秘的问题,只有在通过电缆连接时才会神奇地开始下载,而且总是由于这个设置引起的。 - Matt

3
我遇到了同样的问题,最终我进行了以下设置:
configuration.discretionary = NO;

一切都正常,对于backgroundConfigurationdiscretionary = YES是默认值,似乎任务开始与WIFI和电池连接相关联。希望有所帮助。


0
唯一真正的解决方法是在应用程序处于后台时放弃使用NSURLSession并使用CF套接字。如果我使用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

0
你在做什么?
  • (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{}

你是否在下载完成之前立即调用了completionHandler?我相信这样做不会影响Wi-Fi模式下或连接到Xcode时的操作。但是在蜂窝后台模式下,它会导致下载停滞,直到你切换到Wi-Fi。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接