123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- //
- // RQDownloadManager.m
- // TEST
- //
- // Created by 张嵘 on 2018/10/22.
- // Copyright © 2018 张嵘. All rights reserved.
- //
- #import "RQDownloadManager.h"
- #import "AppDelegate.h"
- @interface RQDownloadManager () <NSURLSessionDelegate, NSURLSessionDownloadDelegate>
- @property (nonatomic, strong) NSURLSession *session; // NSURLSession
- @property (nonatomic, strong) NSMutableDictionary *dataTaskDic; // 同时下载多个文件,需要创建多个NSURLSessionDownloadTask,用该字典来存储
- @property (nonatomic, assign) NSInteger currentCount; // 当前正在下载的个数
- @property (nonatomic, assign) NSInteger maxConcurrentCount; // 最大同时下载数量
- @property (nonatomic, assign) BOOL allowsCellularAccess; // 是否允许蜂窝网络下载
- @end
- @implementation RQDownloadManager
- #pragma mark - 单例相关
- static id instace = nil;
- + (id)allocWithZone:(struct _NSZone *)zone {
- if (instace == nil) {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- instace = [super allocWithZone:zone];
- // 初始化
- [instace initBase];
- // 添加未捕获异常的监听
- [instace handleUncaughtExreption];
- // 添加监听
- [instace addObservers];
- // 开启网络监听
- [[RQNetworkReachabilityManager shareManager] monitorNetworkStatus];
- });
- }
- return instace;
- }
- - (instancetype)init {
- return instace;
- }
- + (instancetype)sharedManager {
- return [[self alloc] init];
- }
- - (id)copyWithZone:(struct _NSZone *)zone {
- return instace;
- }
- - (id)mutableCopyWithZone:(struct _NSZone *)zone {
- return instace;
- }
- #pragma mark - 单例初始化调用
- - (void)initBase {
- // 初始化
- _currentCount = 0;
- _maxConcurrentCount = [[NSUserDefaults standardUserDefaults] integerForKey:RQDownloadMaxConcurrentCountKey];
- _allowsCellularAccess = [[NSUserDefaults standardUserDefaults] boolForKey:RQDownloadAllowsCellularAccessKey];
- _dataTaskDic = [NSMutableDictionary dictionary];
-
- // 单线程代理队列
- NSOperationQueue *queue = [[NSOperationQueue alloc] init];
- queue.maxConcurrentOperationCount = 1;
-
- // 后台下载标识
- NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"RQDownloadBackgroundSessionIdentifier"];
- // 允许蜂窝网络下载,默认为YES,这里开启,我们添加了一个变量去控制用户切换选择
- configuration.allowsCellularAccess = YES;
-
- // 创建NSURLSession,配置信息、代理、代理线程
- _session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:queue];
- }
- /**
- * 添加监听
- */
- - (void)addObservers {
- [[NSNotificationCenter defaultCenter] addObserver:instace selector:@selector(openDownloadTask) name:UIApplicationDidFinishLaunchingNotification object:nil];
-
- [[NSNotificationCenter defaultCenter] addObserver:instace selector:@selector(applicationWillTerminate) name:UIApplicationWillTerminateNotification object:nil];
-
- [[NSNotificationCenter defaultCenter] addObserver:instace selector:@selector(applicationWillTerminate) name:kNotificationUncaughtException object:nil];
-
- // 最大下载并发数变更通知
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadMaxConcurrentCountChange:) name:RQDownloadMaxConcurrentCountChangeNotification object:nil];
-
- // 是否允许蜂窝网络下载改变通知
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadAllowsCellularAccessChange:) name:RQDownloadAllowsCellularAccessChangeNotification object:nil];
-
- // 网路改变通知
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkingReachabilityDidChange:) name:RQNetworkingReachabilityDidChangeNotification object:nil];
- }
- /**
- * 添加未捕获异常的监听
- */
- - (void)handleUncaughtExreption {
- [RQUncaughtExceptionHandler setDefaultHandler];
- }
- #pragma mark - Private Method
- // 加入准备下载任务
- - (void)startDownloadTask:(RQDownloadModel *)model {
- // 取出数据库中模型数据,如果不存在,添加到数据空中
- RQDownloadModel *downloadModel = [[RQDataBaseManager shareManager] getModelWithUrl:model.urlString];
- if (!downloadModel) {
- downloadModel = model;
- [[RQDataBaseManager shareManager] insertModel:downloadModel];
- }
-
- // 更新状态为等待下载
- downloadModel.status = RQDownloadStatus_Waiting;
- [[RQDataBaseManager shareManager] updateWithModel:downloadModel option:RQDBUpdateOptionAllParam];
-
- // 下载
- if (_currentCount < self.maxConcurrentCount && [self networkingAllowsDownloadTask]) [self downloadwithModel:downloadModel];
- }
- // 开始下载
- - (void)downloadwithModel:(RQDownloadModel *)model {
- // 更新状态为开始
- model.status = RQDownloadStatus_Running;
- [[RQDataBaseManager shareManager] updateWithModel:model option:RQDBUpdateOptionAllParam];
- _currentCount++;
-
- // 创建NSURLSessionDownloadTask
- NSURLSessionDownloadTask *downloadTask;
- if (model.resumeData) {
- CGFloat version = [[[UIDevice currentDevice] systemVersion] floatValue];
- if (version >= 10.0 && version < 10.2) {
- downloadTask = [_session downloadTaskWithCorrectResumeData:model.resumeData];
- }else {
- downloadTask = [_session downloadTaskWithResumeData:model.resumeData];
- }
-
- }else {
- downloadTask = [_session downloadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:model.urlString]]];
- }
-
- // 添加描述标签
- downloadTask.taskDescription = model.urlString;
-
- // 更新存储的NSURLSessionDownloadTask对象
- [_dataTaskDic setValue:downloadTask forKey:model.urlString];
-
- // 启动(继续下载)
- [downloadTask resume];
- }
- // 暂停下载
- - (void)pauseDownloadTask:(RQDownloadModel *)model {
- // 取最新数据
- RQDownloadModel *downloadModel = [[RQDataBaseManager shareManager] getModelWithUrl:model.urlString];
-
- // 取消任务
- [self cancelTaskWithModel:downloadModel delete:NO];
-
- // 更新数据库状态为暂停
- downloadModel.status = RQDownloadStatus_Suspended;
- [[RQDataBaseManager shareManager] updateWithModel:downloadModel option:RQDBUpdateOptionAllParam];
- }
- // 删除下载任务及本地缓存
- - (void)deleteTaskAndCache:(RQDownloadModel *)model {
- // 删除本地缓存、数据库数据
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- [[NSFileManager defaultManager] removeItemAtPath:model.destinationPath error:nil];
- model.status = RQDownloadStatus_None;
- model.statusText = nil;
- [[RQDataBaseManager shareManager] updateWithModel:model option:RQDBUpdateOptionAllParam];
- [[RQDataBaseManager shareManager] deleteModelWithUrl:model.urlString];
- [self clearTmpDirectory];
- });
-
- // 如果正在下载,取消任务
- [self cancelTaskWithModel:model delete:YES];
- }
- // 清除沙盒临时文件
- - (void)clearTmpDirectory
- {
- NSArray* tmpDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:NSTemporaryDirectory() error:NULL];
- for (NSString *file in tmpDirectory) {
- [[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), file] error:NULL];
- }
- }
- // 取消任务
- - (void)cancelTaskWithModel:(RQDownloadModel *)model delete:(BOOL)delete {
- if (model.status == RQDownloadStatus_Running) {
- // 获取NSURLSessionDownloadTask
- NSURLSessionDownloadTask *downloadTask = [_dataTaskDic valueForKey:model.urlString];
-
- // 取消任务
- [downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
- // 更新下载数据
- model.resumeData = delete? nil : resumeData;
- [[RQDataBaseManager shareManager] updateWithModel:model option:RQDBUpdateOptionResumeData];
-
- // 更新当前正在下载的个数
- if (_currentCount > 0) _currentCount--;
-
- // 开启等待下载任务
- [self startDownloadWaitingTask];
- }];
-
- // 移除字典存储的对象
- if (delete) {
- [_dataTaskDic removeObjectForKey:model.urlString];
- }
- }
- }
- // 开启等待下载任务
- - (void)startDownloadWaitingTask {
- if (_currentCount < self.maxConcurrentCount && [self networkingAllowsDownloadTask]) {
- // 获取下一条等待的数据
- RQDownloadModel *model = [[RQDataBaseManager shareManager] getWaitingModel];
-
- if (model) {
- // 下载
- [self downloadwithModel:model];
-
- // 递归,开启下一个等待任务
- [self startDownloadWaitingTask];
- }
- }
- }
- // 下载时,杀死进程,更新所有正在下载的任务为等待
- - (void)updateDownloadingTaskState {
- NSArray *downloadingData = [[RQDataBaseManager shareManager] getAllDownloadingData];
- for (RQDownloadModel *model in downloadingData) {
- model.status = RQDownloadStatus_Waiting;
- [[RQDataBaseManager shareManager] updateWithModel:model option:RQDBUpdateOptionState];
- }
- }
- // 重启时开启等待下载的任务
- - (void)openDownloadTask {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- [self startDownloadWaitingTask];
- });
- }
- // 停止正在下载任务为等待状态
- - (void)pauseDownloadingTaskWithAll:(BOOL)all {
- // 获取正在下载的数据
- NSArray *downloadingData = [[RQDataBaseManager shareManager] getAllDownloadingData];
- NSInteger count = all ? downloadingData.count : downloadingData.count - self.maxConcurrentCount;
- for (NSInteger i = 0; i < count; i++) {
- // 取消任务
- RQDownloadModel *model = downloadingData[i];
- [self cancelTaskWithModel:model delete:NO];
-
- // 更新状态为等待
- model.status = RQDownloadStatus_Waiting;
- [[RQDataBaseManager shareManager] updateWithModel:model option:RQDBUpdateOptionAllParam];
- }
- }
- - (void)applicationWillTerminate {
- [self updateDownloadingTaskState];
- }
- #pragma mark - NSURLSessionDownloadDelegate
- // 接收到服务器返回数据,会被调用多次
- - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
- {
- // 获取模型
- RQDownloadModel *model = [[RQDataBaseManager shareManager] getModelWithUrl:downloadTask.taskDescription];
-
- // 更新当前下载大小
- model.fileDownloadSize = totalBytesWritten;
- model.fileTotalSize = totalBytesExpectedToWrite;
-
- // 计算速度时间内下载文件的大小
- model.intervalFileSize += bytesWritten;
-
- // 获取上次计算时间与当前时间间隔
- NSInteger intervals = [RQ_SHARE_FUNCTION getIntervalsWithTimeStamp:model.lastSpeedTime];
- if (intervals >= 1) {
- // 计算速度
- model.speed = model.intervalFileSize / intervals;
-
- // 重置变量
- model.intervalFileSize = 0;
- model.lastSpeedTime = [RQ_SHARE_FUNCTION getTimeStampWithDate:[NSDate date]];
- }
-
- // 计算进度
- double byts = totalBytesWritten * 1.0 / 1024 /1024;
- double total = totalBytesExpectedToWrite * 1.0 / 1024 /1024;
- NSString *text = [NSString stringWithFormat:@"%.1lfMB/%.1lfMB",byts,total];
- CGFloat progress = 1.0 * byts / total;
- model.statusText = text;
- model.progress = progress;
- NSLog(@"%@",text);
-
- // 更新数据库中数据
- [[RQDataBaseManager shareManager] updateWithModel:model option:RQDBUpdateOptionAllParam];
-
- // 进度通知
- [[NSNotificationCenter defaultCenter] postNotificationName:RQDownloadProgressNotification object:model];
- }
- // 下载完成
- - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
- {
- // 获取模型
- RQDownloadModel *model = [[RQDataBaseManager shareManager] getModelWithUrl:downloadTask.taskDescription];
-
- // 移动文件,原路径文件由系统自动删除
- NSError *error = nil;
- if (model) {
- [[NSFileManager defaultManager] moveItemAtPath:[location path] toPath:model.destinationPath error:&error];
- if (error) NSLog(@"下载完成,移动文件发生错误:%@", error);
- }
- }
- #pragma mark - NSURLSessionTaskDelegate
- // 请求完成
- - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
- {
- // 调用cancel方法直接返回,在相应操作是直接进行处理
- if (error && [error.localizedDescription isEqualToString:@"cancelled"]) return;
-
- // 获取模型
- RQDownloadModel *model = [[RQDataBaseManager shareManager] getModelWithUrl:task.taskDescription];
-
- // 下载时,进程杀死,重新启动,回调错误
- if (error && [error.userInfo objectForKey:NSURLErrorBackgroundTaskCancelledReasonKey]) {
- model.status = RQDownloadStatus_Waiting;
- model.resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
- [[RQDataBaseManager shareManager] updateWithModel:model option:RQDBUpdateOptionAllParam];
- return;
- }
-
- // 更新下载数据、任务状态
- if (error) {
- model.status = RQDownloadStatus_Failed;
- model.resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
- [[RQDataBaseManager shareManager] updateWithModel:model option:RQDBUpdateOptionAllParam];
-
- }else {
- model.status = RQDownloadStatus_Completed;
- }
-
- // 更新数据
- if (_currentCount > 0) _currentCount--;
- if (model.urlString) {
- [_dataTaskDic removeObjectForKey:model.urlString];
- }
- // 更新数据库状态
- [[RQDataBaseManager shareManager] updateWithModel:model option:RQDBUpdateOptionAllParam];
-
- // 开启等待下载任务
- [self startDownloadWaitingTask];
- NSLog(@"\n 文件:%@,下载完成 \n 本地路径:%@ \n 错误:%@ \n", model.fileName, model.destinationPath, error);
- }
- #pragma mark - NSURLSessionDelegate
- // 应用处于后台,所有下载任务完成及NSURLSession协议调用之后调用
- - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
- {
- dispatch_async(dispatch_get_main_queue(), ^{
- AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
- if (appDelegate.backgroundSessionCompletionHandler) {
- void (^completionHandler)(void) = appDelegate.backgroundSessionCompletionHandler;
- appDelegate.backgroundSessionCompletionHandler = nil;
-
- // 执行block,系统后台生成快照,释放阻止应用挂起的断言
- completionHandler();
- }
- });
- }
- #pragma mark - RQDownloadMaxConcurrentCountChangeNotification
- - (void)downloadMaxConcurrentCountChange:(NSNotification *)notification {
- self.maxConcurrentCount = [notification.object integerValue];
-
- if (_currentCount < self.maxConcurrentCount) {
- // 当前下载数小于并发数,开启等待下载任务
- [self startDownloadWaitingTask];
-
- }else if (_currentCount > self.maxConcurrentCount) {
- // 变更正在下载任务为等待下载
- [self pauseDownloadingTaskWithAll:NO];
- }
- }
- #pragma mark - RQDownloadAllowsCellularAccessChangeNotification
- - (void)downloadAllowsCellularAccessChange:(NSNotification *)notification {
- _allowsCellularAccess = [notification.object boolValue];
-
- [self allowsCellularAccessOrNetworkingReachabilityDidChangeAction];
- }
- #pragma mark - RQNetworkingReachabilityDidChangeNotification
- - (void)networkingReachabilityDidChange:(NSNotification *)notification
- {
- [self allowsCellularAccessOrNetworkingReachabilityDidChangeAction];
- }
- // 是否允许蜂窝网络下载或网络状态变更事件
- - (void)allowsCellularAccessOrNetworkingReachabilityDidChangeAction {
- if ([[RQNetworkReachabilityManager shareManager] networkReachabilityStatus] == AFNetworkReachabilityStatusNotReachable) {
- // 无网络,暂停正在下载任务
- [self pauseDownloadingTaskWithAll:YES];
-
- }else {
- if ([self networkingAllowsDownloadTask]) {
- // 开启等待任务
- [self startDownloadWaitingTask];
-
- }else {
- // 增加一个友善的提示,蜂窝网络情况下如果有正在下载,提示已暂停
- if ([kRQDownloadDataBaseManager getLastDownloadingModel]) {
- ShowMsg(@"当前为蜂窝网络,已停止下载任务,可在设置中开启");
- }
-
- // 当前为蜂窝网络,不允许下载,暂停正在下载任务
- [self pauseDownloadingTaskWithAll:YES];
- }
- }
- }
- // 是否允许下载任务
- - (BOOL)networkingAllowsDownloadTask {
- // 当前网络状态
- AFNetworkReachabilityStatus status = [[RQNetworkReachabilityManager shareManager] networkReachabilityStatus];
-
- // 无网络 或 (当前为蜂窝网络,且不允许蜂窝网络下载)
- // if (status == AFNetworkReachabilityStatusNotReachable || (status == AFNetworkReachabilityStatusReachableViaWWAN && !_allowsCellularAccess)) {
- // return NO;
- // }
-
- if (status == AFNetworkReachabilityStatusNotReachable) {
- return NO;
- }
-
- return YES;
- }
- - (void)dealloc {
- [_session invalidateAndCancel];
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- }
- #pragma mark - LazyLoad
- @end
|