博客> iOS后台下载断点下载
iOS后台下载断点下载
2018-05-21 05:16 评论:0 阅读:332 我耐你
断点下载 iOS后台下载

断断续续做后台现在已经半年了, 虽然还有一些问题没有解决, 但是基本可以满足后台下载以及断点下载的. 只是有一些小问题.

其他下载不多说直接说后台下载,

  • (NSURLSession )session { if (!session) { //这里使用了id加下载次数作为标识, 是因为通过实际运行我发现如果只是用id作为标识,当下在一部影片没有完成的时候删除在下载, 多次这样操作会导致下载任务不执行, 所以如果当删除了正在下载的影片以后 再次重新下载该影片我就会再添加一个下载次数和id 公同作为标识, 这就保证了不会出现执行了下载操作实际没下载的情况 self.taskDescription = [NSString stringWithFormat:@"%@%ld", _movieID, historyModel.historyNumber]; NSString identifier = [NSString stringWithFormat:@"%@_%@", BUNDLEID,self.taskDescription]; NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier]; _session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue: [NSOperationQueue currentQueue]]; } return _session; }

    -(void)startDownload { //从数据库中取出上次的断点信息 //如果存在就断点下载, 不存在就重头下载 NSData resumeData = [DOWNLOADFMDB getResumeWithMovieID:self.movieID]; if (resumeData.length > 0) { NSData newData = [self updateResumeData:resumeData]; self.downloadTask = [self.session downloadTaskWithResumeData:newData]; [DOWNLOADFMDB deleteResumeDataWithID:[_movieID integerValue]];

            }else{
                self.downloadTask = [self.session downloadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.downloadUrl]]];
            }

    }

    //停止操作. 关于停止操作就存在很多问题了 if (self.downloadTask.state == NSURLSessionTaskStateRunning) { //执行了暂停操作之后,下面的注释问题1:不会出现 [_downloadTask suspend]; NSLog(@"处于运行状态:%ld", (long)self.downloadTask.state); }else { NSLog(@"不出运行状态:%ld", (long)self.downloadTask.state); } __weak typeof(self) weakSelf = self; [_downloadTask cancelByProducingResumeData:^(NSData _Nullable resumeData) { dispatch_async(dispatch_get_main_queue(), ^{ if (resumeData) { NSMutableDictionary resumeDataDict = [weakSelf dictionaryWithResumedata:resumeData]; NSLog(@"resumeDataDict=%@", resumeDataDict); if ([resumeDataDict[@"NSURLSessionResumeInfoTempFileName"] isEqualToString:BUNDLEID]) { NSLog(@"出错了"); }

            //非人为操作使app退出进程(如下载一个影片,再次运行模拟器),再次打开app如果立即断网可能会造成以下两种问题
            //以下两种只是可能出现
            //1,如果检测到断网执行暂停操作会出现error=Error Domain=NSURLErrorDomain Code=-999 "cancelled" ,并且临时文件名称会改变为bundleID
            //2,如果检测到断网不作任何处理让系统自动暂停任务,会在再次联网的时候系统自动删除临时文件
            //所以每次接收到app非人为退出, 再次开始任务时候先暂停,防止出现以上两种情况
        }else {
            //问题1:下载一个影片, 切换到后台, 做一些乱七八糟的操作,  在切换到前台, 这个时候执行断网操作,有可能会执行到这里. 初步分析是因为没有网络导致暂停失败所致, 此时task不受控制.
            NSLog(@"出现错误,停止失败");
            [self resetTask];
        }
    });

    }];

上边这些情况到现在都不清楚为什么, 不过可以再刚打开app执行断点下载的时候先停止任务, 然后在断点下载, 这个时候即便是执行断网, 或者切网也不会导致任务失败.

 #pragma mark - NSURLSessionDownloadDelegate methods
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
 //这里需要判断一下, 最早的时候我执行停止操作之前没有暂停, 就会导致 再次开启任务的时候会有可能会是两个任务在执行, 有一个任务是无法控制的, 即便是暂停, 停止, 关闭都没用. 所以需要添加此判断防止出现意外情况. 经过试验如果在停止任务之前先暂停操作这里的判断意义不大
if (downloadTask == self.downloadTask) {

}else {
    //执行这几行也没用
    //        [downloadTask cancel];
    //        downloadTask = nil;
    //        [session invalidateAndCancel];
    //        session = nil;
  }
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
     NSLog(@"%lld   %lld", fileOffset, expectedTotalBytes);
}

  - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask  *)downloadTask didFinishDownloadingToURL:(NSURL *)location
  {
  //缓存完成, 存储即可
  }

   #pragma mark NSURLSessionTaskDelegate
  - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
  {
if (error) {   //当创建session以后如果有未完成的任务会首先执行这里, 这里很奇怪  有时候这个文件名称会变成bundleid  到现在不知为何  ?  哪位高手知道请告知学习一下     
     NSData *resumeData = error ? error.userInfo[NSURLSessionDownloadTaskResumeData]:nil;
            if (resumeData) {
                NSMutableDictionary *resumeDataDict = [self dictionaryWithResumedata:resumeData];
                if ([resumeDataDict[@"NSURLSessionResumeInfoTempFileName"] isEqualToString:BUNDLEID]) {
                    NSLog(@"出错了");
                    [self resetdownloadStatus];

                }else{
                   //存储
                }
            }
    });
 }
}

 #pragma mark - NSURLSessionDelegate
 - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
 {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
//这里我是这么使用的 在appdelegate拿到handler 赋值给一个全局的handler, 当在后台下载完以后会执行到这里, 我在开启一个新的任务, 新任务创建完以后在执行这个handler 这样做是不符合apple要求的, 这样会有延迟 而且是按后台完成次数成倍延迟的, 就是下一次比上一次延迟多一倍. 苹果推荐的方法是在前台的时候创建所有的下载任务, 但是是我这样做了发现当在后台完成一个任务的时候系统会启动另外一个任务, 然后我自己也启动一个任务这样就变成多个任务在执行, 而且系统启动的任务不受控制.暂时我还是使用的后台创建新的task
     if (appDelegate.backgroundURLSessionCompletionHandler != nil) {
         PLAYERMANAGER.completionHandler = appDelegate.backgroundURLSessionCompletionHandler;
         appDelegate.backgroundURLSessionCompletionHandler = nil;
         //        completionHandler();
      }
   }

我这边的下载地址是有时间限制的, 有时候断点下载失败不知道是不是因为不同的cdn导致没有找到 断点信息导致的. 因为有时间限制所以需要对断点的resumedata做处理,

  //更新resumedata
 - (NSData *)updateResumeData:(NSData *)data{
      NSMutableDictionary *dic = [NSPropertyListSerialization propertyListWithData:[self       getCorrectResumeData:data] options:NSPropertyListImmutable format:0 error:0];
      NSMutableDictionary *resumeDataDict = [NSMutableDictionary dictionaryWithDictionary:dic];

      NSMutableURLRequest *newResumeRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.downloadUrl]];
      [newResumeRequest setValue:[NSString stringWithFormat:@"bytes=\%@", resumeDataDict[@"NSURLSessionResumeBytesReceived"]] forHTTPHeaderField:@"Range"];

      NSData *newResumeRequestData = [NSKeyedArchiver archivedDataWithRootObject:newResumeRequest];
      [resumeDataDict setObject:self.downloadUrl forKey:@"NSURLSessionDownloadURL"];
      [resumeDataDict setObject:newResumeRequestData forKey:@"NSURLSessionResumeCurrentRequest"];

      NSData *newData = [NSPropertyListSerialization dataWithPropertyList:resumeDataDict format:NSPropertyListXMLFormat_v1_0 options:0 error:0];

      return newData;
 }

先写这么多, 后续问题会继续更新, 欢迎大家指出不足一起学习, 一起完善后台下载

这是以前对后台下载开的问答帖 http://www.cocoachina.com/bbs/read.php?tid-1696084.html

收藏
1
sina weixin mail 回到顶部