目录
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
中创建一个网络请求任务---可以取消的任务
, 并下载一个图片展示出来;
效果图为
#pragma mark 开始下载
- (IBAction)startDownload:(id)sender {
if (!self.cancelDownloadTask) {
self.imageView.image = nil;
NSString *imageURLStr = @"http://upload-images.jianshu.io/upload_images/326255-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;
}
更多精彩内容请关注“IT实战联盟”哦~~~