iOS斷點(diǎn)下載偶妖、kill掉APP可繼續(xù)下載

一姜凄、 AFN3.0 下載過程

  1. 第一步肯定是創(chuàng)建AFURLSessionManager,配置一些NSURLSessionConfiguration趾访,這一步我就不做多的敘述了态秧。

不過因?yàn)槲覀兪菙帱c(diǎn)下載,可以在不同的地方都調(diào)用扼鞋,而且為了調(diào)用方便申鱼,我們直接提供類(+)方法愤诱,但有一些成員變量可以重復(fù)使用,例如AFURLSessionManager捐友,綜合考慮淫半,將折現(xiàn)成員變量聲明稱靜態(tài)變量,并在+initialize方法里使用dispatch_once初始化楚殿。

  1. 創(chuàng)建NSURLSessionDownloadTask:

    - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
                                          progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                                       destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                 completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler {}
    
    - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
                                             progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                                          destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                    completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler {}
    

我們可以看到AFN給我們提供了兩個創(chuàng)建NSURLSessionDownloadTask的方法撮慨,第一種方式是通過NSURLRequest創(chuàng)建竿痰,第二種方式是通過NSData創(chuàng)建脆粥。通過參數(shù)我們也能看出來,第一種是為了第一次下載提供的影涉,第二種是為了我們做斷點(diǎn)下載提供的变隔。

  1. 開始下載

    [task resume];
    下載時(shí),會在tmp文件中生成下載的臨時(shí)文件蟹倾,
    文件名是CFNetworkDownload_XXXXXX.tmp匣缘,后綴由系統(tǒng)隨機(jī)生成
    下載完將臨時(shí)文件移動到目的路徑,路徑為創(chuàng)建DownloadTask時(shí)傳入的destination參數(shù)
    
  2. 暫停下載

    [task suspend]
    暫停后task依然有效鲜棠,通過resume又可以恢復(fù)下載
    
  3. 取消下載任務(wù)肌厨,取消下載任務(wù),當(dāng)前的task會失效豁陆,如果想繼續(xù)下載柑爸,需要重新創(chuàng)建下載任務(wù)

    [task cancel]
    [task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {}]
    1. 第二種手動取消任務(wù),會返回一個resumeData盒音,這個參數(shù)是我們做斷點(diǎn)下載所必須的表鳍,我們可以直接通過resumeData開啟一個新的下載任務(wù),發(fā)起下載請求
    2. 取消任務(wù)時(shí)祥诽,只有滿足以下的各條件譬圣,才會產(chǎn)生resumeData
        1. 自從資源開始請求后,資源未更改過
        2. 任務(wù)必須是 HTTP 或 HTTPS 的 GET 請求
        3. 服務(wù)器在response信息匯總提供了 ETag 或 Last-Modified頭部信息
        4. 服務(wù)器支持 byte-range 請求
        5. 下載的臨時(shí)文件未被刪除
    

二雄坪、斷點(diǎn)下載的實(shí)現(xiàn)

源自網(wǎng)絡(luò):下載交互過程順序圖
  1. 為什么會出現(xiàn)斷點(diǎn)下載厘熟,我分了三種情況

    1. 手動取消,也就是我們提供給用戶或我們項(xiàng)目內(nèi)部調(diào)用了1.5中的cancel方法
    2. 網(wǎng)絡(luò)维哈、服務(wù)器異常绳姨,導(dǎo)致下載失敗
    3. 用戶手動kill掉APP
  2. 第二情況的斷點(diǎn)下載實(shí)現(xiàn)(部分網(wǎng)絡(luò)錯誤):

     JQDownloadManagerCompletion completeBlock = ^(NSURLResponse *response, NSURL *filePath, NSError *error) {
         if (!error) { // 任務(wù)完成或暫停下載
             [self removeResumeDataWithUrl:url];
             [self removeTaskWithUrl:url];
         } else  { // 部分網(wǎng)絡(luò)出錯,會返回resumeData
             NSData *resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
             if (resumeData) [self saveResumeData:resumeData url:url];
         }
     }
    

部分網(wǎng)絡(luò)錯誤笨农,在error.userInfo中我們是可以獲取到resumeData就缆,可以直接用于斷點(diǎn)下載。

  1. 第三種情況最復(fù)雜

    1. 嘗試在網(wǎng)絡(luò)失敗時(shí)獲取resumeData谒亦,由于時(shí)間太短竭宰,不可行

    2. 嘗試通過監(jiān)聽UIApplicationWillTerminateNotification的通知空郊,在app要結(jié)束的時(shí)候獲取resumeData并保存,但現(xiàn)實(shí)還是比較殘酷切揭,由于時(shí)間太短還是無法獲取resumeData狞甚,不可行

    3. 從resumeData入手,進(jì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://downloadUrl</string>
           <key>NSURLSessionResumeBytesReceived</key>
               <integer>1474327</integer>
           <key>NSURLSessionResumeCurrentRequest</key>
               <data>
                ......
               </data>
           <key>NSURLSessionResumeEntityTag</key>
               <string>"XXXXXXXXXX"</string>
           <key>NSURLSessionResumeInfoTempFileName</key>
               <string>CFNetworkDownload_XXXXX.tmp</string>
           <key>NSURLSessionResumeInfoVersion</key>
               <integer>2</integer>
           <key>NSURLSessionResumeOriginalRequest</key>
               <data>
                .....
               </data>
           <key>NSURLSessionResumeServerDownloadDate</key>
                <string>week, dd MM yyyy hh:mm:ss </string>
      </dict></plist>
      
      1. 上面就是解析resumeData之后的數(shù)據(jù)廓旬,其實(shí)就是一個plist文件哼审,里面信息包括了下載URL、已接收字節(jié)數(shù)孕豹、臨時(shí)的下載文件名(文件默認(rèn)存在tmp文件夾中)涩盾、當(dāng)前請求、原始請求励背、下載事件春霍、resumeInfo版本、EntityTag這些數(shù)據(jù)
      2. iOS8生成的resumeData稍有不同叶眉,沒有NSURLSessionResumeInfoTempFileName字段址儒,有NSURLSessionResumeInfoLocalPath,記錄了完整的tmp文件地址
  2. 主要需要幾個參數(shù):下載URL衅疙、當(dāng)前請求莲趣、已接收字節(jié)數(shù)、臨時(shí)的下載文件名(文件默認(rèn)存在tmp文件夾中)這四個數(shù)據(jù)

    1. 下載URL:已知
    2. 當(dāng)前請求:需要通過已經(jīng)下載的大小和URL創(chuàng)建
    3. 已接收字節(jié)數(shù):需要通過臨時(shí)文件來獲取大小
    4. 臨時(shí)文件:存放在本地tmp文件夾下饱溢,但由于文件名CFNetworkDownload_XXXXXX.tmp喧伞,是系統(tǒng)隨機(jī)生成的,我們無法將tmp文件和URL對應(yīng)理朋。
  3. 獲取tmp文件路徑

    1. 手動cancel絮识,在繼續(xù)任務(wù),在cancel回調(diào)中可以獲取到resumeData嗽上,里面直接包含所有信息次舌,我們只需要把數(shù)據(jù)中的字節(jié)數(shù)和當(dāng)前請求更換就可以。
    2. 上一種方法兽愤,有顯而易見的缺點(diǎn)彼念,性能時(shí)間都會浪費(fèi),后來通過調(diào)試浅萧,查看信息逐沙,發(fā)現(xiàn)NSURLSessionDownloadTask中有個數(shù)據(jù)downloadFile存放了一些關(guān)于下載的信息,其中一個信息path就是存放臨時(shí)文件路徑的洼畅,通過lastPathComponent就可以直接取到相應(yīng)的臨時(shí)文件名吩案。
    3. 通過tmp文件名獲取tmp文件路徑,這樣做是因?yàn)楸镜匚募窂綍兊鄞兀圆荒苤苯哟鎡ask中的文件路徑徘郭,需要獲取到文件名靠益,通過tmp的路徑獲取到tmp文件路徑
  4. 生成resumeData

     NSData *resumeData;
     NSFileManager *fileMgr = [NSFileManager defaultManager];
     if ([fileMgr fileExistsAtPath:tempFilePath]) {
         NSDictionary *tempFileAttr = [[NSFileManager defaultManager] attributesOfItemAtPath:tempFilePath error:nil ];
         unsigned long long fileSize = [tempFileAttr[NSFileSize] unsignedLongLongValue];
     
         if (fileSize > 0) {
             NSMutableDictionary *fakeResumeData = [NSMutableDictionary dictionary];
         
             NSMutableURLRequest *newResumeRequest =[NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
             NSString *bytesStr =[NSString stringWithFormat:@"bytes=%ld-",fileSize];
             [newResumeRequest addValue:bytesStr forHTTPHeaderField:@"Range"];
         
             NSData *newResumeData =[NSKeyedArchiver archivedDataWithRootObject:newResumeRequest];
             [fakeResumeData setObject:newResumeData forKey:@"NSURLSessionResumeCurrentRequest"];
             [fakeResumeData setObject:url forKey:@"NSURLSessionDownloadURL"];
             [fakeResumeData setObject:@(fileSize) forKey:@"NSURLSessionResumeBytesReceived"];
             [fakeResumeData setObject:[tempFilePath lastPathComponent] forKey:@"NSURLSessionResumeInfoTempFileName"]; // iOS9以下 需要路徑
    
             resumeData = [NSPropertyListSerialization dataWithPropertyList:fakeResumeData format:NSPropertyListXMLFormat_v1_0 options:0 error:nil];
         }
     }
    
  5. 大功告成,如果獲取到resumeData残揉,可以直接通過resumeData創(chuàng)建task胧后,進(jìn)行斷點(diǎn)下載了。

  6. 下載完成抱环,刪除相應(yīng)的數(shù)據(jù)

三壳快、斷點(diǎn)下載中涉及其他的知識點(diǎn)

  1. 數(shù)據(jù)緩存:

    1. 正在下載的URL
    2. 正在下載的URL的回調(diào)函數(shù)
    3. 下載的URL對應(yīng)的resumeData
    4. 下載的URL對應(yīng)的tmp文件名

    1 2 因?yàn)榭隙ㄊ茿PP本次啟動之后才存在的數(shù)據(jù),所以直接使用靜態(tài)變量存儲就可以镇草。
    3 4 則需要本地化眶痰,使用了JQCache存放在Document目錄下

  2. 數(shù)據(jù)安全問題:

    1. 創(chuàng)建一個dispatch_queue
    2. 使用讀寫鎖來保障數(shù)據(jù)安全
      1. dispatch_barrier_async
      2. dispatch_sync
    3. 還有互斥鎖和自旋鎖
    4. 信號量,如果有大量下載同時(shí)訪問陶夜,需要控制并發(fā)數(shù)量凛驮,這里我采用了信號量的方式去控制裆站。
  3. 同一URL處理

    1. 針對同一URL的網(wǎng)絡(luò)請求条辟,如果有下載任務(wù),則不在此下載宏胯,判斷方式通過參考SD的原理羽嫡,考慮如果URL過長,全部對URL進(jìn)行MD5處理肩袍,在進(jìn)行判斷和存儲
    2. 不進(jìn)行下載杭棵,但需要保存當(dāng)前請求的回調(diào)函數(shù),參考了關(guān)聯(lián)對象的實(shí)現(xiàn)邏輯氛赐,對URL的回調(diào)函數(shù)做處理

斷點(diǎn)下載Demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末魂爪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子艰管,更是在濱河造成了極大的恐慌滓侍,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牲芋,死亡現(xiàn)場離奇詭異撩笆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)缸浦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門夕冲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人裂逐,你說我怎么就攤上這事歹鱼。” “怎么了卜高?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵弥姻,是天一觀的道長秩霍。 經(jīng)常有香客問我,道長蚁阳,這世上最難降的妖魔是什么铃绒? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮螺捐,結(jié)果婚禮上颠悬,老公的妹妹穿的比我還像新娘。我一直安慰自己定血,他們只是感情好赔癌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著澜沟,像睡著了一般灾票。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上茫虽,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天刊苍,我揣著相機(jī)與錄音,去河邊找鬼濒析。 笑死正什,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的号杏。 我是一名探鬼主播婴氮,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盾致!你這毒婦竟也來了主经?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤庭惜,失蹤者是張志新(化名)和其女友劉穎罩驻,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜈块,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鉴腻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了百揭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爽哎。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖器一,靈堂內(nèi)的尸體忽然破棺而出课锌,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布渺贤,位于F島的核電站雏胃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏志鞍。R本人自食惡果不足惜瞭亮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望固棚。 院中可真熱鬧统翩,春花似錦、人聲如沸此洲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呜师。三九已至娶桦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汁汗,已是汗流浹背衷畦。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留碰酝,地道東北人霎匈。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像送爸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子暖释,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容