前言

断点续传概述

断点续传就是从文件赏赐中断的地方重新开始下载或者上传数据,而不是从头文件开始。当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会从头下载,这样很浪费时间有木有。所以呢,项目中实现大文件下载的时候,断点续传功能是必不可少了。当然咯,断点续传有一种特殊的情况,就是我们的应用呗用户kill掉或者应用crash,要实现应用重启之后的断点续传,这种情况就是我们将要解决的问题。

断点续传的原理

要实现断点续传,服务器必须是要支持的。目前最常见的两种方式:FTPHTTP

下面来简单介绍HTTP断点续传的原理。

HTTP

通过HTTP,可以非常方便的实现断点续传。断点续传主要依赖于HTTP头部定义的Range,应用可以通过HTTP请求曾经获取失败的资源的某一个返回或者部分来恢复下载该资源。当然并不是所有风服务器都支持Range,所以不支持Range的不在我们考虑之内。Range是以字节计算的,请求的时候不比给我结尾字节数,因为请求方并不一定知道资源的大小。
通过这个关键字可以告诉服务器返回哪些数据给我。
比如:
bytes=500-999 表示第500-第999字节
bytes=500- 表示从第500字节往后的所有字节
然后我们再根据服务器返回的数据,将得到的data数据拼接到文件后面,就可以实现断点续传了。

断点续传分析—AFHTTPRequestOperation

在了解了断点续传的原理之后,我们就可以动手来实现iOS中的断点续传了。由于我现在接触到的项目都是部署在HTTP服务器上的,所以断点续传功能也基于HTTP实现。首先我们来最简单的入手,第三方神奇AFNetworking中提供的实现,下面请看详细代码:

 //1.指定下载文件的地址URLString
//2.获取保存的文件路径filePath
//3.获得NSURLRquest
   NSString* URLString = @"";
   NSString* filePath = @"";    NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:URLString]];
   signed long long downloadBytes = 0;
   ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
       // 3.1 若之前下载过 , 则在 HTTP 请求头部加入 Range
       // 获取已下载文件的 size
       downloadedBytes = [self fileSizeForPath:filePath];        // 验证是否下载过文件
       if (downloadedBytes > 0) {
           // 若下载过 , 断点续传的时候修改 HTTP 头部部分的 Range
           NSMutableURLRequest *mutableURLRequest = [request mutableCopy];
           NSString *requestRange =
           [NSString stringWithFormat:@"bytes=%llu-", downloadedBytes];
           [mutableURLRequest setValue:requestRange forHTTPHeaderField:@"Range"];
           request = mutableURLRequest;
       }
   }    // 4 创建 AFHTTPRequestOperation
   AFHTTPRequestOperation *operation
   = [[AFHTTPRequestOperation alloc] initWithRequest:request];    // 5 设置操作输出流 , 保存在第 2 步的文件中
   operation.outputStream = [NSOutputStream
                             outputStreamToFileAtPath:filePath append:YES];    // 6 设置下载进度处理 block
   [operation setDownloadProgressBlock:^(NSUInteger bytesRead,
                                         long long totalBytesRead, long long totalBytesExpectedToRead) {
       // bytesRead 当前读取的字节数
       // totalBytesRead 读取的总字节数 , 包含断点续传之前的
       // totalBytesExpectedToRead 文件总大小
   }];    // 7 设置 success 和 failure 处理 block
   [operation setCompletionBlockWithSuccess:^(
                                              AFHTTPRequestOperation *operation,
                                              id responseObject) {    }                                failure:^(AFHTTPRequestOperation *operation,
                                              NSError *error) {    }];    // 8 启动 operation
   [operation start];

使用以上代码 , 断点续传功能就实现了,应用重新启动或者出现异常情况下 , 都可以基于已经下载的部分开始继续下载。关键的地方就是把已经下载的数据持久化。接下来简单看下AFHTTPRequestOperation是怎么实现的。通过查看源码 , 我们发现 AFHTTPRequestOperation 继承自 AFURLConnectionOperation,而AFURLConnectionOperation 实现了 NSURLConnectionDataDelegate 协议。

处理流程如图所示:

可以看到,这里的AFNetworking采取自线程调一步接口的方式,是因为直接在主线程调用异步接口会有一个Runloop的问题。当主线程调用[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]时,请求发出之后的监听任务会加到主线程中的Runloop中,我们知道RunloopMode默认为NSDefuaultRunloopMode,这个表示只有当前线程的Runloop处理NSDefaultRunloopMode时,这个任务才会执行。而当用户正在滚动tableview和scrollview的时候,主线程的Runloop处于NSEventTrackingRunloop模式下,就不会执行NSDefaultRunloopMode的任务。

另外由于采取子线程调用接口的方式,所以这边的DownloadProgressBlock,success 和 failure Block 都需要回到主线程来处理。

断点续传实战

NSURLConnecttion的实现

NSURLConnecttion这家伙已经在2015年就已经被苹果遗弃,所以在这里我们不做过多讨论,请注意啊,我是省略号……

NSURLSessionDataTask

苹果在iOS7开始,推出一个新的类NSURLSession,它具备了NSURLConnection所具备的方法,并且更加强大。所以我更加推荐大家使用这个类去实现下载和续传。NSURLConnection 和 NSURLSession delegate 方法的映射关系 , 如下图所示。所以关键是要满足 NSURLSessionDataDelegate 和 NSURLsessionTaskDelegate。

文件下载和暂停的实现

当使用NSURLSessionDownloadTask进行下载的时候,系统会在cache文件夹下创建一个下载的路径,路径下会有一个以”CFNetworking”打头的.tmp文件(以下简称”下载文件”防止混淆),这个就是我们正在下载中的文件。而当我们调用了cancelByProducingResumeData:方法后,会得到一个data文件,通过String格式化后,发现是一个XML文件,里面包含了关于.tmp文件的一些关键点的描述,包括”Range”,”key”,”下载文件的路径”等等.而原本存在于download文件下的下载文件,则被移动到了系统tmp文件夹目录下.而当我们再次进行resume操作的时候,下载文件则又被移回到了download文件夹下。

程序被杀掉的断点续传resumeData

根据上面的分析,基本可以得到以下结论:

  • DownloadTask每次进行断点续传的时候,会根据data文件中的”路径Key”去寻找下载文件,然后校验后再根据”Range”属性去进行断点续传。

  • download文件夹中存放的只会是下载中的文件,一旦暂停就会被移动到tmp文件夹下。

  • 每个暂停得到的data文件,与下载文件一一对应。

  • 断点续传只与tmp文件夹中的文件有关。

所以我们可以这么做,设置一个Bool变量用来判断是否正在下载中,同时用一个周期事件每隔一段时间暂停一次。然后保存data文件和拷贝tmp文件夹下的下载文件到安全目录下(因为tmp文件夹据说随时可能清空)。
当再次下载的时候,先是从安全目录下取到下载文件,删除tmp文件夹中原有的同名文件,然后copy到tmp目录下,最后利用保存的data文件进行再次downloadTaskWithResumeData操作,就可以实现再次下载了。

原文链接:总结iOS开发中的断点续传那些事儿

最新文章

  1. Oracle数据库的SQL分页模板
  2. JavaScript的面临的9个陷阱
  3. 免费电子书:C#代码整洁之道
  4. 404 Not Found错误页面的解决方法和注意事项
  5. python 任意新闻正文提取
  6. 在qq中可以使用添加标签功能
  7. web.xml文件中加载顺序的优先级
  8. pythn BeautifulSoup
  9. 说点手动导jar包的细节Referenced Libraries
  10. vbox要手动mount才能挂载windows的共享文件夹(好用,不用安装samba了)
  11. 为WebClient增加Cookie的支持
  12. 阿里云邮件服务器怎么设置才能在QQ邮箱访问,互发邮件?
  13. VS,连接到oracle 报要升级到8.多少版本的错
  14. qt界面操作
  15. Perl模块管理
  16. 基于MMSE的预测
  17. Codeforces Round #309 (Div. 1) A(组合数学)
  18. SpringBoot中常用注解@Controller/@RestController/@RequestMapping的区别
  19. MATLAB 人脸定位
  20. UVa 10491 奶牛和轿车(全概率公式)

热门文章

  1. StringMVC 中如何做数据校验
  2. C语言 · 阶乘计算 · 基础练习
  3. 有朋友问了数据库ID不连续,怎么获取上一篇和下一篇的文章?(不是所有情况都适用)
  4. Matlab 高脚杯模型切片
  5. System.FormatException: GUID 应包含带 4 个短划线的 32 位数(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)。
  6. 前端性能优化的另一种方式——HTTP2.0
  7. myeclipse 内存不够用报错PermGen space 和 An internal error has occurred.
  8. springmvc的拦截器
  9. java springMVC SSM 操作日志 4级别联动 文件管理 头像编辑 shiro redis
  10. CocoaPods的安装、使用、以及遇到的问题