• 总结iOS开发中的断点续传那些事儿


    前言

    断点续传概述

    断点续传就是从文件赏赐中断的地方重新开始下载或者上传数据,而不是从头文件开始。当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会从头下载,这样很浪费时间有木有。所以呢,项目中实现大文件下载的时候,断点续传功能是必不可少了。当然咯,断点续传有一种特殊的情况,就是我们的应用呗用户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开发中的断点续传那些事儿

  • 相关阅读:
    java实现立方和等式
    java实现立方和等式
    java实现立方和等式
    java实现立方和等式
    java实现立方和等式
    java实现拉丁方块填数字
    java实现拉丁方块填数字
    java实现拉丁方块填数字
    java实现拉丁方块填数字
    IDHTTP用法详解 good
  • 原文地址:https://www.cnblogs.com/tzh521241/p/6213248.html
Copyright © 2020-2023  润新知