• iOS开发之网络编程--4、NSURLSessionDataTask实现文件下载(离线断点续传下载) <进度值显示优化>


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

    本篇还涉及到在子线程创建下载任务,然后通过线程通知UI主线程更新进度条控件显示进度。因为delegateQueue这个属性可以设置主队列线程或者是子队列线程。

    先看看效果:

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

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

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

    在这里不得不先介绍一个工具类,读者可以通过本人的另一篇博文随笔先了解其功能:iOS开发 -- 为本地文件添加自定义属性的工具类

    为本地文件添加属性之后,可以打印看的到:

    本人花了点时间将网络下载这部分简单的封装成了一个工具类: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

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

     

    百度云分享源码链接: http://pan.baidu.com/s/1eRwRkZo 密码: 787n

    转载请注明出处:http://www.cnblogs.com/goodboy-heyang/p/5200873.html ,尊重劳动成果。

  • 相关阅读:
    spring boot welcome-file-list
    spring boot web.xml listener
    Linkflow CDP
    连接数据,构建中台,Linkflow如何推动数据化浪潮 CDP
    客户全生命周期服务解决方案的提供商。
    自然语言处理的发展历史 发展方向 行业(法律)
    SpringBoot 设置 profiles 并在其他配置文件里面读取 pom.xml
    SpringBoot 配置文件无法解析占位符
    微服务 SaaS 低代码开发平台
    混沌工程与分布式系统
  • 原文地址:https://www.cnblogs.com/goodboy-heyang/p/5200873.html
Copyright © 2020-2023  润新知