zoukankan      html  css  js  c++  java
  • Android 多线程断点续传同时下载多个大文件

    最近学习在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个线程同时下载

    由于线程池内的线程数量是有限的,当启动下载之后有空闲的线程会马上执行,如果没有就只能等待下载任务完成再下载。

  • 相关阅读:
    Serialize and Deserialize Binary Tree
    sliding window substring problem汇总贴
    10. Regular Expression Matching
    《深入理解计算机系统》(CSAPP)读书笔记 —— 第七章 链接
    程序员如何写一份合格的简历?(附简历模版)
    9个提高代码运行效率的小技巧你知道几个?
    《深入理解计算机系统》(CSAPP)读书笔记 —— 第六章 存储器层次结构
    24张图7000字详解计算机中的高速缓存
    《深入理解计算机系统》(CSAPP)实验四 —— Attack Lab
    《深入理解计算机系统》(CSAPP)读书笔记 —— 第五章 优化程序性能
  • 原文地址:https://www.cnblogs.com/zhujiabin/p/7143724.html
Copyright © 2011-2022 走看看