最近项目用到了断点下载,结合着项目,总结一些知识点
一、数据的下载
小到一次普通的get请求,大到一个电影的下载都属于数据的下载。对于数据请求,我们肯定会用到NSURLConnection或者NSURLSession。被普遍使用的AFNetWorking的 AFHTTPRequestOperationManager 是基于 NSURLConnection 的封装,而 AFHTTPSessionManager 则是基于 NSURLSession 的封装
值得一提的而是,NSURLSession 是 iOS7之后苹果推出的新的下载类,明确表示推荐开发者使用。这样一来,NSURLConnection 几乎就没有使用的价值了。
二、断点下载原理
顾名思义,断点下载就是我们能在下载的途中,中断下载任务,再次点击下载的时候,接着上个的数据继续传输,而不是从头开始。
所以,就需要我们在每次下载的时候,在请求头里传入我们已经下载的长度,服务器就会从这个位置接着给我们新的数据。我们需要做的就是接收数据,拼接起来。直到整个文件被下载下来。
三、接收处理数据
如图所示,我们一般都不会只是一个下载任务,所以需要一个字典保存所有的任务。
@property (nonatomic, strong) NSMutableDictionary *tasks;//任务字典【用来保存任务,多个任务同时下载,我们要区分开不同的任务,所以用URL作为key】
准守NSURLSessionDownloadDelegate 协议
//下载 供其他页面调用
- (void)downloadFileWithUrl:(NSURL *)url{
//获取NSURLSession对象 并设置代理
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:url];
[self.tasks setValue:task forKey:url];//保存任务
}
需要注意的是对于url里有汉字的,我们需要做一些处理,具体参考URL中文编码。
这里需要实现的代理方法有
//服务器有返回数据,就会调用该方法
- (void)URLSession:(NSURLSession *)session downloadTask: (NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
/*
这里可以计算下载进度
totalBytesWritten //已经写入本地缓存的大小
totalBytesExpectedToWrite//文件总的大小
*/
}
//任务暂停时调用的方法
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
/*
fileOffset:下载任务中止时的偏移量
*/
}
//下载发生错误时候调用的方法
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
}
//下载完成的时候调用【下载的过程中,系统会把数据暂时写入沙盒的temp文件中。所以我们要在下载完成的时候把数据剪切到我们自己的文件夹中】
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
//我们自定义保存文件的路径 一般使用服务器返回的文件名作为我们自己的文件名
NSString *file = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
NSFileManager *fileManager = [NSFileManager defaultManager];
//将文件剪切到我们自己的路径
[fileManager moveItemAtURL:location toURL:[NSURL fileURLWithPath:file] error:nil];
}
四、暂停和继续
//点击暂停的时候获取到当前的URL
- (void)suspendOrresumeWithUrl:(NSURL *)url{
// 根据url ,取出对应得task,继续或者暂停下载
NSURLSessionDownloadTask *task = self.tasks[url];
if(task.state == NSURLSessionTaskStateRunning){
[task suspend];//暂停
}else if(task.state == NSURLSessionTaskStateSuspended){
[task resume];//下载
}
}
五、更新进度条
项目中我使用的是环形进度条,来展示下载的进度。如图
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, DownloadStatus)
{
Download_Finished = 0,
Download_Continue,
Download_Pause,
};
@interface PXSportLoadProgressView : UIView
@property (nonatomic, strong) UIButton *backgroundButton;//暂停和继续下载的按钮
@property (nonatomic, strong) UILabel *centerLabel;//环形中间的进度百分比
@property (nonatomic, strong) UIImageView *centerImageView;//暂停时候的icon
@property (nonatomic, strong) CAShapeLayer *backgroundLayer;//背景layer
@property (nonatomic, strong) CAShapeLayer *progressLayer;//进度layer
//更新进度条
- (void)configProgressViewWithStatus:(DownloadStatus)status andProgress:(CGFloat)progress;
上面就是一个进度条所需要的东西了。下面展示一些关键代码
self.backgroundLayer = ({
CGPoint fCirclrCenter = CGPointMake(CGRectGetMidY(self.bounds),CGRectGetMidX(self.bounds));
CGFloat radius = CGRectGetMidX(self.bounds) - 10;
CAShapeLayer *backgroundLayer = [CAShapeLayer layer];
backgroundLayer.path = ([UIBezierPath bezierPathWithArcCenter:fCirclrCenter radius:radius startAngle:M_PI endAngle:-M_PI clockwise:NO]).CGPath;
backgroundLayer.strokeColor = [UIColor blackColor].CGColor;
backgroundLayer.fillColor = [UIColor clearColor].CGColor;
backgroundLayer.lineWidth = 2;
backgroundLayer;
});
[self.layer addSublayer:self.backgroundLayer];
self.progressLayer = ({
CAShapeLayer *progressLayer = [CAShapeLayer layer];
progressLayer.strokeColor = [UIColor px_greenColor].CGColor;
progressLayer.fillColor = [UIColor clearColor].CGColor;
progressLayer.lineWidth = 2;
progressLayer;
});
[self.layer addSublayer:self.progressLayer];
上面就是创建了两个layer,我们只需要在接受数据的时候,更新progressLayer即可。
//progress为传过来的进度值。
- (void)updateLoadProgressWithProgress:(CGFloat)progress{
CGPoint fCirclrCenter = CGPointMake(CGRectGetMidY(self.bounds),CGRectGetMidX(self.bounds));
CGFloat radius = CGRectGetMidX(self.bounds) - 10;
self.progressLayer.path = ([UIBezierPath bezierPathWithArcCenter:fCirclrCenter radius:radius startAngle:-M_PI/2 endAngle:(-M_PI/2) + 2*M_PI*progress clockwise:YES]).CGPath;
self.centerLabel.text = F(@"%.0f%%",progress*100);
[self.centerLabel setAdjustsFontSizeToFitWidth:YES];
}
需要注意的是progressLayer的开始位置和方向,别搞混了。 顺时针方向,三点钟的方向才是0。我这边进度是以是十二点钟为起点。所以layer的起始位置是-M_PI/2。
以上大致就是断点下载一些重要的知识点,对于一些细节这里不讨论了,项目中如果有问题,大家可以找我一起讨论,相互学习 。