通过 NSURLSession / NSURLSessionDownloadTask 下载完成时发送本地通知
Posted
技术标签:
【中文标题】通过 NSURLSession / NSURLSessionDownloadTask 下载完成时发送本地通知【英文标题】:Send local notification when download completes through NSURLSession / NSURLSessionDownloadTask 【发布时间】:2015-07-29 20:18:46 【问题描述】:我在NSURLSession
上使用NSURLSessionDownloadTask
对象,以允许用户在应用处于后台/设备锁定时下载文档。我还想通过本地通知通知用户个别下载已完成。
为此,我在 -URLSession:downloadTask:didFinishDownloadingToURL:
下载任务委托方法中触发本地通知,但是我想知道是否有更好的地方添加触发通知的代码,因为 Apple 解释它的方式,下载任务将被传递给系统,由此我得出结论,一旦(或不久之后)应用程序后台运行,下载任务的委托将不再调用这些委托。
我的问题:添加触发本地通知的代码的最佳位置是什么?有没有人有过在他们的应用程序中添加这种功能的经验?
【问题讨论】:
我在试验中注意到的一些事情:当多个文件排队等待下载时,通知要么合并并在所有下载结束时发送,要么并非所有文件都被触发。我还尝试在-application:handleEventsForBackgroundURLSession:completionHandler:
中添加通知触发代码,但 似乎 (从我到目前为止所做的测试中)只触发一次通知,而不是针对已下载的每个文档.
【参考方案1】:
您的问题的答案可以在 Apple 文档URL Loading System Programming Guide 中找到:
在 ios 中,当后台传输完成或需要凭据时, 如果您的应用不再运行,iOS 会自动重新启动您的 后台应用程序并调用
application:handleEventsForBackgroundURLSession:completionHandler:
应用程序的UIApplicationDelegate
对象上的方法。此调用提供 导致您的应用程序启动的会话的标识符。 您的应用程序应该存储该完成处理程序,创建一个背景 具有相同标识符的配置对象,并创建一个会话 使用该配置对象。新会话自动 与正在进行的后台活动重新关联。后来,当会话 完成最后一个后台下载任务,它发送会话 委托URLSessionDidFinishEventsForBackgroundURLSession:
消息。 然后,您的会话委托应调用存储的完成处理程序。如果在您的应用暂停期间完成了任何任务,委托的 然后
URLSession:downloadTask:didFinishDownloadingToURL:
方法 使用任务和新下载文件的 URL 调用 相关联。
如您所见,它比设置delegate
对象要复杂得多。通过委托方法,只有当应用程序处于前台模式时才会通知您。在其他情况下(应用程序处于后台模式,应用程序被终止)您需要处理上面引用中描述的 AppDelegate
方法。
Apple 还提供example project,它展示了如何处理后台下载/上传任务。此示例将帮助您找到放置“本地通知”代码的位置。
【讨论】:
尽管文档说每次任务完成时都会调用application:handleEventsForBackgroundURLSession:completionHandler
,但我发现情况并非如此,它实际上是在整个会话完成时调用一次。对此有何想法?
@Andrei 并不是说每次任务完成时都会调用它。它表示如果在任务完成时应用程序被终止,则将调用此方法。当所有其他任务完成时,应用程序可能处于后台。在这种情况下,这个方法只会被调用一次。
感谢您的快速回复。知道我应该如何找出一项任务何时完成?
@Andrei,据我了解,如果在应用程序终止时任务完成,您需要处理此调用 application:handleEventsForBackgroundURLSession:completionHandler
。在所有其他情况下,您需要处理 NSURLSession
委托方法。此外,Apple 文档中描述的所有逻辑都必须在您的应用中实现。【参考方案2】:
正如 Visput 上面解释的那样,一旦下载完成,就会调用这个方法。application:handleEventsForBackgroundURLSession:completionHandler:
如果您将 NSURLSessionConfiguration 类与 backgroundSessionConfiguraton 一起使用,则会发生这种情况。你可能错过了那部分。
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.BGTransfer"];
sessionConfiguration.HTTPMaximumConnectionsPerHost = 5; // To set the max concurrent connections
详细解释here。
【讨论】:
【参考方案3】:正如@Gautam Jain 所建议的,你必须使用backgroundSessionConfiguration
来实现你的目标。下面我附上了一个例子,希望它对你有所帮助
DownloadModel.h
#import "AppDelegate.h"
@interface DownloadModel : NSObject<NSURLSessionDelegate,NSURLSessionTaskDelegate,NSURLSessionDownloadDelegate>
NSString *resp;
+(instancetype)shared;
-(NSURLSessionDownloadTask *) downloadTaskWithURL:(NSURL*)url ;
@end
下载模型.m
#import "DownloadModel.h"
@interface DownloadModel ()
@property (strong,nonatomic) NSURLSession *downloadSession;
@end
@implementation DownloadModel
+(instancetype)shared
static dispatch_once_t onceToken;
static DownloadModel *downloader=nil;
dispatch_once(&onceToken, ^
downloader=[DownloadModel new];
);
return downloader;
-(id)init
self=[super init];
if(self)
NSURLSessionConfiguration *downloadConfig=[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"DownloadDemo"];
// downloadConfig.timeoutIntervalForRequest = 30;
// downloadConfig.timeoutIntervalForResource = 30;
// downloadConfig.HTTPMaximumConnectionsPerHost = 1;
// downloadConfig.sessionSendsLaunchEvents=YES;
downloadConfig.allowsCellularAccess = YES;
downloadConfig.networkServiceType = NSURLNetworkServiceTypeBackground;
// downloadConfig.discretionary = YES;
self.downloadSession=[NSURLSession sessionWithConfiguration:downloadConfig delegate:self delegateQueue:nil];
self.downloadSession.sessionDescription=@"Video Downloader";
return self;
-(NSURLSessionDownloadTask *) downloadTaskWithURL:(NSURL*)url
return [self.downloadSession downloadTaskWithURL:url];
#pragma mark download delegate
在此方法中使用通知或本地通知
- (void)URLSession:(NSURLSession *)session downloadTask(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL(NSURL *)location
[[NSNotificationCenter defaultCenter] postNotificationName:@"DownloadFinish" object:downloadTask userInfo:nil];
下载进度
- (void)URLSession:(NSURLSession *)session downloadTask(NSURLSessionDownloadTask *)downloadTask didWriteData(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
CGFloat progress=(CGFloat)totalBytesWritten/totalBytesExpectedToWrite;
NSDictionary *userInfo=@@"progress":@(progress);
[[NSNotificationCenter defaultCenter] postNotificationName:@"DownloadProgress" object:downloadTask userInfo:userInfo];
#pragma mark delegate
-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
AppDelegate *appdelegate=[[UIApplication sharedApplication] delegate];
if(appdelegate.backgroundSessionCompletionHandler)
appdelegate.backgroundSessionCompletionHandler();
appdelegate.backgroundSessionCompletionHandler=nil;
@end
AppDelegate.h
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (copy ,nonatomic) void(^backgroundSessionCompletionHandler)();
@end
AppDelegate.m
-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
self.backgroundSessionCompletionHandler=completionHandler;
[DownloadModel shared];
ViewController.m 调用此方法-(NSURLSessionDownloadTask *) downloadTaskWithURL:(NSURL*)url
- (void)viewDidLoad
//Add Notification observers to track download progress and call the above method
[DownloadModel shared] downloadTaskWithURL:url];
别忘了启用后台提取
【讨论】:
后台下载不需要添加“Background Fetch”。以上是关于通过 NSURLSession / NSURLSessionDownloadTask 下载完成时发送本地通知的主要内容,如果未能解决你的问题,请参考以下文章
通过 React Native 使用 must-revalidate 和 NSURLSession
NSURLSession 通过 httpproxy 和 /etc/hosts 时出现 -1001 错误
如何通过 AFNetworking 和 NSUrlSession 调用 SOAP Web 服务