美文网首页
断点续传

断点续传

作者: zhouzhuo933 | 来源:发表于2018-01-17 14:30 被阅读0次

    1.原理

    断点续传的理解可以分为两部分:一部分是断点,一部分是续传。断点的由来是在下载的过程中,将一个下载文件分成了多个部分。同时进行多个部分一起的下载,当某个时间点,任务被暂停了。此时下载暂停的位置就是断点了。续传就是一个未完成的任务再次开始时,会从上次断点继续传送。
    使用多线程断点续传的时候,将下载后上传任务(一个文件或者一个压缩包)人为的划分为几个部分。每一部分采用一个线程进行上传或下载,多个线程并发可以占用服务器更多资源,从而加快下载速度。在下载(或上传)过程中,如果网络故障,点量不足等原因导致下载中断,就需要用到断点续传功能。下次启动时,可以从记录位置(已下载的部分)开始,继续下载没有下载的部分,避免重复部分的下载。断点续传的实质就是能记录上一次已下载完成的位置

    断点续传的过程
    1 断点续传需要在下载过程中记录每条线程的下载进度;
    2 每次下载开始之前先读取数据库,查询是否有未完成的记录,有就继续下载,没有则创建记录插入数据库
    3 在每次向文件中写入数据之后,在数据库中更新下载进度
    4 下载完成之后删除数据库中的进度

    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;//线程数量
            private Map<Integer,ProgressBar> progressBarMap;//键 线程iD,值 进度条控件
            private Handler handler;
            private String path;
    
            /**
             *
             * @param path 要下载文件的网络路径
             * @param targetFilePath 保存下载文件的目录
             * @param threadCount 开启的线程数量,默认为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();
                    //在本地创建一个与资源文件同样大小的文件来占位
                    RandomAccessFile randomAccessFile = new RandomAccessFile(new File(targetFilePath,getFileName(url)),"rw");
                    randomAccessFile.setLength(connectionLength);
                    //将下载任务分配给每个线程
                    //计算每个线程理论上下载的数量
                    int blockSize = connectionLength/threadCount;
                    //为每个线程分配任务
                    for (int threadId=0;threadId<threadCount;threadId++){
                        //线程开始下载的位置
                        int startIndex = blockSize*threadId;
                        //线程结束下载的位置
                        int endIndex = blockSize*(threadId+1)-1;
                        //如果是最后一个线程,将剩下的下载任务全部交给这个线程
                        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;//当前线程下载文件的总大小
            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+"开始下载");
                //分段请求网络,分段将文件保存到本地
                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();
                        //设置下载起点
                        this.startIndex = Integer.parseInt(startIndex_str);
                    }else {
                        downThreadStream = new RandomAccessFile(downThreadFile,"rwd");
                    }
    
                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("GET");
                    connection.setConnectTimeout(10000);
                    //设置分段下载的头信息。Range:做分段数据请求用的。格式:Range types = 0-1024  或者 bytes:0-1024
                    connection.setRequestProperty("Range","bytes="+startIndex+"-"+endIndex);
                    Log.d("zhouzhuo","线程_"+threadId+"的下载起点是:"+startIndex+" 下载终点是:"+endIndex);
                    if(connection.getResponseCode() == 206){
                        //200:请求全部资源成功
                        //206: 代表部分资源请求成功
                        //获取流
                        InputStream inputStream = connection.getInputStream();
                        //获取前面已创建的文件
                        RandomAccessFile randomAccessFile =new RandomAccessFile(
                                new File(targetFilePath,getFileName(url)),"rw"
                        );
                        //文件写入的开始位置
                        randomAccessFile.seek(startIndex);
    
                        //将网络流中的文件写入本地
                        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;
                            //将当前下载到位置保持到文件中
                            int currentThreadPosition = startIndex +total;//当前文件下载位置
                            downThreadStream.seek(0);
                            downThreadStream.write(((currentThreadPosition)+"").getBytes("UTF-8"));
                            //设置进度条
                            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();
                        //删除临时文件
                        cleanTemp(downThreadFile);
    
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        //获取下载文件的名称
        private String getFileName(URL url) {
            String filename = url.getFile();
            return  filename.substring(filename.lastIndexOf("/")+1);
        }
    
        //删除线程产生的临时文件
        private synchronized void cleanTemp(File file){
            file.delete();
        }
    }
    
    

    3.代码分析

    首先根据下载的地址获取连接流,得到下载文件的大小。然后根据下载线程的个数把文件拆分成若干断,交给各个线程去下载。
    每一个下载线程里面都有下载的起始和结束位置。
    通过connection.setRequestProperty("Range","bytes="+startIndex+"-"+endIndex);设置分段请求。然后通过RandomAccessFile seek移动位置,然后在相应位置RandomAccessFileWrite()写入文件。

    相关文章

      网友评论

          本文标题:断点续传

          本文链接:https://www.haomeiwen.com/subject/rdfuoxtx.html