iOS网络----简单下载/断点续传/后台下载

作者: simuty | 来源:发表于2016-08-05 11:49 被阅读366次

    目录

    1.断点续传概述;
    2.断点续传原理;
    3.简单下载的实现;
    4.实现断点续传;
    5.实现后台下载
    
    
    第一: 断点续传概述

    断点续传就是从文件上次中断的地方开始重新下载或上传数据,而不是从文件开头。

    第二: 断点续传原理

    要实现断点续传,服务器必须支持。目前最常见的是两种方式:FTP 和 HTTP。下面来简单介绍 HTTP 断点续传的原理:

    断点续传主要依赖于HTTP 头部定义的 Range 来完成。具体 Range 的说明参见 RFC2616中 14.35.2 节。有了 Range,应用可以通过 HTTP 请求获取当前下载资源的的位置进而来恢复下载该资源。Range 的定义如图 1 所示:

    图片一 图片二

    在上面的例子中的“Range: bytes=1208765-”表示请求资源开头 1208765 字节之后的部分。

    图片三

    上面例子中的”Accept-Ranges: bytes”表示服务器端接受请求资源的某一个范围,并允许对指定资源进行字节类型访问。”Content-Range: bytes 1208765-20489997/20489998”说明了返回提供了请求资源所在的原始实体内的位置,还给出了整个资源的长度。这里需要注意的是 HTTP return code 是 206 而不是 200。

    第三.简单下载的实现;

    大家用惯了AFN, 可能对于系统原生的不大熟悉, 为了直观, 先回顾一些系统原生的东西----知其然知其所以然

    以下代码在当前的currentSession中创建一个网络请求任务---可以取消的任务, 并下载一个图片展示出来;

    效果图为

    Paste_Image.png
    #pragma mark 开始下载
    - (IBAction)startDownload:(id)sender {
    
        if (!self.cancelDownloadTask) {
            self.imageView.image = nil;
            NSString *imageURLStr = @"http:https://img.haomeiwen.com/i326255/2834f592d7890aa6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240";
            //创建网络请求
            NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];
            //将当前session中创建下载取消的任务
            self.cancelDownloadTask = [self.currentSession downloadTaskWithRequest:request];
            //保证下载按钮只点击一次
            [self setDownloadButtonsWithEnabled:NO];
            //开始
            [self.cancelDownloadTask resume];
        }
    
    }
    
    

    下载的代理

    创建了网络请求, 之后主要的任务都在于下载的代理中做相应的任务.

    #pragma mark 下载的代理
    
    /**
     *  每次写入沙盒完毕调用
     *  在这里面监听下载进度,totalBytesWritten/totalBytesExpectedToWrite
     *
     *  @param bytesWritten              这次写入的大小
     *  @param totalBytesWritten         已经写入沙盒的大小
     *  @param totalBytesExpectedToWrite 文件总大小
     */
    
    
    /* 执行下载任务时有数据写入 */
    - (void)URLSession:(NSURLSession *)session
          downloadTask:(NSURLSessionDownloadTask *)downloadTask
          didWriteData:(int64_t)bytesWritten
     totalBytesWritten:(int64_t)totalBytesWritten
    totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
    {
        // 计算当前下载进度并更新视图
        float downloadProgress = totalBytesWritten / (float)totalBytesExpectedToWrite;
        NSLog(@"----%@", [NSThread currentThread]);
    
        WeakSelf;
        dispatch_async(dispatch_get_main_queue(), ^{
            /* 根据下载进度更新视图 */
            weakSelf.progressView.progress = downloadProgress;
        });
        
    }
    
    
    /* 从fileOffset位移处恢复下载任务 */
    - (void)URLSession:(NSURLSession *)session
          downloadTask:(NSURLSessionDownloadTask *)downloadTask
     didResumeAtOffset:(int64_t)fileOffset
    expectedTotalBytes:(int64_t)expectedTotalBytes {
        NSLog(@"NSURLSessionDownloadDelegate: Resume download at %lld", fileOffset);
    }
    
    /* 完成下载任务,无论下载成功还是失败都调用该方法 */
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
        
        NSLog(@"=====%@", [NSThread currentThread]);
        //恢复按钮的点击效果
       [self setDownloadButtonsWithEnabled:YES];
    
        if (error) {
            NSLog(@"下载失败:%@", error);
            self.progressView.progress = 0.0;
            self.imageView.image = nil;
        }
    }
    
    

    其中, 我们操作最多的该是下边这个代理

    
    /* 完成下载任务,只有下载成功才调用该方法 */
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
        NSLog(@"NSURLSessionDownloadDelegate: Finish downloading");
        NSLog(@"----%@", [NSThread currentThread]);
    
        // 1.将下载成功后的文件<在tmp目录下>移动到目标路径
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSArray *fileArray = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
        NSURL *destinationPath = [fileArray.firstObject URLByAppendingPathComponent:[location lastPathComponent]];
        NSLog(@"----路径--%@/n---%@", destinationPath.path, location);
        if ([fileManager fileExistsAtPath:[destinationPath path] isDirectory:NULL]) {
            [fileManager removeItemAtURL:destinationPath error:NULL];
        }
        
            //在此有个下载文件后缀的问题
    
        
        //2将下载默认的路径移植到指定的路径
        NSError *error = nil;
        if ([fileManager moveItemAtURL:location toURL:destinationPath error:&error]) {
            self.progressView.progress = 1.0;
            //刷新视图,显示下载后的图片
            UIImage *image = [UIImage imageWithContentsOfFile:[destinationPath path]];
            WeakSelf;
            //回主线程
            dispatch_async(dispatch_get_main_queue(), ^{
                weakSelf.imageView.image = image;
            });
        }
        
        // 3.取消已经完成的下载任务
        if (downloadTask == self.cancelDownloadTask) {
            self.cancelDownloadTask = nil;
        }else if (downloadTask == self.resumableDownloadTask) {
            self.resumableDownloadTask = nil;
            self.resumableData = nil;
        }else if (session == self.backgroundSession) {
            self.backDownloadTask = nil;
            AppDelegate *appDelegate = [AppDelegate sharedDelegate];
            if (appDelegate.backgroundURLSessionCompletionHandler) {
                // 执行回调代码块
                void (^handler)() = appDelegate.backgroundURLSessionCompletionHandler;
                appDelegate.backgroundURLSessionCompletionHandler = nil;
                handler();
            }
        }
    }
    
    

    **请思考一下: 为何要回主线程中更新视图? **


    知道了断点续传的大致原理以及下载的常用方法, 接下来就先实现断点续传

    第四.实现断点续传---下载了较大的文件

    苹果在 iOS7 开始,推出了一个新的类 NSURLSession, 它具备了 NSURLConnection 所具备的方法,并且更强大。2015年NSURLConnection开始被废弃了, 所以直接上NSURLSession的子类NSURLSessionDownloadTask;

    // 当前会话
    @property (strong, nonatomic)  NSURLSession *currentSession;  
    
    ----------------------------------------------
    
    // 可恢复的下载任务
    @property (strong, nonatomic) NSURLSessionDownloadTask *resumableTask;
    // 用于可恢复的下载任务的数据
    @property (strong, nonatomic) NSData *partialData;
    ---------------------------------------------
    
    #pragma mark 暂停/继续下载
    - (IBAction)suspendDownload:(id)sender {
        
        //在此对该按钮做判断
        if (self.judgeSuspend) {
            [self.suspendBtn setTitle:@"继续下载" forState:UIControlStateNormal];
            self.judgeSuspend = NO;
            [self suspendDown];
        }else{
             [self.suspendBtn setTitle:@"暂停下载" forState:UIControlStateNormal];
            self.judgeSuspend = YES;
            [self startResumableDown];
        }
    }
    
    
    

    以上步骤其实像数据持久化的那一层一样, 先判断本地数据, 然后在做是否从网络获取的操作.但前提是, 退出/暂停时必须将下载的数据保存起来以便后续使用.

    以下展示了继续下载和暂停下载的代码:

    //继续下载
    - (void)startResumableDown{
    
        if (!self.resumableDownloadTask) {
            // 如果是之前被暂停的任务,就从已经保存的数据恢复下载
            if (self.resumableData) {
                self.resumableDownloadTask = [self.currentSession downloadTaskWithResumeData:self.resumableData];
            }else {
                // 否则创建下载任务
                NSString *imageURLStr = @"http://dlsw.baidu.com/sw-search-sp/soft/9d/25765/sogou_mac_32c_V3.2.0.1437101586.dmg";
                NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];
                self.resumableDownloadTask = [self.currentSession downloadTaskWithRequest:request];
            }
            //关闭所有的按钮的相应
            [self setDownloadButtonsWithEnabled:NO];
            self.suspendBtn.enabled   = YES;
            self.imageView.image = nil;
            [self.resumableDownloadTask resume];
        }
    
    }
    
    
    //暂停下载
    - (void)suspendDown{
    
        if (self.resumableDownloadTask) {
            [self.resumableDownloadTask cancelByProducingResumeData:^(NSData *resumeData) {
                // 如果是可恢复的下载任务,应该先将数据保存到partialData中,注意在这里不要调用cancel方法
                self.resumableData = resumeData;
                self.resumableDownloadTask = nil;
            }];
        }
    
    }
    
    
    第五. 实现后台下载----下载较大文件

    第一步, 不变的初始化

    #pragma mark 后台下载
    - (IBAction)backDownload:(id)sender {
            NSString *imageURLStr = @"http://dlsw.baidu.com/sw-search-sp/soft/9d/25765/sogou_mac_32c_V3.2.0.1437101586.dmg";
            NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];
            self.backDownloadTask = [self.backgroundSession downloadTaskWithRequest:request];
            [self setDownloadButtonsWithEnabled:NO];
            [self.backDownloadTask resume];
    }
    
    

    第二步: 对于获取数据的地方

    /* 完成下载任务,只有下载成功才调用该方法 */
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
        NSLog(@"NSURLSessionDownloadDelegate: Finish downloading");
        NSLog(@"----%@", [NSThread currentThread]);
    
        // 1.将下载成功后的文件<在tmp目录下>移动到目标路径
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSArray *fileArray = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
        NSURL *destinationPath = [fileArray.firstObject URLByAppendingPathComponent:[location lastPathComponent]];
        
        
        //在此有个下载文件后缀的问题
        
        NSLog(@"----路径--%@/n---%@", destinationPath.path, location);
        if ([fileManager fileExistsAtPath:[destinationPath path] isDirectory:NULL]) {
            [fileManager removeItemAtURL:destinationPath error:NULL];
        }
        
        //2将下载默认的路径移植到指定的路径
        NSError *error = nil;
        if ([fileManager moveItemAtURL:location toURL:destinationPath error:&error]) {
            self.progressView.progress = 1.0;
            //刷新视图,显示下载后的图片
            UIImage *image = [UIImage imageWithContentsOfFile:[destinationPath path]];
            WeakSelf;
            //回主线程
            dispatch_async(dispatch_get_main_queue(), ^{
                weakSelf.imageView.image = image;
            });
        }
        
        [self setDownloadButtonsWithEnabled:YES];
    
        // 3.取消已经完成的下载任务
        if (session == self.backgroundSession) {
            self.backDownloadTask = nil;
            AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
            
            //需要在APPDelegate中做相应的处理
            if (appDelegate.backgroundURLSessionCompletionHandler) {
                // 执行回调代码块
                void (^handler)() = appDelegate.backgroundURLSessionCompletionHandler;
                appDelegate.backgroundURLSessionCompletionHandler = nil;
                handler();
            }
        
    }
    
    
    
    

    因为是后台处理, 因此需要在程序入口做处理

    #import <UIKit/UIKit.h>
    
    @interface AppDelegate : UIResponder <UIApplicationDelegate>
    
    @property (strong, nonatomic) UIWindow *window;
    
    /* 用于保存后台下载任务完成后的回调代码块 */
    @property (copy) void (^backgroundURLSessionCompletionHandler)();
    
    @end
    
    ---
    
    #import "AppDelegate.h"
    
    /* 后台下载任务完成后,程序被唤醒,该方法将被调用 */
    - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
        NSLog(@"Application Delegate: Background download task finished");
        
        // 设置回调的完成代码块
        self.backgroundURLSessionCompletionHandler = completionHandler;
    }
    
    

    Demo地址---iOSDownload


    参考:
    浅析 iOS 应用开发中的断点续传

    更多精彩内容请关注“IT实战联盟”哦~~~


    IT实战联盟.jpg

    相关文章

      网友评论

      • 61a13fde3f8e:请问如果要连续后台,无延迟下载怎么做呢

      本文标题:iOS网络----简单下载/断点续传/后台下载

      本文链接:https://www.haomeiwen.com/subject/ghmhsttx.html