您的位置:首页 > 其它

多线程下载文件

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;

//每一个线程要下载的大小
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 如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐