iOS应用后台进行数据更新和下载

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];
     //通过查看UIApplication的applicationState
     NSLog(@"Launched in background %d", UIApplicationStateBackground == application.applicationState);
     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;
          }
          [UIApplication sharedApplication].applicationIconBadgeNumber +=1;  
          // 解析响应/数据以决定新内容是否可用
          BOOL hasNewData = ...
          if (hasNewData) {
               completionHandler(UIBackgroundFetchResultNewData);
          } else {
               completionHandler(UIBackgroundFetchResultNoData);
          }
     }];

     // 开始任务
     [task resume];
}

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);
}

使用[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的委托方法,这些委托方法全部是必须实现的。了解所有类型session task的生命周期可以参考官方文档:https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/URLLoadingSystem/NSURLSessionConcepts/NSURLSessionConcepts.html#//apple_ref/doc/uid/10000165i-CH2-SW42

实现委托

#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.
", 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.
");
     }

     [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();
     }
}

Background Task

通常情况下,应用在进入background之后,很快就会转到suspended状态。但是,如果应用有需要的话(比如我们这个需求),可以向系统申请一点额外的时间来完成当前的任务

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    __block UIBackgroundTaskIdentifier bgTask;// 后台任务标识
     
    // 结束后台任务
    void (^endBackgroundTask)() = ^(){
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    };
     
    bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
        endBackgroundTask();
    }];
     
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
         
        double start_time = application.backgroundTimeRemaining;// 记录后台任务开始时间
         
        BOOL networkAvailable = [YLSGlobalUtils isNetworkAvailable];
        if(!networkAvailable){
            NSLog(@"网络不可用,取消自动备份");
            endBackgroundTask();
            return;
        }
         
        BOOL need = [backupService checkNeedBackup];
        if(!need){
            NSLog(@"无需备份");
            endBackgroundTask();
            return;
        }
         
        [backupService doBackupProcessHandler:^(float done, float total){
            // nothing to do with progress
        } CompletionHandler:^(NSError* error, NSArray* statistics){
            double done_time = application.backgroundTimeRemaining;
            double spent_time = start_time - done_time;
            NSLog(@"后台备份完成,耗时: %f秒", spent_time);
            endBackgroundTask();
        }];
    });
}

核心是这个方法:

- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void(^)(void))handler

注册一个后台任务,这个任务最多只有10分钟时间,如果超时,则会调用参数中的block,在此block中,必须调用这个方法:


- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)identifier



否则,应用会crash。只要调用了beginBackgroundTaskWithExpirationHandler:方法,就必须在handler里相应地调用endBackgroundTask:方法

 

实际的逻辑,在下面的block里完成,这里没什么特别的,只是如果提前结束了任务,也调用一次endBackgroundTask:方法,这样就不会超时,前面的expirationHandler就不会被执行

另外,通过UIApplication的backgroundTimeRemaining属性,可以获取此后台任务还剩余的时间(当此值变成0,expirationHandler就被执行)

这段代码,是写在ApplicationDelegate的生命周期方法里:

1

- (void)applicationDidEnterBackground:(UIApplication *)application


这主要是因为,我们就是希望仅当应用被切换到后台时才开始自动备份。但是后台任务并不是只能在这种情况下启动,如果应用中有一些关键性的任务,希望即使被切换到后台也要先完成再suspend,就可以随时调用

1

- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void(^)(void))handler


以确保此任务完成,写法只要参考上面的代码就行了

转载于:https://my.oschina.net/ososchina/blog/689192

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值