iOS中有三种方式使得程序在关闭或崩溃的情况下也能够在后台持续进行一些任务,比如手更新程序界面快照,下载文件等。这三个方法分别是Background Fetch,Remote Notification和NSURLSession的backgroundSessionConfiguration。
Background Fetch
开启
首先在info plist文件中开启UIBackgroundModes的Background fetch。或者手动编辑这个值
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
</array>
iOS默认不进行Background Fetch,需要设置一个时间间隔
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//UIApplicationBackgroundFetchIntervalMinimum表示尽可能频繁去获取,如果需要指定至少多少时间更新一次就需要给定一个时间值
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
return YES;
}
最后在App Delegate里实现下面的方法,这个方法只能在30秒内完成。
- (void) application:(UIApplication *)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSURL *url = [[NSURL alloc] initWithString:@"http://yourserver.com/data.json"];
NSURLSessionDataTask *task = [session dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
completionHandler(UIBackgroundFetchResultFailed);
return;
}
// 解析响应/数据以决定新内容是否可用
BOOL hasNewData = ...
if (hasNewData) {
completionHandler(UIBackgroundFetchResultNewData);
} else {
completionHandler(UIBackgroundFetchResultNoData);
}
}];
// 开始任务
[task resume];
}
测试
- 通过查看UIApplication的applicationState
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSLog(@"Launched in background %d", UIApplicationStateBackground == application.applicationState);
return YES;
}
Remote Notification
在普通的远程通知中带上content-available标志就可以在通知用户同时在后台进行更新,通知结构如下:
{
"aps" : {
"content-available" : 1
},
"content-id" : 42
}
接收一条带有content-available的通知会调用下面的方法
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
NSLog(@"Remote Notification userInfo is %@", userInfo);
NSNumber *contentID = userInfo[@"content-id"];
// 根据 content ID 进行操作
completionHandler(UIBackgroundFetchResultNewData);
}
利用NSURLSession进行background transfer task
使用[NSURLSessionConfiguration backgroundSessionConfiguration]创建一个后台任务,当应用退出后,崩溃或进程被关掉都还是会运行。
范例,先处理一条远程通知,并将NSURLSessionDownloadTask添加到后台传输服务队列。
- (NSURLSession *)backgroundURLSession
{
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *identifier = @"io.objc.backgroundTransferExample";
NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration backgroundSessionConfiguration:identifier];
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
});
return session;
}
- (void) application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
NSLog(@"Received remote notification with userInfo %@", userInfo);
NSNumber *contentID = userInfo[@"content-id"];
NSString *downloadURLString = [NSString stringWithFormat:@"http://yourserver.com/downloads/%d.mp3", [contentID intValue]];
NSURL* downloadURL = [NSURL URLWithString:downloadURLString];
NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
NSURLSessionDownloadTask *task = [[self backgroundURLSession] downloadTaskWithRequest:request];
task.taskDescription = [NSString stringWithFormat:@"Podcast Episode %d", [contentID intValue]];
//执行resume保证开始了任务
[task resume];
completionHandler(UIBackgroundFetchResultNewData);
}
下载完成后调用NSURLSessionDownloadDelegate的委托方法,这些委托方法全部是必须实现的。
#Pragma Mark - NSURLSessionDownloadDelegate
- (void) URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
NSLog(@"downloadTask:%@ didFinishDownloadingToURL:%@", downloadTask.taskDescription, location);
// 必须用 NSFileManager 将文件复制到应用的存储中,因为临时文件在方法返回后会被删除
// ...
// 通知 UI 刷新
}
- (void) URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
}
- (void) URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
}
后台的任务完成后如果应用没有在前台运行,需要实现UIApplication的两个delegate让系统唤醒应用
- (void) application:(UIApplication *)application
handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
// 你必须重新建立一个后台 seesiong 的参照
// 否则 NSURLSessionDownloadDelegate 和 NSURLSessionDelegate 方法会因为
// 没有 对 session 的 delegate 设定而不会被调用。参见上面的 backgroundURLSession
NSURLSession *backgroundSession = [self backgroundURLSession];
NSLog(@"Rejoining session with identifier %@ %@", identifier, backgroundSession);
// 保存 completion handler 以在处理 session 事件后更新 UI
[self addCompletionHandler:completionHandler forSession:identifier];
}
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
NSLog(@"Background URL session %@ finished events.\n", session);
if (session.configuration.identifier) {
// 调用在 -application:handleEventsForBackgroundURLSession: 中保存的 handler
[self callCompletionHandlerForSession:session.configuration.identifier];
}
}
- (void)addCompletionHandler:(CompletionHandlerType)handler forSession:(NSString *)identifier
{
if ([self.completionHandlerDictionary objectForKey:identifier]) {
NSLog(@"Error: Got multiple handlers for a single session identifier. This should not happen.\n");
}
[self.completionHandlerDictionary setObject:handler forKey:identifier];
}
- (void)callCompletionHandlerForSession: (NSString *)identifier
{
CompletionHandlerType handler = [self.completionHandlerDictionary objectForKey: identifier];
if (handler) {
[self.completionHandlerDictionary removeObjectForKey: identifier];
NSLog(@"Calling completion handler for session %@", identifier);
handler();
}
}
网友评论