sessionDownLoad 断点下载,仿爱奇艺视频下载,支持后台任务,支持多任务

是千辛万苦总结出来的关于使用session来做完美的断点下载,希望能帮到各位猿友####

此篇断点下载的特色

1.支持断点下载,这个必须的
2.支持多任务,任务数可以根据实际情况自行设置
3.支持后台下载(session本身就支持,是不是觉得session很强大,加入session的队列吧)
4.支持app意外退出保存断点信息
5.每隔一秒更新一次进度,降低进度条的更新频率

使用到的第三方工具

AFNetworking
FMDB

好了废话也不多说,直接上代码,希望能帮到各位同行,demo我一托管在github,有兴趣的朋友可以看一下,里面会有一些我写这个demo时注意的一些东西,还有一些总结

demo演示

demo1.gif

初始化下载单例
//注册通知处理异常情况
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionDownloadAplicationWillTerminate) name:UIApplicationWillTerminateNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];

    _lock = [[NSLock alloc] init];
    _lock.name = @"ZBHTTPSessionShareTaskDict";
    
    _taskDict = [NSMutableDictionary dictionary];
    //后台任务处理
    _completionHandlerDictionary = [NSMutableDictionary dictionary];
    _maxCount = 3;
    _downloadingList = [NSMutableArray array];
    _diskFileList = [NSMutableArray array];
    //更新文件状态,防止重新运行程序时,上次未下载完成的任务可能会开始下载
    [FileModelDbManager updateUnFinishedFileState];
    [_diskFileList addObjectsFromArray:[FileModelDbManager getAllDownloadedFile]];
    [_downloadingList addObjectsFromArray:[FileModelDbManager getAllNotCompletedFile]];
    
    [ZB_NetWorkShare ZB_NetWorkShare].backSessionCompletionDelegate = self;

下载关键代码

for (FileModel *file in self.downloadingList) {
    if (self.taskDict.count < self.maxCount) {
        if (file.fileState == FileWillDownload) {
            file.fileState = FileDownloading;
        }
    } else if(self.taskDict.count > self.maxCount){
        if (file.fileState == FileDownloading) {
            file.fileState = FileStopDownload;
        }
    } else {
        
    }
    if (file.fileState == FileDownloading) {
        NSURLSessionTask *task = [self taskForKey:file.fileUrl];
        if (!task) {
            [self AF_BeginDownloadFileWithFileModel:file];
        }
    } else {
        //此处未异步回掉self.downloadingcout--,不能及时生效,所以更换另外一种方式
        NSURLSessionDownloadTask *task = [self taskForKey:file.fileUrl];
            if (task) {
                [self removeTaskForKey:file.fileUrl];
                if (file) {
                    file.fileState = FileStopDownload;
                    [FileModelDbManager insertFile:file];
                }
              [task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
                 
              }];
        }
    }
}

app意外退出断点支持,为了完成这个功能,楼主也是费了9牛二虎之力,其中使用到了信号量,以及重新创建resumedata,重新创建resumedata必须使用xml的参数,否则会造成生成的resumedata文件不正确

NSDictionary *dict = [NSClassFromString(@"__NSCFLocalDownloadTask") getPropertiesDict];
for (NSString *obj in dict.allKeys) {
    if ([@"downloadFile" isEqualToString:obj]) {
        id propertyValue = [task valueForKeyPath:obj];
        NSDictionary *downDict = [[propertyValue class] getPropertiesDict];
        for (NSString *downProperty in downDict.allKeys) {
            if ([@"path" isEqualToString:downProperty]) {
                NSString *temFilePath = [propertyValue valueForKeyPath:downProperty];
                
                NSData *resumeData = [self reCreateResumeDataWithTask:task tmFilePath:temFilePath];
                [self handleResumeData:resumeData file:nil];
                break;
            }
        }
        break;
    }
    
}

程序退出后台时执行的代码,由于程序退出的时候,仅有短暂的几秒时间app执行任务,而且不会执行异步任务,所以这里用到了信号来取消后台下载任务

dispatch_semaphore_t sem = dispatch_semaphore_create(1);
for (NSURLSessionDownloadTask *task in _taskDict.allValues) {
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    
    [task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        
        dispatch_semaphore_signal(sem);
    }];
    [self saveResumeDataWithTask:task];
}
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

还剩下关键的一步就可以支持app被kill时的断点下载了,那就是重新生成resumedata

NSMutableDictionary *resumeDataDict = [NSMutableDictionary dictionary];

NSMutableURLRequest *newResumeRequest = [task.currentRequest mutableCopy];
NSData *tmData = [NSData dataWithContentsOfFile:tmFilePath];

[newResumeRequest addValue:[NSString stringWithFormat:@"bytes=%@-",@(tmData.length)] forHTTPHeaderField:@"Range"];
[resumeDataDict setObject:newResumeRequest.URL.absoluteString forKey:@"NSURLSessionDownloadURL"];
NSData *newResumeRequestData = [NSKeyedArchiver archivedDataWithRootObject:newResumeRequest];
NSData *oriData = [NSKeyedArchiver archivedDataWithRootObject:task.originalRequest];
[resumeDataDict setObject:@(tmData.length) forKey:@"NSURLSessionResumeBytesReceived"];
[resumeDataDict setObject:newResumeRequestData forKey:@"NSURLSessionResumeCurrentRequest"];
[resumeDataDict setObject:@(2) forKey:@"NSURLSessionResumeInfoVersion"];
[resumeDataDict setObject:oriData forKey:@"NSURLSessionResumeOriginalRequest"];
[resumeDataDict setObject:[tmFilePath lastPathComponent] forKey:@"NSURLSessionResumeInfoTempFileName"];
return [NSPropertyListSerialization dataWithPropertyList:resumeDataDict format:NSPropertyListXMLFormat_v1_0 options:0 error:nil];

相关链接

关于iOS的后台下载和断点续传,说一说自己的理解
ios使用nsurlsession进行下载
iOS中利用NSURLSession进行文件断点下载
NSURLSessionDownloadTask的深度断点续传
苹果官方论坛

源码我已上传至github很方便朋友们下载demo

如果您有好的建议欢迎留言,或者issue我,谢谢!

你可能感兴趣的:(sessionDownLoad 断点下载,仿爱奇艺视频下载,支持后台任务,支持多任务)