Android 中实现多线程下载和断点续传的原理和代码

在上一章,用Java代码实现了 多线程下载和断点续传, 现在我们将它移植到 Android 上面.

下面这个类能帮助我们实现多线程下载和断点续传功能.

Android 中实现多线程下载和断点续传的原理和代码_第1张图片
介绍一下这个类的使用方法.

快速下载
1. 该类的构造方法需要提供三个参数.
2. public MuchThreadDown(String path, String targetFilePath, int threadCount)
- 参数一 : 要下载的资源网络路径.
- 参数二 : 资源保存在手机上的路径
- 参数三 : 开启的线程个数.默认为 3.
3. 创建类的实例MuchThreadDown threadDown = new MuchThreadDown(url_str, path, progress);
4. 调用类的 download() 方法.
5. 文件会被下载到指定的目录.

下载的过程中更新进度条

  1. 如果在下载的时候需要在页面展示进度条.
  2. 为每一个线程创建一个进度条控件,封装到Map map中的key代表线程的id,比如有当前开了三个线程, 线程的id就是0, 1, 2 进度条就需要我们动态的去创建了,将创建的进度条封装到map中.threadDown.setProgressBar_map(progressBar_map); 设置给 MuchThreadDown 的对象.

获取线程的下载结果
1. 将handler对象设置给MuchThreadDown 的对象.threadDown.setHandler(handler); 当一个线程下载完成的时候,返回 1 .
2. Message中封装的数据如下
3. `Message msg = Message.obtain();

  msg.what = 66;
   msg.obj = 1;
   handler.sendMessage(msg);`
package com.yb.muchthreaddown.util;

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

import android.os.Handler;
import android.os.Message;
import android.widget.ProgressBar;

/**
 * 多线程下载 和 断点续传 for Android
 * @author 杨斌.
 *
 */
public class MuchThreadDown {

//  private String path = "http://mpge.5nd.com/2016/2016-11-15/74847/1.mp3";    //下载路径
    private String path = "http://117.169.69.238/mp3.9ku.com/m4a/186947.m4a";
    private String targetFilePath="/";  //下载文件存放目录
    private int threadCount = 3;    //线程数量
    public Map progressBar_map;//键 线程id, 值 进度条控件
    private Handler handler;
    /**
     * 构造方法 
     * @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 getProgressBar_map() {
        return progressBar_map;
    }   

    public Handler getHandler() {
        return handler;
    }

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

    public void setProgressBar_map(Map progressBar_map) {
        this.progressBar_map = progressBar_map;
    }


    /**
     * 下载文件
     */
    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();
            System.out.println(connectionLength);
            //在本地创建一个与资源同样大小的文件来占位
            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 = threadId * blockSize; //线程开始下载的位置
                int endIndex = (threadId+1) * blockSize -1; //线程结束下载的位置
                if(threadId == (threadCount - 1)){  //如果是最后一个线程,将剩下的文件全部交给这个线程完成
                    endIndex = connectionLength - 1;
                }

                if(progressBar_map != null){
                    new DownloadThread(threadId, startIndex, endIndex, progressBar_map.get(threadId),handler).start();//开启线程下载
                }else{
                    new DownloadThread(threadId, startIndex, endIndex, null,handler).start();//开启线程下载
                }


            }
//          randomAccessFile.close();
        }

    }

    //下载的线程
    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() {
            System.out.println("线程"+ threadId + "开始下载");
            try {
                //分段请求网络连接,分段将文件保存到本地.
                URL url = new URL(path);

                //加载下载位置的文件
                File downThreadFile = new File(targetFilePath, getFileName(url) +"_downThread_" + threadId+".dt");
                RandomAccessFile downThreadStream = null;
                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 bytes=0-1024  或者 bytes:0-1024
                connection.setRequestProperty("Range", "bytes="+ startIndex + "-" + endIndex);

                System.out.println("线程_"+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 msg = Message.obtain();
                        msg.what = 66;
                        msg.obj = 1;
                        handler.sendMessage(msg);
                    }
                    downThreadStream.close();
                    inputStream.close();
                    randomAccessFile.close();                   
                    cleanTemp(downThreadFile);//删除临时文件
                    System.out.println("线程"+ threadId + "下载完毕");
                }else{
                    System.out.println("响应码是" +connection.getResponseCode() + ". 服务器不支持多线程下载");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

    //删除线程产生的临时文件
    private synchronized void cleanTemp(File file){
        file.delete();
    }

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

    public static void main(String[] args) {
        try {
            new MuchThreadDown(null, null, 3).download();

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

}

你可能感兴趣的:(技术)