多线程下载文件

来源:互联网 发布:信用卡淘宝退款 编辑:程序博客网 时间:2024/06/05 14:18

作者:夏至 转载请保留这段申明
http://blog.csdn.net/u011418943/article/details/56675652

项目下载地址:
https://git.oschina.net/zhengshaorui/MtlThreadDownloadFile.git

上一章,我们实现了单线程的下载,这一章,我们实现一个简单的多线程下载,不先加断线续传,下一章再加断点续传。
上一章连接:http://blog.csdn.net/u011418943/article/details/56674086

首先,文件在下载中,我们常用的是用单线程下载,这样的好处在于好控制,能够监控这个文件的下载进度等等。缺点在于,没有完全利用cpu的利用率,而且如果是大文件,下载的速度较慢。所以,我们可以通过多线程的方式,去下载文件。
实现原理是什么呢?就是把一个文件给切分几块来下载。比如一个11M的文件,我们把它分成5个部分来下载;那么它的计算公式就为
blocksize = 11%5 == 0? 11/5:11%5+1;

//每一个线程要下载的大小 blocksize = filesize%threadcount == 0? filesize/threadcount : filesize/threadcount+1

图片摘自网络,如下:
这里写图片描述

理解了原理,我们来实现一个小demo,下载一个南方周末的apk,连为:http://images.infzm.com/mobile/infzmreader.apk
首先,先想一下,我们需要传递给线程下载的参数有哪些,无非就是 url,apk下载的路径,文件名字,和线程个数,当然可以根据需求,加你想要的参数。
首先权限先加:

<!--获取网络权限-->    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />    <uses-permission android:name="android.permission.INTERNET"/>    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>    <!--在SDCard中创建与删除文件权限  -->    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>    <!-- 往SDCard写入数据权限 -->    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>    <!--  从SDCard读取数据权限 -->    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
再写我们的实体类,跟上一章一样
public class FilesInfo implements Serializable {    private String fileUrl;    private String fileName;    private String fileDir;    private int threadcount;    public FilesInfo(String fileUrl, int threadcount, String fileDir, String fileName) {        this.fileUrl = fileUrl;        this.threadcount = threadcount;        this.fileDir = fileDir;        this.fileName = fileName;    }    public int getThreadcount() {        return threadcount;    }    public void setThreadcount(int threadcount) {        this.threadcount = threadcount;    }    public int getPriority() {        return priority;    }    public String getFileUrl() {        return fileUrl;    }    public void setFileUrl(String fileUrl) {        this.fileUrl = fileUrl;    }    public String getFileName() {        return fileName;    }    public void setFileName(String fileName) {        this.fileName = fileName;    }    public String getFileDir() {        return fileDir;    }    public void setFileDir(String fileDir) {        this.fileDir = fileDir;    }}

这里,我用到了Serializable 这个属性来传递参数,毕竟我们是通过启动service 来启动线程的,用这个传递可以少去代码臃肿。然后在 onclick 那里,启动我们的服务:

public void download(View view){       FilesInfo filesInfo = new FilesInfo(apkUrl,5,fileDir,"南方周末");        Intent downloadservice = new Intent(MainActivity.this,DownloadManager.class);        downloadservice.putExtra("fileinfo",filesInfo);        startService(downloadservice);    }

接着,我们在onStartCommand 这里获取我们的数据,然后开启我们的线程。

public int onStartCommand(Intent intent, int flags, int startId) {        FilesInfo mFilesInfo = (FilesInfo) intent.getSerializableExtra("fileinfo");        startdownload(mFilesInfo.getFileUrl(),mFilesInfo.getFileDir(),mFilesInfo.getFileName(),                mFilesInfo.getThreadcount(),mFilesInfo.getPriority());        return super.onStartCommand(intent, flags, startId);    }

其中 startdownload 如下:

private void startdownload(String apkUrl, String fileDir, String filename, int threadcount            , int priority) {        File dir  = new File(fileDir);        if (!dir.exists()){            dir.mkdir();        }        String filepath =  filename;            downloadManager downloadManager = new    downloadManager(apkUrl,dir,filepath,threadcount,priority);        downloadManger.start();    }

downloadManager 是我们下载的实现方法,代码如下:

class downloadManager extends  Thread{        String apkurl,filepath;        int threadcount,priority;        int blocksize; //每个线程需要下载的区域        File dir;        public downloadManager(String apkUrl,File dir, String filepath, int threadcount, int priority) {            this.apkurl = apkUrl;            this.filepath = filepath;            this.threadcount = threadcount;            this.priority = priority;            this.dir = dir;        }        @Override        public void run() {            HttpURLConnection con = null;            try {                DownloadTask[]  mDownloadTasks = new DownloadTask[threadcount];                URL url = new URL(this.apkurl);                con = (HttpURLConnection) url.openConnection();                con.setRequestMethod("GET");                con.setReadTimeout(5000);                con.setConnectTimeout(5000);                int filesize = con.getContentLength(); //获取文件总长度                if (filesize <= 0){                    Log.d(TAG, "文件获取失败");                    return;                }                //每一个线程要下载的大小                blocksize = filesize%threadcount == 0? filesize/threadcount : filesize/threadcount+1;                Log.d(TAG, "filesize: "+filesize+" blocksize: "+blocksize);                File file = new File(this.filepath);                for (int i = 0; i < mDownloadTasks.length; i++) { //根据线程数,开启线程                    mDownloadTasks[i] = new DownloadTask(url,dir,filepath,blocksize,                            i,threadcount,filesize);                      mDownloadTasks[i].start();                }                boolean isfinished = false;                int filelength = 0;                while(!isfinished){  //这里用来判断是否下载完成,不加去掉也行                    isfinished = true;                    filelength = 0;                    for (int i = 0; i < mDownloadTasks.length; i++) {                        if (!mDownloadTasks[i].isCompleted()){                            isfinished = false;                        }else{                            filelength += mDownloadTasks[i].getDownloadLength();                        }                    }                }                Log.d(TAG, "finished: "+filelength+" "+filesize);            } catch (Exception e) {                e.printStackTrace();            }            super.run();        }    }

其中,DownloadTask 为我们的下载类,代码如下:

public class DownloadTask extends Thread{    private static final String TAG = "zsr";    public DownloadTask(){    }    private boolean isCompleted = false;    private URL connectUrl;    private File dir;    private int blocksize,threadid;    private int downloadLength = 0;    private int threadcount;    private int filelength;    private String filename;    public DownloadTask(URL connectUrl, File dir, String filename,int blocksize, int threadid,                        int threadcount,int filelength){        this.connectUrl = connectUrl;        this.dir = dir;        this.blocksize = blocksize;        this.threadid = threadid;        this.threadcount = threadcount;        this.filelength = filelength;        this.filename = filename;    }    @Override    public void run() {        RandomAccessFile raf = null;        BufferedInputStream bis = null;        HttpURLConnection con = null;        try {            con = (HttpURLConnection) connectUrl.openConnection();            con.setRequestMethod("GET");            con.setReadTimeout(5000);            con.setConnectTimeout(5000);            int startpos = blocksize * threadid; //开始位置            int endpos = blocksize * (threadid+1) -1; //结束位置            if (threadid == (threadcount -1)) endpos = filelength;            //设置下载位置            con.setRequestProperty("Range", "bytes="+startpos+"-"+endpos);            Log.d(TAG, "range: "+startpos+" "+endpos);            //设置文件的写入位置            File file = new File(dir,filename);            raf = new RandomAccessFile(file,"rwd");            raf.seek(startpos);            //读数据            bis = new BufferedInputStream(con.getInputStream());            byte[] bytes = new byte[1024*4];            int len = -1;            while( (len = bis.read(bytes)) !=-1 ){                raf.write(bytes,0,len);                downloadLength += len;            }            isCompleted = true;            Log.d(TAG, "finish: "+downloadLength);        } catch (IOException e) {            e.printStackTrace();        }finally {            if (bis != null) try {                bis.close();                if (raf != null) raf.close();             } catch (IOException e) {                e.printStackTrace();            }            if (con != null) con.disconnect();        }        super.run();    }    public boolean isCompleted() {        return isCompleted;    }    public int getDownloadLength(){        return downloadLength;    }}

代码,没啥好解释的,跟一般的文件下载差不多,只不过这里是分块的形式,所以,random 属性那里,start 和end 要做一下判断:

    int startpos = blocksize * threadid; //开始位置            int endpos = blocksize * (threadid+1) -1; //结束位置            if (threadid == (threadcount -1)) endpos = filelength; //最后一个结尾,以文件的长度结尾

这里写图片描述

2、用线程池管理

在上面中,我们都是直接 start() 启动我们的线程,在多个线程的线程的时候,我们这种做法是很不对的,因为线程的开启和销毁,是需要时间的,这个时候,线程就会在内存一直占用着,这样的后果是你的内存一直在上升;假如你把这个回收了,过一会又要启动了,这个启动和销毁也是耗资源的。
这个时候,线程池的作用就出来了,顾名思义,线程池,就是线程组织的一个池塘,它由 Excutors 创建和管理。用线程池的好处如下:

  1. 在同事并发执行线程时,提高API的性能
  2. 减少线程的重复开启、销毁
  3. 减少内存消耗,控制线程数量

线程池,共有四种,分别如下:newCachedThreadPool() :

  • cacheThreadPool 缓存型池子,先查看池中有没有以前建立的线程,如果有,就reuse.如果没有,就建一个新的线程加入池中。能reuse的线程,必须是timeout IDLE内的池中线程,缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。缓存型池子通常用于执行一些生存期很短的异步型任务 。
  • newFixedThreadPool() fixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程 其独特之处:任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子。和cacheThreadPool不同:fixedThreadPool池线程数固定,但是0秒IDLE(无IDLE)。这也就意味着创建的线程会一直存在。所以fixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器。
  • newScheduledThreadPool() 调度型线程池。这个池子里的线程可以按schedule依次delay执行,或周期执行 。0秒IDLE(无IDLE)。
  • SingleThreadExecutor 单例线程,任意时间池中只能有一个线程 。用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE(无IDLE)。

由于,我们是用到一个下载器的作用,所以,我们这里采用 newFixedThreadPool 固定并发的线程池,那么怎么用?很简单,把线程的start() 方法,用 线程池的 excute 就行了。如:

private ExecutorService mExecutorService = Executors.newFixedThreadPool(5);//固定线程并发数量为5
// mDownloadTasks[i].start(); mExecutorService.execute(mDownloadTasks[i]);

log 如下:
这里写图片描述

0 0
原创粉丝点击