斷點(diǎn)續(xù)傳

1.原理

斷點(diǎn)續(xù)傳的理解可以分為兩部分:一部分是斷點(diǎn),一部分是續(xù)傳。斷點(diǎn)的由來是在下載的過程中,將一個(gè)下載文件分成了多個(gè)部分。同時(shí)進(jìn)行多個(gè)部分一起的下載,當(dāng)某個(gè)時(shí)間點(diǎn),任務(wù)被暫停了。此時(shí)下載暫停的位置就是斷點(diǎn)了。續(xù)傳就是一個(gè)未完成的任務(wù)再次開始時(shí),會從上次斷點(diǎn)繼續(xù)傳送。
使用多線程斷點(diǎn)續(xù)傳的時(shí)候,將下載后上傳任務(wù)(一個(gè)文件或者一個(gè)壓縮包)人為的劃分為幾個(gè)部分。每一部分采用一個(gè)線程進(jìn)行上傳或下載,多個(gè)線程并發(fā)可以占用服務(wù)器更多資源,從而加快下載速度。在下載(或上傳)過程中,如果網(wǎng)絡(luò)故障,點(diǎn)量不足等原因?qū)е孪螺d中斷,就需要用到斷點(diǎn)續(xù)傳功能。下次啟動時(shí),可以從記錄位置(已下載的部分)開始,繼續(xù)下載沒有下載的部分,避免重復(fù)部分的下載。斷點(diǎn)續(xù)傳的實(shí)質(zhì)就是能記錄上一次已下載完成的位置

斷點(diǎn)續(xù)傳的過程
1 斷點(diǎn)續(xù)傳需要在下載過程中記錄每條線程的下載進(jìn)度;
2 每次下載開始之前先讀取數(shù)據(jù)庫,查詢是否有未完成的記錄,有就繼續(xù)下載,沒有則創(chuàng)建記錄插入數(shù)據(jù)庫
3 在每次向文件中寫入數(shù)據(jù)之后,在數(shù)據(jù)庫中更新下載進(jìn)度
4 下載完成之后刪除數(shù)據(jù)庫中的進(jìn)度

2.代碼

package com.zhouzhuo.multitaskdownload;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.ProgressBar;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;

/**
 * Created by zhouzhuo on 2018/1/17.
 */

public class MainActivity extends Activity {
    private String path = "http://117.169.69.238/mp3.9ku.com/m4a/186947.m4a";
    private ProgressBar progressBar;
    private String downloadPath = Environment.getDownloadCacheDirectory()
            + File.separator+"download";
    private String targetFilePath="/";  //下載文件存放目錄
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            new MuchThreadDown(path, downloadPath, 3).downLoad();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public class MuchThreadDown {

        private String targetFilePath = "/";//下載文件存放目錄
        private int threadCount = 3;//線程數(shù)量
        private Map<Integer,ProgressBar> progressBarMap;//鍵 線程iD,值 進(jìn)度條控件
        private Handler handler;
        private String path;

        /**
         *
         * @param path 要下載文件的網(wǎng)絡(luò)路徑
         * @param targetFilePath 保存下載文件的目錄
         * @param threadCount 開啟的線程數(shù)量,默認(rèn)為3
         */
        public MuchThreadDown(String path,String targetFilePath,int threadCount){
            this.path = path;
            this.targetFilePath = targetFilePath;
            this.threadCount = threadCount;
        }

        public Map<Integer,ProgressBar> getProgressBarMap(){
            return progressBarMap;
        }

        public Handler getHandler(){
            return handler;
        }

        public void setHandler(Handler handler){
            this.handler = handler;
        }

        public void setProgressBarMap(Map<Integer,ProgressBar> progressBarMap){
            this.progressBarMap = progressBarMap;
        }

        //下載文件
        public void downLoad() throws Exception{
            //連接資源
            URL url = new URL(path);

            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(10000);

            int code = connection.getResponseCode();
            if(code == 200){
                //獲取資源大小
                int connectionLength  = connection.getContentLength();
                //在本地創(chuàng)建一個(gè)與資源文件同樣大小的文件來占位
                RandomAccessFile randomAccessFile = new RandomAccessFile(new File(targetFilePath,getFileName(url)),"rw");
                randomAccessFile.setLength(connectionLength);
                //將下載任務(wù)分配給每個(gè)線程
                //計(jì)算每個(gè)線程理論上下載的數(shù)量
                int blockSize = connectionLength/threadCount;
                //為每個(gè)線程分配任務(wù)
                for (int threadId=0;threadId<threadCount;threadId++){
                    //線程開始下載的位置
                    int startIndex = blockSize*threadId;
                    //線程結(jié)束下載的位置
                    int endIndex = blockSize*(threadId+1)-1;
                    //如果是最后一個(gè)線程,將剩下的下載任務(wù)全部交給這個(gè)線程
                    if(threadId == threadCount -1){
                        endIndex = connectionLength - 1;
                    }

                    //TODO
                    if(progressBarMap !=null){
                        new DownloadThread(threadId,startIndex,endIndex,progressBarMap.get(threadId),handler);
                    }else {
                        new DownloadThread(threadId,startIndex,endIndex,null,handler).start();
                    }

                }
            }
        }


    }

    private class DownloadThread extends Thread{

        private int threadId;
        private int startIndex;
        private int endIndex;
        private ProgressBar progressBar;
        private int currentThreadTotal;//當(dāng)前線程下載文件的總大小
        private Handler handler;

        public DownloadThread(int threadId,
                              int startIndex,
                              int endIndex,
                              ProgressBar progressBar,
                              Handler handler){
            this.threadId = threadId;
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.currentThreadTotal = endIndex -startIndex+1;
            this.progressBar = progressBar;
            this.handler = handler;
        }

        @Override
        public void run() {
            super.run();
            Log.d("zhouzhuo","線程"+threadId+"開始下載");
            //分段請求網(wǎng)絡(luò),分段將文件保存到本地
            try {
                URL url = new URL(path);
                //加載下載文件的位置
                File downThreadFile = new File(targetFilePath,getFileName(url)+"_downThread_"+threadId+".dt");
                RandomAccessFile downThreadStream ;
                if(downThreadFile.exists()){//
                    downThreadStream = new RandomAccessFile(downThreadFile,"rwd");
                    String startIndex_str = downThreadStream.readLine();
                    //設(shè)置下載起點(diǎn)
                    this.startIndex = Integer.parseInt(startIndex_str);
                }else {
                    downThreadStream = new RandomAccessFile(downThreadFile,"rwd");
                }

                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");
                connection.setConnectTimeout(10000);
                //設(shè)置分段下載的頭信息。Range:做分段數(shù)據(jù)請求用的。格式:Range types = 0-1024  或者 bytes:0-1024
                connection.setRequestProperty("Range","bytes="+startIndex+"-"+endIndex);
                Log.d("zhouzhuo","線程_"+threadId+"的下載起點(diǎn)是:"+startIndex+" 下載終點(diǎn)是:"+endIndex);
                if(connection.getResponseCode() == 206){
                    //200:請求全部資源成功
                    //206: 代表部分資源請求成功
                    //獲取流
                    InputStream inputStream = connection.getInputStream();
                    //獲取前面已創(chuàng)建的文件
                    RandomAccessFile randomAccessFile =new RandomAccessFile(
                            new File(targetFilePath,getFileName(url)),"rw"
                    );
                    //文件寫入的開始位置
                    randomAccessFile.seek(startIndex);

                    //將網(wǎng)絡(luò)流中的文件寫入本地
                    byte[] buffer = new byte[1024*100];
                    int length = -1;
                    //記錄本次下載文件的大小
                    int total = 0;
                    boolean flag = false;
                    if(progressBar !=null){
                        flag = true;
                        progressBar.setMax(currentThreadTotal);
                    }
                    while ((length = inputStream.read(buffer))>0){
                        randomAccessFile.write(buffer,0,length);
                        total += length;
                        //將當(dāng)前下載到位置保持到文件中
                        int currentThreadPosition = startIndex +total;//當(dāng)前文件下載位置
                        downThreadStream.seek(0);
                        downThreadStream.write(((currentThreadPosition)+"").getBytes("UTF-8"));
                        //設(shè)置進(jìn)度條
                        if(flag){
                            progressBar.setProgress(currentThreadTotal-(endIndex-currentThreadPosition));
                        }
                    }
                    if(handler !=null){
                        Message message = Message.obtain();
                        message.what = 66;
                        message.obj = 1;
                        handler.sendMessage(message);
                    }
                    downThreadStream.close();
                    inputStream.close();
                    randomAccessFile.close();
                    //刪除臨時(shí)文件
                    cleanTemp(downThreadFile);

                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //獲取下載文件的名稱
    private String getFileName(URL url) {
        String filename = url.getFile();
        return  filename.substring(filename.lastIndexOf("/")+1);
    }

    //刪除線程產(chǎn)生的臨時(shí)文件
    private synchronized void cleanTemp(File file){
        file.delete();
    }
}

3.代碼分析

首先根據(jù)下載的地址獲取連接流,得到下載文件的大小。然后根據(jù)下載線程的個(gè)數(shù)把文件拆分成若干斷,交給各個(gè)線程去下載。
每一個(gè)下載線程里面都有下載的起始和結(jié)束位置。
通過connection.setRequestProperty("Range","bytes="+startIndex+"-"+endIndex);設(shè)置分段請求。然后通過RandomAccessFile seek移動位置,然后在相應(yīng)位置RandomAccessFileWrite()寫入文件。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容