ios 后台下载,断点续传总结
2018年12月05日 16:09:00 weixin_34101784 閱讀數(shù):5
?https://blog.csdn.net/weixin_34101784/article/details/87569604
斷點(diǎn)續(xù)傳
demo
?
demog.gif
斷點(diǎn)續(xù)傳的原理是在HTTP1.1協(xié)議(RFC2616)中定義了斷點(diǎn)續(xù)傳相關(guān)的HTTP頭的Range和Content-Range字段,支持只請(qǐng)求資源的一部分。
Range:可以請(qǐng)求文件資源的一個(gè)或者多個(gè)子范圍。
例如:
表示頭500個(gè)字節(jié):bytes=0-499;
表示第二個(gè)500字節(jié):bytes=500-999;
表示最后500個(gè)字節(jié):bytes=-500;
表示500字節(jié)以后的范圍:bytes=500- ;
第一個(gè)和最后一個(gè)字節(jié):bytes=0-0,-1;
同時(shí)指定幾個(gè)范圍:bytes=500-600,601-999;
Content-Range:字段說明服務(wù)器返回了文件的某個(gè)范圍及文件的總長(zhǎng)度。這時(shí)Content-Length字段就不是整個(gè)文件的大小了,而是對(duì)應(yīng)文件這個(gè)范圍的字節(jié)數(shù),這一點(diǎn)一定要注意。一般格式,Content-Range: bytes 500-999/1000
NSURlSessionDownloadTask
iOS可以使用NSURlSessionDownloadTask來(lái)實(shí)現(xiàn)下載的斷點(diǎn)續(xù)傳功能,它提供了resumeData來(lái)實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能,不需要在httpheader里設(shè)置Range了。網(wǎng)上關(guān)于NSURlSessionDownloadTask實(shí)現(xiàn)斷點(diǎn)續(xù)傳下載的代碼有很多,這里總結(jié)下自己遇到的問題。
下載暫停和恢復(fù)
NSURlSessionDownloadTask有兩種實(shí)現(xiàn)暫停的方法:
- suspend:直接調(diào)suspend方法可以使task暫停下載,恢復(fù)下載可以調(diào)用resume方法,;
- cancelByProducingResumeData::這個(gè)方法會(huì)取消task,從block里會(huì)得到一個(gè)resumeData。resumeData是用來(lái)恢復(fù)下載的。由于之前的task已經(jīng)被取消了,方法downloadTaskWithResumeData:可以使用resumeData來(lái)獲取一個(gè)新的NSURlSessionDownloadTask來(lái)繼續(xù)下載。
在使用suspend時(shí)碰到的問題:
如果一個(gè)task正在下載,調(diào)用suspend暫停task后,然后com+R重啟應(yīng)用,雖然在session的getTasksWithCompletionHandler:的block里能夠獲取到task,但是重新resume的時(shí)候有時(shí)候會(huì)失敗,有時(shí)候成功,不知道為什么。所以建議使用cancelByProducingResumeData來(lái)實(shí)現(xiàn)暫停功能。
把resumeData轉(zhuǎn)成字符串打印看一下:
-
<?xml version="1.0" encoding="UTF-8"?> -
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> -
<plist version="1.0"> -
<dict> -
? ? <key>NSURLSessionDownloadURL</key> -
? ? <string>http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.2.4.dmg</string> -
? ? <key>NSURLSessionResumeBytesReceived</key> -
? ? <integer>8297040</integer> -
? ? <key>NSURLSessionResumeCurrentRequest</key> -
? ? <data> -
? ? ? ? ... -
? ? </data> -
? ? <key>NSURLSessionResumeInfoTempFileName</key> -
? ? <string>CFNetworkDownload_sFZ0E8.tmp</string> -
? ? <key>NSURLSessionResumeInfoVersion</key> -
? ? <integer>4</integer> -
? ? <key>NSURLSessionResumeOriginalRequest</key> -
? ? <data> -
?? ? ? ? ... -
? ? </data> -
? ? <key>NSURLSessionResumeServerDownloadDate</key> -
? ? <string>Thu, 12 May 2016 02:41:27 GMT</string> -
</dict> -
</plist>
可以看出,resumeData實(shí)質(zhì)是一個(gè)plist文件,里面包含了一些下載的信息,NSURLSessionResumeBytesReceived對(duì)應(yīng)的是已經(jīng)下載的字節(jié)數(shù),NSURLSessionResumeInfoTempFileName對(duì)應(yīng)的是下載的臨時(shí)文件的名字。
如果下載出現(xiàn)錯(cuò)誤,文檔里是這樣描述的
When any task completes, the NSURLSession object calls the delegate’s URLSession:task:didCompleteWithError: method with either an error object or nil (if the task completed successfully). If the download task can be resumed, the NSError object’s userInfo dictionary contains a value for the NSURLSessionDownloadTaskResumeData key. Your app should pass this value to call downloadTaskWithResumeData: or downloadTaskWithResumeData:completionHandler: to create a new download task that continues the existing download. If the task can’t be resumed, your app should create a new download task and restart the transaction from the beginning. In either case, if the transfer failed for any reason other than a server error, go to step 3 (creating and resuming task objects).
出錯(cuò)時(shí)會(huì)調(diào)用URLSession:task:didCompleteWithError:獲得error對(duì)象,如果下載是可以恢復(fù)的,可以使用error.userInfo[NSURLSessionDownloadTaskResumeData]來(lái)獲取resumeData恢復(fù)下載。有兩種情況獲取resumeData:
- task調(diào)用cancelByProducingResumeData:之后會(huì)調(diào)用URLSession:task:didCompleteWithError:來(lái)獲取resumeData,
- 還有user主動(dòng)kill應(yīng)用后系統(tǒng)會(huì)取消下載中的task,重新啟動(dòng)時(shí)創(chuàng)建session后也可以在URLSession:task:didCompleteWithError:里獲取到resumeData。
NSURLSessionDownloadTask在后臺(tái)下載
NSURLSessionDownloadTask是支持后臺(tái)下載的。
downloading_files_in_the_background
Downloading Content in the Background
When downloading files, apps should use an NSURLSession object to start the downloads so that the system can take control of the download process in case the app is suspended or terminated. When you configure an NSURLSession object for background transfers, the system manages those transfers in a separate process and reports status back to your app in the usual way. If your app is terminated while transfers are ongoing, the system continues the transfers in the background and launches your app (as appropriate) when the transfers finish or when one or more tasks need your app’s attention.
Once configured, your NSURLSession object seamlessly hands off upload and download tasks to the system at appropriate times. If tasks finish while your app is still running (either in the foreground or the background), the session object notifies its delegate in the usual way. If tasks have not yet finished and the system terminates your app, the system automatically continues managing the tasks in the background. If the user terminates your app, the system cancels any pending tasks.
文檔里提到了app的幾種情況:
- 如果app在后臺(tái),但是is still running(比如按home鍵把a(bǔ)pp切到后臺(tái)),會(huì)正常調(diào)用session的代理方法;
- 如果app被系統(tǒng)terminate了(比如app在后臺(tái)時(shí)間過長(zhǎng)可能會(huì)被系統(tǒng)強(qiáng)制殺掉),系統(tǒng)會(huì)繼續(xù)在后臺(tái)管理session的task,當(dāng)task完成時(shí)系統(tǒng)會(huì)啟動(dòng)app,此時(shí)只要使用相同的id創(chuàng)建session就會(huì)回調(diào)代理方法了;
- 如果使用戶主動(dòng)kill了app,系統(tǒng)會(huì)取消session的task,重新啟動(dòng)時(shí),用相同的id創(chuàng)建session,會(huì)調(diào)用代理方法URLSession:task:didCompleteWithError:,可以獲取到resumeData來(lái)恢復(fù)下載。
在后臺(tái)下載完成時(shí)的處理
下載任務(wù)在后臺(tái)完成后:
如果app在后臺(tái),但是沒有被系統(tǒng)teminate,系統(tǒng)會(huì)resume 應(yīng)用并且調(diào)用UIApplicationDelegate的代理方法application:handleEventsForBackgroundURLSession:completionHandler:。之后會(huì)調(diào)用session的代理方法。
如果app在后臺(tái)時(shí)候被系統(tǒng)terminate了,當(dāng)下載task完成時(shí),系統(tǒng)會(huì)在后臺(tái)重啟應(yīng)用并調(diào)用UIApplicationDelegate的代理方法application:handleEventsForBackgroundURLSession:completionHandler:。方法里獲得的identifier是之前創(chuàng)建session時(shí)的identifier。需要用這個(gè)idetifier重新創(chuàng)建session,之后會(huì)調(diào)用session的代理方法。
調(diào)用這個(gè)方法獲得的completionHandler可以讓系統(tǒng)知道您的應(yīng)用程序的用戶界面已更新,并且可以拍攝新的快照。一般在session的代理方法URLSessionDidFinishEventsForBackgroundURLSession:中調(diào)用。
在下載過程中用戶主動(dòng)kill app
用戶主動(dòng)kill app會(huì)導(dǎo)致下載task取消,當(dāng)應(yīng)用再次啟動(dòng)時(shí),需要使用之前創(chuàng)建session的identifier重新創(chuàng)建session,之后會(huì)調(diào)用URLSession: task: didCompleteWithError:方法,從error中可以獲取resumeData來(lái)恢復(fù)下載。
系統(tǒng)終止了app,重啟時(shí)獲取下載中的task
比如應(yīng)用退到后臺(tái),因?yàn)閮?nèi)存問題被系統(tǒng)teminate,這時(shí)候下載任務(wù)不會(huì)取消,系統(tǒng)會(huì)繼續(xù)管理下載task,此時(shí)若重新打開應(yīng)用,可以使用相同的identifier創(chuàng)建session,然后通過
-
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { -
?? ? -
?? }];
這個(gè)方法來(lái)獲取task。如果在模擬器上正在進(jìn)行下載任務(wù),然后com+R重新運(yùn)行程序也可以使用這個(gè)方法獲取之前創(chuàng)建的下載task。
如果task在下載過程中用戶主動(dòng)kill app,task會(huì)被取消,再重新啟動(dòng)應(yīng)用時(shí)。上面的這個(gè)方法會(huì)獲取到取消的task,它的state是NSURLSessionTaskStateCompleted,不能使用這個(gè)task繼續(xù)請(qǐng)求了,可以從session的代理方法里獲取resumeData重新創(chuàng)建task來(lái)繼續(xù)請(qǐng)求。
?
iOS 大文件下載、斷點(diǎn)續(xù)傳、后臺(tái)下載 —— HERO博客
https://blog.csdn.net/hero_wqb/article/details/80407478
2018年05月23日 14:40:02 hero_wqb 閱讀數(shù):6681
?
版權(quán)聲明:轉(zhuǎn)載請(qǐng)注明出處。 https://blog.csdn.net/hero_wqb/article/details/80407478
本篇簡(jiǎn)述一下實(shí)現(xiàn)文件下載功能,包含大文件下載,后臺(tái)下載,殺死進(jìn)程,重新啟動(dòng)時(shí)繼續(xù)下載,設(shè)置下載并發(fā)數(shù),監(jiān)聽網(wǎng)絡(luò)改變等,并在最后附有Demo。
?
下載功能的實(shí)現(xiàn):
使用的網(wǎng)絡(luò)連接的類為NSURLSession。該類用以替代NSURLConnection,在iOS7時(shí)推出,至此iOS系統(tǒng)才有了后臺(tái)傳輸。在初始化NSURLSession前,需要先創(chuàng)建NSURLSessionConfiguration,可以理解為是NSURLSession需要的一個(gè)配置。NSURLSessionConfiguration有三種模式:
1. default:可以使用緩存的Cache、Cookie、鑒權(quán)。
2. ephemeral,僅內(nèi)存緩存,不使用緩存的Cache、Cookie、鑒權(quán)。
3. background,支持后臺(tái)傳輸,需要一個(gè)identifier標(biāo)識(shí),用來(lái)重新連接session對(duì)象。
創(chuàng)建后臺(tái)模式NSURLSessionConfiguration:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"HWDownloadBackgroundSessionIdentifier"];
創(chuàng)建NSURLSession,設(shè)置配信息、代理、代理線程:
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
在實(shí)現(xiàn)下載前,還需要了解一個(gè)很重要的類,NSURLSessionTask,無(wú)論下載多少文件,我們只需要初始化一個(gè)NSURLSession即可,而每個(gè)task對(duì)應(yīng)一個(gè)任務(wù),需要通過task才能實(shí)現(xiàn)下載,NSURLSessionTask是一個(gè)基類,有四個(gè)子類:
1. NSURLSessionDataTask:下載時(shí),內(nèi)容以NSData對(duì)象返回,需要我們不斷寫入文件,但不支持后臺(tái)傳輸,切換后臺(tái)會(huì)終止下載,回到前臺(tái)時(shí)在協(xié)議方法中輸出error,下面貼一下用NSURLSessionDataTask實(shí)現(xiàn)斷點(diǎn)續(xù)傳的核心代碼:
-
// 遵守協(xié)議 -
<NSURLSessionDataDelegate> -
// 創(chuàng)建NSMutableURLRequest -
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]]; -
[request setValue:[NSString stringWithFormat:@"bytes=%zd-", tmpFileSize] forHTTPHeaderField:@"Range"]; -
// 創(chuàng)建NSURLSessionDataTask -
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request]; -
// 開始、繼續(xù)下載 -
[dataTask resume]; -
// 暫停下載 -
[dataTask suspend]; -
// 取消下載 -
[dataTask cancel]; -
? -
// 接收到服務(wù)器響應(yīng) -
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler -
{ -
? ? // 更新文件的總大小 -
? ? totalFileSize = response.expectedContentLength + tmpFileSize; -
? ? -
? ? // 創(chuàng)建輸出流 -
? ? NSOutputStream *stream = [[NSOutputStream alloc] initToFileAtPath:fullPath append:YES]; -
? ? [stream open]; -
? ? -
? ? // 允許處理服務(wù)器的響應(yīng),繼續(xù)接收數(shù)據(jù) -
? ? completionHandler(NSURLSessionResponseAllow); -
} -
? -
// 接收到服務(wù)器返回?cái)?shù)據(jù),會(huì)被調(diào)用多次 -
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data -
{ -
? ? // 寫入數(shù)據(jù) -
? ? [stream write:data.bytes maxLength:data.length]; -
? ? -
? ? // 當(dāng)前下載大小 -
? ? tmpFileSize += data.length; -
? ? -
? ? // 進(jìn)度 -
? ? self.progressView.progress = 1.0 * tmpFileSize / totalFileSize; -
} -
? -
// 當(dāng)請(qǐng)求完成之后調(diào)用,如果錯(cuò)誤,那么error有值 -
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error -
{ -
? ? [stream close]; -
} -
? -
- (void)dealloc -
{ -
? ? [session invalidateAndCancel]; -
}
有幾點(diǎn)需要注意,調(diào)用cancel方法會(huì)立即進(jìn)入-URLSession: task: didCompleteWithError這個(gè)回調(diào);調(diào)用suspend方法,即使任務(wù)已經(jīng)暫停,但達(dá)到超時(shí)時(shí)長(zhǎng),也會(huì)進(jìn)入這個(gè)回調(diào),可以通過error進(jìn)行判斷;當(dāng)一個(gè)任務(wù)調(diào)用了resume方法,但還未開始接受數(shù)據(jù),這時(shí)調(diào)用suspend方法是無(wú)效的。也可以通過cancel方法實(shí)現(xiàn)暫停,只是每次需要重新創(chuàng)建NSURLSessionDataTask。
2. NSURLSessionUploadTask:繼承自NSURLSessionDataTask,內(nèi)容以NSData對(duì)象返回,協(xié)議方法中可以查看請(qǐng)求時(shí)上傳內(nèi)容的過程,支持后臺(tái)傳輸。
3. NSURLSessionStreamTask:建立了一個(gè)TCP/IP連接,替代NSInputStream/NSOutputStream,新的API可異步讀寫,自動(dòng)通過HTTP代理連接遠(yuǎn)程服務(wù)器。
4. NSURLSessionDownloadTask:筆者推薦使用該task實(shí)現(xiàn)文件下載,斷點(diǎn)續(xù)傳系統(tǒng)幫我們做了,資源會(huì)下載到一個(gè)臨時(shí)文件,下載完成需將文件移動(dòng)至想要的路徑,系統(tǒng)會(huì)刪除臨時(shí)路勁文件,暫停時(shí),系統(tǒng)會(huì)返回NSData對(duì)象,恢復(fù)下載時(shí)用這個(gè)data創(chuàng)建task,支持后臺(tái)傳輸,下面重點(diǎn)介紹一下NSURLSessionDownloadTask的使用:
創(chuàng)建NSURLSessionDownloadTask,有兩種方式,后面會(huì)講解NSData在哪里獲取,其中需要注意一點(diǎn),在iOS 10.0和iOS 10.1系統(tǒng)中,使用downloadTaskWithResumeData:會(huì)發(fā)生數(shù)據(jù)錯(cuò)誤問題,需要進(jìn)行額外處理,具體可以在Demo中查看:
-
// 根據(jù)NSData對(duì)象創(chuàng)建,可以繼續(xù)上次進(jìn)度下載 -
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithResumeData:resumeData]; -
? -
// 根據(jù)NSURLRequesta對(duì)象創(chuàng)建,開啟新的下載 -
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:model.url]]];
開始、繼續(xù)下載用NSURLSessionTask的resume方法,暫停下載用下面方法,這里拿到回調(diào)的NSData,保存,可以通過它來(lái)創(chuàng)建task實(shí)現(xiàn)繼續(xù)下載:
-
[downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) { -
? ? model.resumeData = resumeData; -
}];
遵守協(xié)議,實(shí)現(xiàn)相應(yīng)協(xié)議方法:
NSURLSessionDownloadDelegate:
-
/** -
?接收到服務(wù)器返回?cái)?shù)據(jù),會(huì)被調(diào)用多次,可獲取文件大小,進(jìn)度,計(jì)算速度等 -
-
?@param bytesWritten 當(dāng)次寫入文件大小 -
?@param totalBytesWritten 已寫入文件大小 -
?@param totalBytesExpectedToWrite 文件總大小 -
?*/ -
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite -
{ -
? ? // 計(jì)算進(jìn)度 -
? ? model.progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite; -
} -
? -
// 下載完成 -
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location -
{ -
? ? // 移動(dòng)文件,原路徑文件由系統(tǒng)自動(dòng)刪除 -
? ? [[NSFileManager defaultManager] moveItemAtPath:[location path] toPath:localPath error:nil]; -
}
NSURLSessionTaskDelegate,注意調(diào)用cancel、cancelByProducingResumeData:方法也會(huì)調(diào)用:
-
// 請(qǐng)求完成,有錯(cuò)誤時(shí),error有值 -
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;
?
后臺(tái)下載:
到這里,已經(jīng)可以通過NSURLSessionDownloadTask實(shí)現(xiàn)斷點(diǎn)續(xù)傳了,下面介紹如何實(shí)現(xiàn)后臺(tái)下載,其實(shí)非常簡(jiǎn)單,一共三步:
1. 創(chuàng)建NSURLSession時(shí),需要?jiǎng)?chuàng)建后臺(tái)模式NSURLSessionConfiguration,上面已經(jīng)介紹過了。
2. 在AppDelegate中實(shí)現(xiàn)下面方法,并定義變量保存completionHandler代碼塊:
-
// 應(yīng)用處于后臺(tái),所有下載任務(wù)完成調(diào)用 -
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler -
{ -
? ? _backgroundSessionCompletionHandler = completionHandler; -
}
3. 在下載類中實(shí)現(xiàn)下面NSURLSessionDelegate協(xié)議方法,其實(shí)就是先執(zhí)行完task的協(xié)議,保存數(shù)據(jù)、刷新界面之后再執(zhí)行在AppDelegate中保存的代碼塊:
-
// 應(yīng)用處于后臺(tái),所有下載任務(wù)完成及NSURLSession協(xié)議調(diào)用之后調(diào)用 -
- (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; -
? ? ? ? ? ? -
? ? ? ? ? ? // 執(zhí)行block,系統(tǒng)后臺(tái)生成快照,釋放阻止應(yīng)用掛起的斷言 -
? ? ? ? ? ? completionHandler(); -
? ? ? ? } -
? ? }); -
}
?
?
程序終止,再次啟動(dòng)繼續(xù)下載:
后臺(tái)下載實(shí)現(xiàn)之后,再看一下如何實(shí)現(xiàn)進(jìn)程殺死后,再次啟動(dòng)時(shí)繼續(xù)下載,在應(yīng)用程序被殺掉時(shí),系統(tǒng)會(huì)自動(dòng)保存應(yīng)用下載session信息,重新啟動(dòng)應(yīng)用時(shí),如果創(chuàng)建和之前相同identifier的session,系統(tǒng)會(huì)找到對(duì)應(yīng)的session數(shù)據(jù),并響應(yīng)-URLSession: task: didCompleteWithError:方法,打印error輸出如下:
error: Error Domain=NSURLErrorDomain Code=-999 "(null)" UserInfo={NSURLErrorBackgroundTaskCancelledReasonKey=0, NSErrorFailingURLStringKey=https://www.apple.com/105/media/cn/iphone-x/2017/01df5b43-28e4-4848-bf20-490c34a926a7/films/feature/iphone-x-feature-cn-20170912_1280x720h.mp4, NSErrorFailingURLKey=https://www.apple.com/105/media/cn/iphone-x/2017/01df5b43-28e4-4848-bf20-490c34a926a7/films/feature/iphone-x-feature-cn-20170912_1280x720h.mp4, NSURLSessionDownloadTaskResumeData=<CFData 0x7ff401097c00 [0x104cb6bb0]>{length = 6176, capacity = 6176, bytes = 0x3c3f786d6c2076657273696f6e3d2231 ... 2f706c6973743e0a}}
可以看到,有幾點(diǎn)有用的信息:
1)error.localizedDescription為"(null)",打印結(jié)果為"The operation couldn't be completed. (NSURLErrorDomain error -999.)"。
2)[error.userInfo objectForkey:NSURLErrorBackgroundTaskCancelledReasonKey]有值。
3)返回了NSURLSessionDownloadTaskResumeData。
綜上進(jìn)程殺死后,再次啟動(dòng)繼續(xù)下載的思路就是,重啟時(shí),創(chuàng)建相同identifier的session,在-URLSession: task: didCompleteWithError:方法中拿到resumeData,用resumeData創(chuàng)建task,就可以恢復(fù)下載。
再說明一下,另外一種不可取的思路,在appDelegate中進(jìn)程殺死時(shí)會(huì)調(diào)用-applicationWillTerminate:方法,在這里task調(diào)用cancelByProducingResumeData:方法暫停正在下載的任務(wù),但是這個(gè)方法的回調(diào)需要時(shí)間,還沒有執(zhí)行到代碼塊進(jìn)程就已經(jīng)終止了。
?
并發(fā)數(shù)設(shè)置:
下面介紹一下下載并發(fā)數(shù)的設(shè)置:NSURLSession本身就支持多任務(wù)同時(shí)下載,它會(huì)根據(jù)性能內(nèi)部控制同時(shí)下載的個(gè)數(shù),最多5個(gè)。一個(gè)任務(wù)對(duì)應(yīng)一個(gè)NSURLSessionDownloadTask,所以想多任務(wù)同時(shí)下載,需要?jiǎng)?chuàng)建多個(gè)task,可以用數(shù)組或字典保存。我們定義變量去記錄當(dāng)前下載文件個(gè)數(shù)及用戶設(shè)置的最大下載個(gè)數(shù)。
?
監(jiān)聽網(wǎng)絡(luò)改變:用AFN監(jiān)聽,可以點(diǎn)擊這里查看
為了增加用戶體驗(yàn),往往在設(shè)置中會(huì)給用戶一個(gè)選項(xiàng), 選擇蜂窩網(wǎng)絡(luò)下是否允許下載。NSURLSessionConfiguration本身就有一個(gè)屬性allowsCellularAccess,默認(rèn)為YES,允許蜂窩網(wǎng)絡(luò)下載。如果不需要用戶隨時(shí)變更這個(gè)選項(xiàng),是可以用這個(gè)屬性。但是對(duì)于正在下載的任務(wù),修改這個(gè)屬性是無(wú)效的,即我們已經(jīng)通過session創(chuàng)建了task對(duì)象,開啟了任務(wù),再試圖用session.configuration.allowsCellularAccess = NO;去修改這個(gè)選項(xiàng)是無(wú)效的。如果一定要用這個(gè)屬性修改這個(gè)選項(xiàng),那么只能重新創(chuàng)建session:
-
// 重新創(chuàng)建后臺(tái)NSURLSessionConfiguration,并且identifier需要改變,不能與之前一樣 -
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"HWDownloadBackgroundSessionIdentifierNew"]; -
// 修改是否允許蜂窩網(wǎng)絡(luò)下載 -
configuration.allowsCellularAccess = NO; -
// 重新創(chuàng)建NSURLSession -
_session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
所以我們創(chuàng)建NSURLSessionConfiguration時(shí)把a(bǔ)llowsCellularAccess設(shè)為YES,然后定義一個(gè)變量去控制是否允許蜂窩網(wǎng)絡(luò)下載,在網(wǎng)絡(luò)狀態(tài)改變及用戶設(shè)置修改這個(gè)選項(xiàng)之后,調(diào)用暫停、開啟任務(wù)。
?
數(shù)據(jù)保存:用FMDB存儲(chǔ)數(shù)據(jù),可以點(diǎn)擊這里查看
?
下載速度計(jì)算:
聲明兩個(gè)變量,一個(gè)記錄時(shí)間,一個(gè)記錄在特定時(shí)間內(nèi)接收到的數(shù)據(jù)大小,在接收服務(wù)器返回?cái)?shù)據(jù)的-URLSession: downloadTask: didWriteData: totalBytesWritten: totalBytesExpectedToWrite:方法中,統(tǒng)計(jì)接收到數(shù)據(jù)的大小,達(dá)到時(shí)間限定時(shí),計(jì)算速度=數(shù)據(jù)/時(shí)間,然后清空變量,為方便數(shù)據(jù)庫(kù)存儲(chǔ),這里用的時(shí)間戳:
-
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite -
{ -
? ? // 記錄在特定時(shí)間內(nèi)接收到的數(shù)據(jù)大小 -
? ? model.intervalFileSize += bytesWritten; -
? ? -
? ? // 獲取上次計(jì)算時(shí)間與當(dāng)前時(shí)間間隔 -
? ? NSInteger intervals = [[NSDate date] timeIntervalSinceDate:[NSDate dateWithTimeIntervalSince1970:model.lastSpeedTime * 0.001 * 0.001]]; -
? ? if (intervals >= 1) { -
? ? ? ? // 計(jì)算速度 -
? ? ? ? model.speed = model.intervalFileSize / intervals; -
? ? ? ? -
? ? ? ? // 重置變量 -
? ? ? ? model.intervalFileSize = 0; -
? ? ? ? model.lastSpeedTime = [[NSNumber numberWithDouble:[[NSDate date] timeIntervalSince1970] * 1000 * 1000] integerValue]; -
? ? } -
}
?
Demo效果圖:
?
??
這里在模型中加入了一個(gè)變量,記錄任務(wù)加入準(zhǔn)備下載的時(shí)間,用于計(jì)算任務(wù)開始的先后順序,如上圖3,開啟任務(wù)08、09、10、11、12,暫停,依次開啟10、11、12、08、09,然后將最大并發(fā)數(shù)由5改為2,暫停的應(yīng)該為12、08、09三個(gè)任務(wù),當(dāng)10下載完成,開啟的應(yīng)該是12而不是08。
?
Demo下載鏈接:https://github.com/HeroWqb/HWDownloadDemo
?
寫博客的初心是希望大家共同交流成長(zhǎng),博主水平有限難免有偏頗之處,歡迎批評(píng)指正。
轉(zhuǎn)載于:https://www.cnblogs.com/sundaysgarden/p/10524095.html
總結(jié)
以上是生活随笔為你收集整理的ios 后台下载,断点续传总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 发那科FANUC系统选项U盘,可以随意添
- 下一篇: 使用HanLP增强Elasticsear