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个线程同时下载

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

  • 相关阅读:
    数据库事务隔离级别
    impala jdbc4的group by语句的bug,加上limit没错
    火狐不支持innerText属性,只支持innerHTML属性
    struts2.x + Tiles2.x读取多个xml 配置文件
    ids for this class must be manually assigned before calling save():Xxx
    整合ssh model $$_javassist_13 cannot be cast to javassist.util.proxy.Proxy
    火狐点击链接请求两次的问题
    C++——类和动态内存分配
    C++——使用类
    C++——对象和类
  • 原文地址:https://www.cnblogs.com/zhujiabin/p/7143724.html
Copyright © 2011-2022 走看看