多线程下载文件
2017-02-23 16:44
148 查看
作者:夏至 转载请保留这段申明
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;
图片摘自网络,如下:
理解了原理,我们来实现一个小demo,下载一个南方周末的apk,连为:http://images.infzm.com/mobile/infzmreader.apk
首先,先想一下,我们需要传递给线程下载的参数有哪些,无非就是 url,apk下载的路径,文件名字,和线程个数,当然可以根据需求,加你想要的参数。
首先权限先加:
这里,我用到了Serializable 这个属性来传递参数,毕竟我们是通过启动service 来启动线程的,用这个传递可以少去代码臃肿。然后在 onclick 那里,启动我们的服务:
接着,我们在onStartCommand 这里获取我们的数据,然后开启我们的线程。
其中 startdownload 如下:
downloadManager 是我们下载的实现方法,代码如下:
其中,DownloadTask 为我们的下载类,代码如下:
代码,没啥好解释的,跟一般的文件下载差不多,只不过这里是分块的形式,所以,random 属性那里,start 和end 要做一下判断:
这个时候,线程池的作用就出来了,顾名思义,线程池,就是线程组织的一个池塘,它由 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 就行了。如:
log 如下:
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 如下:
相关文章推荐
- 使文件下载的自定义连接支持 FlashGet 的断点续传多线程链接下载! C#/ASP.Net 实现
- 使文件下载的自定义连接支持 FlashGet 的断点续传多线程链接下载! C#/ASP.Net 实现!
- 多线程idhttp下载文件源代码
- 用java编写多线程ftp断点下载文件程序
- 使文件下载的自定义连接支持 FlashGet 的断点续传多线程链接下载! JSP/Servlet 实现!
- 使文件下载的自定义连接支持 FlashGet 的断点续传多线程链接下载! C#/ASP.Net 实现!
- 正在加载中 图片大全,【绝对是你做软件急需的,多线程,文件处理,下载】
- 基于select I/O模型的远程目录浏览与多线程文件下载
- 使用 CInternetSession 封装多线程 http 文件下载
- 如何用多线程下载文件
- Asp.net 2.0 文件下载[支持多线程, 断点续传功能](示例代码下载)
- Asp.net 2.0 文件下载[支持多线程, 断点续传功能](示例代码下载)
- 使用 CInternetSession 封装多线程 http 文件下载
- 使文件下载的自定义连接支持 FlashGet 的断点续传多线程链接下载! JSP/Servlet 实现!
- Asp.net 2.0 文件下载[支持多线程, 断点续传功能](示例代码下载)
- 使文件下载的自定义连接支持 FlashGet 的断点续传多线程链接下载! C#/ASP.Net 实现! 转
- Asp.net 2.0 文件下载[支持多线程, 断点续传功能](示例代码下载)
- [导入]Asp.net 2.0 文件下载[支持多线程, 断点续传功能](示例代码下载)
- 以多线程、断点续传方式下载文件的实现
- C# HttpWebRequest可断点上传,下载文件;SOCKET多线程多管道可断点传送大文件