NSURLSessionDataTask实现文件下载(离线断点续传下载) <进度值显示优化>

SharronBeru 9年前

来自: http://www.cnblogs.com/goodboy-heyang/p/5200873.html

前言:根据前篇《 iOS开发之网络编程--2、NSURLSessionDownloadTask文件下载 》或者《 iOS开发之网络编程--3、NSURLSessionDataTask实现文件下载(离线断点续传下载) 》,都遗留了一个细节未处理的问题,那就是在离线断点下载的过程中,当应用程序重新启动之后,进度条的进度值默认没有设置为之前已经下载的进度,根据基本公式"当前进度值 = 已经下载的数据长度 ÷ 最终下载完的数据总长度",已经下载的数据长度可以由沙盒中已经下载的那部分数据获取,但是最终下载完的数据总长度就需要通过网络返回的信息了,但是别忘了,每一次重新启动应用程序初始状态默认都是暂停下载,或者是断网的情况下无法请求网络数据,那么如何获取这个"最终下载完的数据总长度"呢?

问题解决:"最终下载完的数据总长度"可以在首次从0开始下载的时候通过网络获取,然后将其"最终下载完的数据总长度"这个值存储在缓存中的某个文件(这个文件可以是字典等等)中,等待下一次获取。

而我则采用的方法是将这个"最终下载完的数据总长度"作为文件的属性添加进文件属性列表中,以备下一次读取的时候,获得到这个文件之后,就可以读取该文件的属性列表中的"最终下载完

的数据总长度"的属性和属性值。

在这里不得不先介绍一个工具类,读者可以通过本人的博文随笔先了解其功能:

本人花了点时间将网络下载这部分简单的封装成了一个工具类:DownloadTool,下面就展示源码:

DownloadTool.h

 1 #import <Foundation/Foundation.h>   2    3    4 // 定义一个block用来传递进度值   5 typedef  void (^SetProgressValue)(float progressValue);   6    7 @interface DownloadTool : NSObject   8    9 /** 创建下载工具对象 */  10 + (instancetype)DownloadWithURLString:(NSString*)urlString setProgressValue:(SetProgressValue)setProgressValue;  11 /** 开始下载 */  12 -(void)startDownload;  13 /** 暂停下载 */  14 -(void)suspendDownload;  15   16 @end

DownloadTool.m

  1 #import "DownloadTool.h"    2     3 #import "ExpendFileAttributes.h"    4     5 #define Key_FileTotalSize @"Key_FileTotalSize"    6     7 @interface DownloadTool () <NSURLSessionDataDelegate>    8 /** Session会话 */    9 @property (nonatomic,strong)NSURLSession *session;   10 /** Task任务 */   11 @property (nonatomic,strong)NSURLSessionDataTask *task;   12 /** 文件的全路径 */   13 @property (nonatomic,strong)NSString *fileFullPath;   14 /** 传递进度值的block */   15 @property (nonatomic,copy) SetProgressValue setProgressValue;   16 /** 当前已经下载的文件的长度 */   17 @property (nonatomic,assign)NSInteger currentFileSize;   18 /** 输出流 */   19 @property (nonatomic,strong)NSOutputStream *outputStream;   20 /** 不变的文件总长度 */   21 @property (nonatomic,assign)NSInteger fileTotalSize;   22 @end   23    24 @implementation DownloadTool   25    26 + (instancetype)DownloadWithURLString:(NSString*)urlString setProgressValue:(SetProgressValue)setProgressValue{   27     DownloadTool* download = [[DownloadTool alloc] init];   28     download.setProgressValue = setProgressValue;   29     [download getFileSizeWithURLString:urlString];   30     [download creatDownloadSessionTaskWithURLString:urlString];   31     NSLog(@"%@",download.fileFullPath);   32     return download;   33 }   34 // 刚创建该网络下载工具类的时候,就需要查询本地是否有已经下载的文件,并返回该文件已经下载的长度   35 -(void)getFileSizeWithURLString:(NSString*)urlString{   36     // 创建文件管理者   37     NSFileManager* fileManager = [NSFileManager defaultManager];   38     // 获取文件各个部分   39     NSArray* fileComponents = [fileManager componentsToDisplayForPath:urlString];   40     // 获取下载之后的文件名   41     NSString* fileName = [fileComponents lastObject];   42     // 根据文件名拼接沙盒全路径   43     NSString* fileFullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:fileName];   44     self.fileFullPath = fileFullPath;   45        46     NSDictionary* attributes = [fileManager attributesOfItemAtPath:fileFullPath   47                                                              error:nil];   48     // 如果有该文件,且为下载没完成,就直接拿出该文件的长度设置进度值,并设置当前的文件长度   49     NSInteger fileCurrentSize = [attributes[@"NSFileSize"] integerValue];   50     // 如果文件长度为0,就不需要计算进度值了   51     if (fileCurrentSize != 0) {   52         // 获取最终的文件中长度   53         NSInteger fileTotalSize = [[ExpendFileAttributes stringValueWithPath:self.fileFullPath key:Key_FileTotalSize] integerValue];   54         self.currentFileSize = fileCurrentSize;   55         self.fileTotalSize = fileTotalSize;   56         // 设置进度条的值   57         self.setProgressValue(1.0 * fileCurrentSize / fileTotalSize);   58     }   59     NSLog(@"当前文件长度:%lf" , self.currentFileSize * 1.0);   60 }   61 #pragma mark - 创建网络请求会话和任务,并启动任务   62 -(void)creatDownloadSessionTaskWithURLString:(NSString*)urlString{   63     //判断文件是否已经下载完毕   64     if (self.currentFileSize == self.fileTotalSize && self.currentFileSize != 0) {   65         NSLog(@"文件已经下载完毕");   66         return;   67     }   68     NSURLSession* session =   69     [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]   70                                   delegate:self   71                              delegateQueue:[[NSOperationQueue alloc]init]];   72     NSURL* url = [NSURL URLWithString:urlString];   73     NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];   74     //2.3 设置请求头   75     NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentFileSize];   76     [request setValue:range forHTTPHeaderField:@"Range"];   77     NSURLSessionDataTask* task = [session dataTaskWithRequest:request];   78     self.session = session;   79     self.task = task;   80 }   81    82 #pragma mark - 控制下载的状态   83 // 开始下载   84 -(void)startDownload{   85     [self.task resume];   86 }   87 // 暂停下载   88 -(void)suspendDownload{   89     [self.task suspend];   90 }   91 #pragma mark - NSURLSessionDataDelegate 的代理方法   92 // 收到响应调用的代理方法   93 -(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:   94 (NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{   95     NSLog(@"执行了收到响应调用的代理方法");   96     // 创建输出流,并打开流   97     NSOutputStream* outputStream = [[NSOutputStream alloc] initToFileAtPath:self.fileFullPath append:YES];   98     [outputStream open];   99     self.outputStream = outputStream;  100     // 如果当前已经下载的文件长度等于0,那么就需要将总长度信息写入文件中  101     if (self.currentFileSize == 0) {  102         NSInteger totalSize = response.expectedContentLength;  103         NSString* totalSizeString = [NSString stringWithFormat:@"%ld",totalSize];  104         [ExpendFileAttributes extendedStringValueWithPath:self.fileFullPath key:Key_FileTotalSize value:totalSizeString];  105         // 别忘了设置总长度  106         self.fileTotalSize = totalSize;  107     }  108     // 允许收到响应  109     completionHandler(NSURLSessionResponseAllow);  110 }  111 // 收到数据调用的代理方法  112 -(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{  113     NSLog(@"执行了收到数据调用的代理方法");  114     // 通过输出流写入数据  115     [self.outputStream write:data.bytes maxLength:data.length];  116     // 将写入的数据的长度计算加进当前的已经下载的数据长度  117     self.currentFileSize += data.length;  118     // 设置进度值  119     NSLog(@"当前文件长度:%lf,总长度:%lf",self.currentFileSize * 1.0,self.fileTotalSize * 1.0);  120     NSLog(@"进度值: %lf",self.currentFileSize * 1.0 / self.fileTotalSize);  121     // 获取主线程  122     NSOperationQueue* mainQueue = [NSOperationQueue mainQueue];  123     [mainQueue addOperationWithBlock:^{  124         self.setProgressValue(self.currentFileSize * 1.0 / self.fileTotalSize);  125     }];  126 }  127 // 数据下载完成调用的方法  128 -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{  129     // 关闭输出流 并关闭强指针  130     [self.outputStream close];  131     self.outputStream = nil;  132     // 关闭会话  133     [self.session invalidateAndCancel];  134     NSLog(@"%@",[NSThread currentThread]);  135 }  136 -(void)dealloc{  137 }  138 @end

使用示例源码:

 1 #import "ViewController.h"   2 #import "RainbowProgress.h"   3    4 #import "DownloadTool.h"   5    6 #define MP4_URL_String @"http://120.25.226.186:32812/resources/videos/minion_12.mp4"   7    8    9 @interface ViewController ()  10 @property (weak, nonatomic) IBOutlet UILabel *showDownloadState;  11 /** 彩虹进度条 */  12 @property (nonatomic,weak)RainbowProgress *rainbowProgress;  13 /** 网络下载工具对象 */  14 @property (nonatomic,strong)DownloadTool *download;  15 @end  16   17 @implementation ViewController  18   19 - (void)viewDidLoad {  20     [super viewDidLoad];  21     [self setSelfView];  22     [self addProgress];  23     [self addDownload];  24       25 }  26 // 启动和关闭的网络下载开关  27 - (IBAction)SwitchBtn:(UISwitch *)sender {  28     if (sender.isOn) {  29         self.showDownloadState.text = @"开始下载";  30         [self.download startDownload];  31     }else{  32         self.showDownloadState.text = @"暂停下载";  33         [self.download suspendDownload];  34     }  35 }  36 #pragma mark - 设置控制器View  37 -(void)setSelfView{  38     self.view.backgroundColor = [UIColor blackColor];  39 }  40 #pragma mark - 添加彩虹进度条  41 -(void)addProgress{  42     // 创建彩虹进度条,并启动动画  43     RainbowProgress* rainbowProgress = [[RainbowProgress alloc] init];  44     [rainbowProgress startAnimating];  45     [self.view addSubview:rainbowProgress];  46     self.rainbowProgress = rainbowProgress;  47 }  48 #pragma mark - 创建网络下载任务  49 -(void)addDownload{  50     DownloadTool* download = [DownloadTool DownloadWithURLString:MP4_URL_String setProgressValue:^(float progressValue) {  51         self.rainbowProgress.progressValue = progressValue;  52     }];  53     self.download = download;  54 }  55   56 #pragma mark - 设置状态栏样式  57 -(UIStatusBarStyle)preferredStatusBarStyle{  58     return UIStatusBarStyleLightContent;  59 }  60   61 @end

效果图(中间有个过程是重新启动应用程序看看进度条显示的效果,然后继续测试开始下载和暂停下载):