標籤:
首先聲明一點: 這裡的多線程下載 並不是指的 多個線程下載一個 檔案,而是 每個線程 負責一個檔案。真正的多線程 希望後面能給大家帶來。
------------- 歡迎 愛學習的小夥伴 加群 -------------
-------------android交流群:230274309-------------
-------------一起分享,一起進步! 需要你們--------------
-------------- 期待各位愛學習的小夥伴們 的到來--------------
本文是接著前面http://blog.csdn.net/u011733020/article/details/47016715繼續寫的。感興趣的小夥伴 可以看前面的。
介面效果
2015/7/31改進
2015/7/31 日 先說 這次 改進了兩點。
第一點 ,前面說過 項目 只適合學習,作為商用的話, 效率不高,是因為 當時 點擊暫停 ,在點擊下載 繼續下載時候,如果檔案前面下載部分較大,會比較慢, 因為 java 的 inputstream 的 skip(long size) 跳過位元組 這個方法 並不能按照你 想要跳過的位元組,而是 跳過的 往往是比較小的,所以 要不斷遍曆,直到 返回滿足 條件 ,比較耗時。打個比方, 檔案大小 30M ,你下載了20M,你點了暫停 然後繼續點下載,就要跳過這20M,但是你用skip 方法 可能每次跳過 4096 位元組,這樣 要跳過20M 的時間 就會很長。這樣應該好理解。
第二點,原來 項目中, 你這一次下載沒有完成,下次在下載是 刪除掉原來的 從新 下載,這次 改成繼續上次的地方 接著下載。
吐槽下,關於下載,我最近一周 一直在看 開源的download, 但是 無奈水平有限,收穫甚微,往往是看到最後 腦袋短路。
這次改的方式比較簡單,只改動了 項目中 DownloadManager 這個類。在來看下 DownloadManager 這個類 的run 方法,
@Overridepublic void run() {info.setDownloadState(STATE_DOWNLOADING);// 先改變下載狀態notifyDownloadStateChanged(info);File file = new File(info.getPath());// 擷取下載檔案HttpResult httpResult = null;InputStream stream = null;if (info.getCurrentSize() == 0 || !file.exists()|| file.length() != info.getCurrentSize()) {// 如果檔案不存在,或者進度為0,或者進度和檔案長度不相符,就需要重新下載<span></span>info.setCurrentSize(0);file.delete();}httpResult = HttpHelper.download(info.getUrl());if (httpResult == null|| (stream = httpResult.getInputStream()) == null) {info.setDownloadState(STATE_ERROR);// 沒有下載內容返回,修改為錯誤狀態notifyDownloadStateChanged(info);} else {try {skipBytesFromStream(stream, info.getCurrentSize());} catch (Exception e1) {e1.printStackTrace();}FileOutputStream fos = null;try {fos = new FileOutputStream(file, true);int count = -1;byte[] buffer = new byte[1024];while (((count = stream.read(buffer)) != -1)&& info.getDownloadState() == STATE_DOWNLOADING) {// 每次讀取到資料後,都需要判斷是否為下載狀態,如果不是,下載需要終止,如果是,則重新整理進度fos.write(buffer, 0, count);fos.flush();info.setCurrentSize(info.getCurrentSize() + count);notifyDownloadProgressed(info);// 重新整理進度}} catch (Exception e) {info.setDownloadState(STATE_ERROR);notifyDownloadStateChanged(info);info.setCurrentSize(0);file.delete();} finally {IOUtils.close(fos);if (httpResult != null) {httpResult.close();}}<span></span>// 判斷進度是否和app總長度相等if (info.getCurrentSize() == info.getAppSize()) {info.setDownloadState(STATE_DOWNLOADED);notifyDownloadStateChanged(info);} else if (info.getDownloadState() == STATE_PAUSED) {// 判斷狀態notifyDownloadStateChanged(info);} else {info.setDownloadState(STATE_ERROR);notifyDownloadStateChanged(info);info.setCurrentSize(0);// 錯誤狀態需要刪除檔案file.delete();}}mTaskMap.remove(info.getId());}
從伺服器 返回的資料流 stream 最終是在 HttpHelper 這個類中
HttpResponse response = httpClient.execute(requestBase, httpContext);//訪問網路
通過 httpclient 去連網請求的 。
我沒有試過 httpclient addHeader("Range", "bytes=" + begin + "-" + end); 可不可以進行繼續下載。
而是改成了 通過 httpurlconnection 去請求資料
現在 的run() 方法 是這樣的。
@Overridepublic void run() {info.setDownloadState(STATE_DOWNLOADING);// 先改變下載狀態notifyDownloadStateChanged(info);File file = new File(info.getPath());// 擷取下載檔案/**********************************************************///try {try {URL url = new URL(info.getUrl());HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod("GET");conn.setConnectTimeout(30000);conn.setReadTimeout(30000);if (!file.exists()) {info.setCurrentSize(0);file.delete();} else if (file.length() > info.getAppSize()) {info.setCurrentSize(0);file.delete();} else if (file.length() == info.getAppSize()) {} else if (file.length() < info.getAppSize()) {info.setCurrentSize(file.length());}if (info.getCurrentSize() == 0 || !file.exists() || file.length() != info.getCurrentSize()) {// 如果檔案不存在,或者進度為0,或者進度和檔案長度不相符,就需要重新下載info.setCurrentSize(0);file.delete();} else if (file.length() == info.getCurrentSize() && file.length() < info.getAppSize()) {conn.setRequestProperty("Range", "bytes=" + info.getCurrentSize() + "-" + info.getAppSize());}int code = conn.getResponseCode();RandomAccessFile raf = new RandomAccessFile(file, "rw");InputStream is = conn.getInputStream();byte[] buffer = new byte[1024 * 8];int len = -1;int total = 0;// 當前線程下載的總的資料的長度if (code == 200) {} else if (code == 206) {raf.seek(file.length());}while (((len = is.read(buffer)) != -1) && (info.getDownloadState() == STATE_DOWNLOADING)) { // 下載資料的過程。raf.write(buffer, 0, len);total += len;// 需要記錄當前的資料。info.setCurrentSize(info.getCurrentSize() + len);notifyDownloadProgressed(info);// 重新整理進度}is.close();raf.close();} catch (MalformedURLException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (ProtocolException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}/*************************對於各種情況,需要刪除下載任務,從新下載的 請自己改動代碼*****************************/// 判斷進度是否和app總長度相等//} catch (Exception e) {//System.out.println(e.toString());//info.setDownloadState(STATE_ERROR);//info.setCurrentSize(0);//file.delete();//e.printStackTrace();//}if (info.getCurrentSize() == info.getAppSize()) {info.setDownloadState(STATE_DOWNLOADED);notifyDownloadStateChanged(info);} else if (info.getDownloadState() == STATE_PAUSED) {// 判斷狀態notifyDownloadStateChanged(info);} else {info.setDownloadState(STATE_ERROR);notifyDownloadStateChanged(info);info.setCurrentSize(0);// 錯誤狀態需要刪除檔案file.delete();}/**********************************************************/mTaskMap.remove(info.getId());}
先判斷 檔案存不存在,以及大小是否滿足條件, 在這裡做判斷
if (info.getCurrentSize() == 0 || !file.exists() || file.length() != info.getCurrentSize()) {// 如果檔案不存在,或者進度為0,或者進度和檔案長度不相符,就需要重新下載info.setCurrentSize(0);file.delete();} else if (file.length() == info.getCurrentSize() && file.length() < info.getAppSize()) { conn.setRequestProperty("Range", "bytes=" + info.getCurrentSize() + "-" + info.getAppSize()); }如果 檔案當前大小為0,或者檔案不存在,或者長度不等於當前長度,則重新下載,否則 設定 Range
下面 判斷 code 正常情況下code =200 表示成功,如果 設定了Range 那麼 code 返回 206 表示正常。這個時候 我們通過RandomAccessFile
RandomAccessFile 這個 類實現了 RandomAccessFile implements DataInput, DataOutput, 就是一個既可以讀 也可以寫的類。
RandomAccessFile 這個類來 處理 跳過多少位元組, 前面 我說過 inpuStream.skeep() 方法 不準確,但是 RandomAccessFile 這個類是可以的。
RandomAccessFile raf = new RandomAccessFile(file, "rw");InputStream is = conn.getInputStream();byte[] buffer = new byte[1024 * 8];int len = -1;int total = 0;// 當前線程下載的總的資料的長度if (code == 200) {} else if (code == 206) {raf.seek(file.length());}
通過 seek 方法 跳過 這些位元組。
然後
while (((len = is.read(buffer)) != -1) && (info.getDownloadState() == STATE_DOWNLOADING)) { // 下載資料的過程。raf.write(buffer, 0, len);total += len;// 需要記錄當前的資料。info.setCurrentSize(info.getCurrentSize() + len);notifyDownloadProgressed(info);// 重新整理進度}is.close();raf.close();很普通的代碼,把資料寫出去。不斷重新整理當前進度, 最後關閉流。
這樣就可以保證 快速的 暫停 繼續下載, 並且 本次下載 沒有完成,點了暫停, 下次進應用,繼續下載的時候 會接著上一次下載,但是斷網,或者你自己把網關掉 ,下次在恢複網路,或者 在點下載,我並沒有處理,有需要的就自己處理下吧,應該是捕獲異常 seckouttimeException,然後儲存資料。自己動手試下就知道了。
本次就到這裡。 關於多個線程 分段下載一個檔案, 等我有時間 在整理。
共勉
著作權聲明:本文為博主原創文章
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
J哥---------Android 多線程下載 仿下載助手(改進版)