NSURLSessionDataTask實現文件下載(離線斷點續傳下載) <進度值顯示優化>
來自: 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
效果圖(中間有個過程是重新啟動應用程序看看進度條顯示的效果,然后繼續測試開始下載和暫停下載):