多线程下载文件
来源:互联网 发布:信用卡淘宝退款 编辑:程序博客网 时间: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 创建和管理。用线程池的好处如下:
- 在同事并发执行线程时,提高API的性能
- 减少线程的重复开启、销毁
- 减少内存消耗,控制线程数量
线程池,共有四种,分别如下: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 如下:
- 多线程技术下载文件
- 多线程下载文件
- 文件的多线程下载
- 使用多线程下载文件
- Android 多线程文件下载
- 多线程文件下载
- 实现多线程下载文件
- QT:多线程下载文件
- Java多线程下载文件
- java多线程下载文件
- c# 多线程下载文件
- 多线程下载文件
- c# 多线程下载文件
- Java多线程下载文件
- 多线程断点下载文件
- Java多线程下载文件
- Java多线程下载文件
- 多线程下载文件
- C语言测试
- Android服务端API仿真
- 基于windows服务监控tomcat服务,防止tomcat死掉
- C++ template高级
- Java中的时间
- 多线程下载文件
- Linux系统中添加库文件路径的方法
- Android:学习AIDL,这一篇文章就够了(下)
- HDU2187悼念512汶川大地震遇难同胞
- 双重检查锁定与延迟初始化
- oracle中flashback_on_standby详细介绍
- iOS检测耳机插入/拔出
- springMVC框架的文件上传
- JS与WebView交互存在的一些问题