http多线程断点续传

时间:2021-12-25 08:23:12

单线程下载HTTP文件是一件非常简单的事。那么,多线程断点需要什么功能?

 1.多线程下载;

 2.支持断点;

 

思路:第一次下载某个文件的时候,创建一个下载线程开始从网络下载文件,把已经下载的部分保存到磁盘文件,当用户通过UI主动暂停下载的时候,把线程停止销毁。

          下次下载同一个url的文件的时候,重新创建一个下载线程开始从网络下载文件,如果发现磁盘上保存过该文件,则通过  HttpURLConnection.setRequestProperty("Range", "bytes=本地缓存的文件的字节数-完整文件的大小");

          从上次中断的地方请求继续下载。

 

一、多线程

使用多线程的好处:使用多线程下载会提升文件下载的速度。那么多线程下载文件的过程是: 

  (1)首先获得下载文件的长度,然后设置本地文件的长度。

       HttpURLConnection.getContentLength();//获取下载文件的长度

       RandomAccessFile file = new RandomAccessFile("QQWubiSetup.exe","rwd");

       file.setLength(filesize);//设置本地文件的长度

 

   (2)根据文件长度和线程数计算每条线程下载的数据长度和下载位置。

      如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M,每条线程开始下载的位置如下图所示。

   http多线程断点续传

 

 

   (3)使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,

       比如要下载的文件是1000个字节(编号0-999),分两次下载

       HttpURLConnection.setRequestProperty("Range", "bytes=0-499");  下载文件的前500个字节,0代表第一个字节。

       HttpURLConnection.setRequestProperty("Range", "bytes=500-999");   

       

 

   (4)保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。

       RandomAccessFile threadfile = new RandomAccessFile("QQWubiSetup.exe ","rwd");

       threadfile.seek(2097152);//从文件的什么位置开始写入数据

          

 二、断点续传

写了一个断点续传的组件

public class SuspendableDownloader {    private  static  final String SDPATH = "//sdcard//";    private  static  final String TAG  = "SuspendableDownloader";    public boolean isStopDownload = false;    public void startDownload() {        isStopDownload = false;    }    public void stopDownload(){        isStopDownload = true;    }     public interface CallBack{        public boolean notfiyProgress(int percent);        public void onDownLoadCancel();    }    private   CallBack mCallBack = null;    public  void setCallBack(CallBack callback){        this.mCallBack = callback;    }     public  String downLoadFile(String httpUrl) throws IOException {        File tmpFile = new File(SDPATH);        if (!tmpFile.exists()) {            tmpFile.mkdir();        }        String fileName = httpUrl.split("/")[httpUrl.split("/").length-1];        final RandomAccessFile file = new RandomAccessFile(SDPATH + fileName,"rwd");        //第一次下载        if (file.length() == 0) {            URL url = new URL(httpUrl);            HttpURLConnection conn = (HttpURLConnection) url.openConnection();            int  length = conn.getContentLength();            InputStream is = conn.getInputStream();            //FileOutputStream fos = new FileOutputStream(file);            byte[] buf = new byte[256*2];            conn.connect();            double count = 0;            if (conn.getResponseCode() >= 400) {                 Log.i(TAG,"time exceed");            else {                while (count <= 100 && !isStopDownload) {                    if (is != null) {                        int numRead = is.read(buf);                         if (numRead <= 0) {                            break;                        else {                            file.write(buf, 0, numRead);                            mCallBack.notfiyProgress((int)(file.length()*100/length));                            Log.d(TAG,"notifyprogress="+(int)(file.length()*100/length));                        }                    else {                        break;                    }                }                if (isStopDownload) {                    mCallBack.onDownLoadCancel();                }            }            conn.disconnect();            file.close();            is.close();        }else {            //不是第一次下载,之前有过下载            Log.d(TAG,"continue file.length() ="+file.length()  );        //    file.seek(file.length());            URL url = new URL(httpUrl);            HttpURLConnection conn = (HttpURLConnection) url.openConnection();            int length = conn.getContentLength();             HttpURLConnection conn2 = (HttpURLConnection) url.openConnection();            conn2.setRequestProperty("Range""bytes="+file.length()+"-"+(length-1)); //如果文件已经下载完成,即从 1234-1234 会报告FileNotFound Exception            //java.lang.IllegalStateException: Cannot set request property after connection is made          //  int length = conn.getContentLength();http://www.eoeandroid.com/thread-154241-1-1.html            if (file.length() == length) {                 return SDPATH + fileName;            }            // file.setLength(length);            InputStream is = conn.getInputStream();            //FileOutputStream fos = new FileOutputStream(file);            byte[] buf = new byte[256*2];            conn.connect();            double count = 0;            if (conn.getResponseCode() >= 400) {                 Log.i(TAG,"time exceed");            else {                while (count <= 100 && !isStopDownload) {                    if (is != null) {                        int numRead = is.read(buf);                        if (numRead <= 0) {                            break;                        else {                            file.write(buf, 0, numRead);                            mCallBack.notfiyProgress((int)(file.length()*100/length));                            Log.d(TAG,"notifyprogress="+(int)(file.length()*100/length));                        }                    else {                        break;                    }                }                if (isStopDownload) {                    mCallBack.onDownLoadCancel();                }            }            conn.disconnect();            file.close();            is.close();         }        return SDPATH + fileName;    }}

 

注意一些地方:

       1.错误 java.lang.IllegalStateException: Cannot set request property after connection is made

           由于要通过HttpURLConnection  getContentLength获取下载文件的长度,之后才能设置Range请求头,但是getContentLength已经与server建立了connection,导致了这个错误。解决方法就是通过两个HttpURLConnection实例,第一次获取下载文件长度,第二次获取文件

      2. Thread.stop 已经废弃,建议使用调用interrupt , 但是有些操作是不可打断的。包括进入synchronized段以及Lock.lock(),inputSteam.read()等。解决方法是同一个变量来控制isStopDownload来控制下载线程的退出。