1. 程式人生 > >Android 多線程斷點續傳同時下載多個大文件

Android 多線程斷點續傳同時下載多個大文件

總結 orm acc ast mil view inpu lib 設置

最近學習在Android環境中一些網絡請求方面的知識,其中有一部分是關於網絡下載方面的知識。在這裏解析一下自己寫的demo,總結一下自己所學的知識。下圖為demo的效果圖,仿照一些應用下載商城在ListView中列出加載項,然後可以可以下載和停止。

技術分享

1.概述

這裏有幾個比較重要的類DownloadManager、DownloadService、DownloadTask、ThreadDAOImpl。主要的下載流程如下。
(1) DownloadManager 負責下載任務的調配,以及下載服務DownloadService的啟動
(2) DownloadService 主獲取下載文件的的一些信息,包括文件的名字、文件的長度等,並創建下載任務DownloadTask
(3) DownloadTask 是正式下載文件的類,首先查看數據庫裏有沒保存過相應的斷點,並從相應的斷點開始下載,如果沒有則將文件分段,並啟動下載
(4) ThreadDAOImpl 數據庫操作類,主要是保存線程下載的斷點信息

2.多線程斷點續傳

當然這裏最核心的部分就是多線程斷點續傳,原來不是很難,就是將要下載的文件分割成多個部分,每個部分使用的不同的線程同時下載。

2.1獲取下載文件長度,設置本地文件

在DownloadService 設置下載文件的信息,如下一段代碼:

 class InitThread extends Thread {
//        FileInfo fileInfo;
TaskInfo taskInfo; public InitThread (TaskInfo taskInfo) { this.taskInfo = taskInfo; } @Override public void run() { super.run(); Log.i(tag,"InitThread"); try { URL url = new URL(taskInfo.getUrl()); HttpURLConnection con
= (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.setConnectTimeout(5000); if(con.getResponseCode() == HttpURLConnection.HTTP_OK) { int len = con.getContentLength(); <span style="color:#ff0000;">//文件的總長度</span> taskInfo.setLenght(len); if(len <= 0) { return; } …………此處省略部分 //start 設置下載文件 <span style="color:#ff6666;"> RandomAccessFile accessFile = new RandomAccessFile(new File(taskInfo.getFilePath(),taskInfo.getFileName()),"rwd"); accessFile.setLength(len); //設置文件長度</span> accessFile.close(); //end 設置下載文件 …………此處省略部分 } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }

2.2 文件分段

接下的工作是分段下載
舉個列子一個10M的文件,分成三份求整10/3 = 3,前面的一二份分別是3M,最後一份是4M。所以第一份從0~2.9,第二份是從3~5.9,第三份是從6~10,這裏只是粗來的說明。接下來看代碼,在DownloadTask中有如下有如下代碼:

/**
 * 啟動下載
 */
public void downlaod() {

     …………此處省略部分
    //start 數據庫沒有對應的線程信息,則創建相應的線程信息
    if(threadInfoList.size() <=0) {
        <span style="color:#ff6666;">int block = mTaskInfo.getLenght()/mThreadCount; //將下載文件分段,每段的長度</span>
        if(block > 0) {
            //start 根據線程數量分別建立線程信息
            for(int i = 0;i < mThreadCount;i++) {
                ThreadInfo info = new ThreadInfo(i,mTaskInfo.getUrl(),i*block,(i+1)*block-1,0);
                if(i == mThreadCount -1) {
                   <span style="color:#ff0000;"> info.setEnd(mTaskInfo.getLenght()); //分段最後一個,結束位置到文件總長度末尾</span>
                }
                threadInfoList.add(info);         //加入列表
                mThreadDao.insertThread(info);   //向數據庫插入線程信息
            }
            //end 根據線程數量分別建立線程信息
        }else {
            ThreadInfo info = new ThreadInfo(0,mTaskInfo.getUrl(),0,mTaskInfo.getLenght(),0);
            threadInfoList.add(info);
            mThreadDao.insertThread(info);
        }
    }
    //end 數據庫中沒有對應的線程信息,則創建相應的線程信息

    …………此處省略部分
}

2.3下載線程

下面是主要的下載文件的線程

主要的是設置開始讀取和結束的地方:

con.setRequestProperty("Range", "bytes=" + start + "-" + threadInfo.getEnd()); //設置讀取文件的位置,和結束位置

寫入本地文件的地方:

accessFile.seek(start);    //設置開始寫入的位置
/**
     * 下載線程
     */
    class DownloadThread extends  Thread {
        …………此處省略部分
        @Override
        public void run() {
                …………此處省略部分
                    int start = threadInfo.getStart()+threadInfo.getFinished(); //讀取文件的位置
                    //start 初始化下載鏈接
                    …………此處省略部分                    
con.setRequestProperty("Range", "bytes=" + start + "-" + threadInfo.getEnd()); //設置讀取文件的位置,和結束位置
                    //end 初始化下載鏈接
                    //start 初始化下載到本地的文件
                    <span style="color:#ff0000;">accessFile  = new RandomAccessFile(new File(mTaskInfo.getFilePath(), mTaskInfo.getFileName()),"rwd");
                    accessFile.seek(start);    //設置開始寫入的位置</span>
                    //end 初始化下載到本地的文件

                   …………此處省略部分                      
  while((readLen = inputStream.read(buffer))!=-1) {
                          <span style="background-color: rgb(255, 255, 255);"><span style="color:#ff0000;">  accessFile.write(buffer, 0, readLen);</span></span>
//                            Log.i(tag, "readLen = " + readLen);
                            finished += readLen;
                            threadInfo.setFinished(finished);    //設置已經下載進度
                            if(System.currentTimeMillis() - time >2000) {
//                                Log.i(tag, "readLen = " + readLen);
                                notifyProgress(threadInfo.getId(), finished); //每隔2秒通知下載進度
                                time = System.currentTimeMillis();
                            }
                            //start 停止下載,保存進度
                            if(isPause) {
                                Log.i(tag,"pause name = "+mTaskInfo.getFileName());
                                notifyProgress(threadInfo.getId(), finished);        //通知下載進度
                                mThreadDao.updateThread(threadInfo.getUrl(),threadInfo.getId(),finished);  //更新數據庫對應的線程信息
                                return;
                            }
                            //end 停止下載,保存進度
                        }
                        //end 讀取輸入流寫入文件

                 …………此處省略部分                    }
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (ProtocolException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {

                         …………此處省略部分
                }
            super.run();
        }
    }
 

2.4保存斷點

在上面的DownloadThread下載線程中保存斷點信息,是使用數據庫形式保存的。

mThreadDao.updateThread(threadInfo.getUrl(),threadInfo.getId(),finished);  //更新數據庫對應的線程信息

3其他輔助類

(1) 數據庫操作類ThreadDAOImpl,斷點信息的增、改、查、刪。
(2) 回調接口OnDownload,下載進度以及下載完成
(3) 下載任務信息TaskInfo
(4) 線程信息ThreadInfo

4線程池

由於使用到多線程同時下載,這裏在使用了線程池管理。在DownloadService類中創建並初始化線程池,按照CPU核心數量乘以2再加1設置線程池中線程的數量
mThreadPool = Executors.newFixedThreadPool(getNumberOfCPUCores()*2+1);  //初始化線程
在下載時使用三個線程同時下載
DownloadTask task = new DownloadTask(DownloadService.this,info,mThreadPool,3); //建立下載任務,3個線程同時下載

由於線程池內的線程數量是有限的,當啟動下載之後有空閑的線程會馬上執行,如果沒有就只能等待下載任務完成再下載。

Android 多線程斷點續傳同時下載多個大文件