关于断点续传的那些事
最近在研究视频下载的内容,下载的时候需要实现断点续传,上网查了很多资料,大部分都是程序运行时可以实现断点续传,但是程序退出后,再次进入又得从头开始,所以研究了好几天,总结了以下几种能够实现程序重新运行时断点续传的方法,废话不多说,进入正题。
一共三种方法:(1)基于AFNetworking的AFDownloadRequestOperation(这个框架可以在gitHub上下载)。(2)NSURLConnection (3)AFNetworking
对于一个程序员,代码是最好的沟通工具,所以,直接上代码。
第一种方法:AFDownloadRequestOperation这个类已经为我们封装好了断点续传的方法,所以我们不不需要再设置。
首先在h文件中
@interface ViewController : UIViewController<ASIHTTPRequestDelegate,ASIProgressDelegate>
//显示进度的进度条
@property (weak, nonatomic) IBOutlet UIProgressView *myProgress;
//显示已下载文件和文件总大小的比例。
@property (weak, nonatomic) IBOutlet UILabel *sizeLabel;
//显示当前下载情况的label(单位:M)
@property (weak, nonatomic) IBOutlet UILabel *currentLabel;
//显示文件的总大小(单位:M)
@property (weak, nonatomic) IBOutlet UILabel *totalLabel;
@property (nonatomic, strong)ASIHTTPRequest * request;
@property (nonatomic, strong)AFDownloadRequestOperation *operation;
// ********************************************************/
在viewController的m文件中
- (void)viewDidLoad {
[super viewDidLoad];
//创建下载文件的URL
NSURL * url = [NSURL URLWithString:@"http://182.92.5.120/d/file/20150210/lrKXiNVNwfyns/1/mp4/1_1.mp4"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:3600];
//创建文件路径
NSString * path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) lastObject];
NSString * destionPath = [path stringByAppendingPathComponent:@"mp4"];
self.operation = [[AFDownloadRequestOperation alloc] initWithRequest:request targetPath:destionPath shouldResume:YES];
//当下载成功后会执行的方法
[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"Successfully downloaded file to %@", destionPath);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
__block ViewController * viewVC = self;(防止循环引用造成内存泄漏,所以用block修饰一下)
//当在下载的时候,会一直调用这个方法,只要是现在在进行,此方法就一直执行,所以一般显示进度,显示下载文件大小的代码都写在这个方法里面。
[self.operation setProgressiveDownloadProgressBlock:^(AFDownloadRequestOperation *operation, NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpected, long long totalBytesReadForFile, long long totalBytesExpectedToReadForFile) {
float percentDone = totalBytesReadForFile/(float)totalBytesExpectedToReadForFile;
viewVC.myProgress.progress = percentDone;
viewVC.sizeLabel.text = [NSString stringWithFormat:@"%.0f%%",percentDone*100];
viewVC.currentLabel.text= [NSString stringWithFormat:@"CUR : %lli M",totalBytesReadForFile/1024/1024];
viewVC.totalLabel.text = [NSString stringWithFormat:@"TOTAL : %lli M",totalBytesExpectedToReadForFile/1024/1024];
NSLog(@"1");
}];
- (IBAction)Start:(UIButton *)sender {
[self.operation start];
}
AFDownloadRequestOperation 这个类本身封装方法已经写好,我们点用pause就是暂停,暂停后调用resume方法后就是断点续传,不虚设置其他的。
- (IBAction)Pause:(UIButton *)sender {
[self.operation pause];
}
- (IBAction)goon:(UIButton *)sender {
[self.operation resume];
}
代码写到这里,一个简单的断点续传的小demo就写好了。
第二种方法:NSURLConnection
现在大部分开发者都不用NSURLConnection进行数据请求了,大部分都是在用af,大家都觉得NSURLConnection用起来特别麻烦,但是我一直都是在用NSURLConnection,觉得NSURLConnection虽然用法复杂,但是还是有一定优势的。直接上代码
首先在viewController的h文件中
@property (weak, nonatomic) IBOutlet UIProgressView *myProgress;
//下载的url
@property (nonatomic, strong)NSString * url;
//用来监听下载进度
@property (nonatomic, copy)void (^progressHandle)(double progress);
//用来监听下载完成
@property (nonatomic, copy)void (^completionHandler)();
//用来监听下载失败
@property(nonatomic,copy)void(^failureHandler)(NSError *error);
@property (nonatomic, copy)NSString * name;
//已下载文件的大小
@property (nonatomic, assign)long long currentLength;
//文件的总大小
@property (nonatomic, assign)long long sumLength;
//请求对象
@property (nonatomic, strong)NSURLConnection * connection;
//文件句柄
@property (nonatomic, strong)NSFileHandle * writeHandle;
在viewController的m文件中
- (void)start
{
NSURL * url = [NSURL URLWithString:@"http://182.92.5.120/d/file/20150210/lrKXiNVNwfyns/1/mp4/1_1.mp4"];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
//设置请求头信息;
接下来的两句代码就就是为我们断点续传的关键,如果没有这两句是无法实现断点续传的。
NSString *value=[NSString stringWithFormat:@"bytes=%lld-",self.currentLength];
[request setValue:value forHTTPHeaderField:@"Range"];
//发送请求(使用代理的方式)
self.connection=[NSURLConnection connectionWithRequest:request delegate:self];
[self.connection start];
}
NSUTLConnection的代理方法
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
设置存放路径
NSString * str = [@"http://182.92.5.120/d/file/20150210/lrKXiNVNwfyns/1/mp4/1_1.mp4" stringByReplacingOccurrencesOfString:@":" withString:@""];
NSString * newStr = [str stringByReplacingOccurrencesOfString: @"/" withString:@""];
NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [cache stringByAppendingPathComponent:newStr];
NSFileManager * fielManage = [NSFileManager defaultManager];
if (![fielManage fileExistsAtPath:filePath]) {
[fielManage createFileAtPath:filePath contents:nil attributes:nil];
}
//拿到一个关于文件的handler句柄
self.writeHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
//获取完整的文件长度
self.sumLength = response.expectedContentLength;
}
只要是有接收数据此方法就会一直执行。
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
self.currentLength = self.currentLength + data.length;
double progress = (double)self.currentLength/self.sumLength;
self.myProgress.progress = progress;
if (self.progressHandle) {sssssss
self.progressHandle(progress);
}
[self.writeHandle seekToEndOfFile];
[self.writeHandle writeData:data];
}
这样,用NSURLCOnnection封装的下载器就可以用了,这是第二种方法。
第三种方法 AFNetworking
还是直接上代码,在viewController的h文件中
@interface ViewController : UIViewController
用来显示进度的进度条
@property (weak, nonatomic) IBOutlet UIProgressView *myProgress;
请求数据对象
@property (nonatomic, strong)AFHTTPRequestOperation * operation;
@end
在viewController的m文件中。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
此方法用来返回需要需要下载的文件的大小
- (unsigned long long)fileSizeForPath:(NSString *)path {
signed long long fileSize = 0;
NSFileManager *fileManager = [NSFileManager new]; // 用defalut线程不安全
NSData * data = [fileManager contentsAtPath:path];
NSInteger length = [data length];
return length;
}
开始下载
- (void)startDownload {
NSString *downloadUrl = @"http://182.92.5.120/d/file/20150210/lrKXiNVNwfyns/1/mp4/1_1.mp4";
NSString *cacheDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *downloadPath = [cacheDirectory stringByAppendingPathComponent:@"mp3"];
NSLog(@"%@",downloadPath);
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:downloadUrl]];
//检查文件是否已经下载了一部分
unsigned long long downloadedBytes = 0;
if ([[NSFileManager defaultManager] fileExistsAtPath:downloadPath]) {
//获取已下载的文件长度
downloadedBytes = [self fileSizeForPath:downloadPath];
if (downloadedBytes > 0) {
NSMutableURLRequest *mutableURLRequest = [request mutableCopy];
NSString *requestRange = [NSString stringWithFormat:@"bytes=%llu-", downloadedBytes];
[mutableURLRequest setValue:requestRange forHTTPHeaderField:@"Range"];
request = mutableURLRequest;
}
}
//不使用缓存,避免断点续传出现问题
[[NSURLCache sharedURLCache] removeCachedResponseForRequest:request];
//下载请求
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
//下载路径
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:downloadPath append:YES];
//下载进度回调
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
//下载进度
float progress = ((float)totalBytesRead + downloadedBytes) / (totalBytesExpectedToRead + downloadedBytes);
self.myProgress.progress = progress;
NSLog(@"1");
}];
//成功和失败回调
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
[operation start];
self.operation = operation;
}
- (IBAction)Start:(UIButton *)sender {
[self startDownload];
}
- (IBAction)goon:(UIButton *)sender {
[self.operation resume];
}
- (IBAction)Pause:(UIButton *)sender {
[self.operation pause];
}
这样,断点续传就实现了。
这三种方法都是可以实现断点续传的,如果哪里不对,还望大神批评指正。