多线程下载网上有很多的例子,其中需要注意的就是:
每个线程该分配多大的算法;
通过请求从返回的 getContentLength() 方法获取需要下载的文件大小。
使用 RandomAccessFile 类来创建文件夹。因为该类可以从文件的任何位置开始读写操作,有 seek() 方法。
本例是用 Tomcat 充当后台来下载的。
下载成功后的示意图:
具体代码如下:
MultiThreadDownload.java :
package com.crazy.multidownload;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
public class MultiThreadDownload {
/* 需要下载资源的地址*/
private String urlStr;
/* 下载的文件*/
private File localFile;
/* 需要下载文件的存放的本地文件夹路径*/
private String dirStr;
/* 存储到本地的文件名*/
private String filename;
/* 开启的线程数量*/
private int threadCount;
/* 下载文件的大小*/
private long fileSize;
private Handler handler;
public MultiThreadDownload(String urlStr, String dirStr,
String filename, int threadCount, Handler handler) {
this.urlStr = urlStr;
this.dirStr = dirStr;
this.filename = filename;
this.threadCount = threadCount;
this.handler = handler;
}
public void download() throws IOException {
createFileByUrl();
/* 计算每个线程需要下载的数据长度*/
long block = fileSize % threadCount == 0 ? fileSize / threadCount
: fileSize / threadCount + 1;
for (int i = 0; i < threadCount; i++) {
long start = i * block;
long end = start + block >= fileSize ? fileSize : start + block - 1;
new DownloadThread(new URL(urlStr), localFile, start, end).start();
}
}
/**
* 根据资源的URL获取资源的大小,以及在本地创建文件
*/
public void createFileByUrl() throws IOException {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(15 * 1000);
conn.setRequestMethod("GET");
conn.setRequestProperty(
"Accept",
"image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, " +
"application/xaml+xml, application/vnd.ms-xpsdocument, " +
"application/x-ms-xbap, application/x-ms-application, " +
"application/vnd.ms-excel, application/vnd.ms-powerpoint, " +
"application/msword, */*");
conn.setRequestProperty("Accept-Language", "zh-CN");
conn.setRequestProperty("Referer", urlStr);
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty(
"User-Agent",
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; " +
".NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30;" +
" .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.connect();
if (conn.getResponseCode() == 200) {
// 根据响应获取文件大小
this.fileSize = conn.getContentLength();
if (fileSize <= 0)
throw new RuntimeException(
"下载文件大小出错 ... ");
File dir = new File(dirStr);
if (!dir.exists())
dir.mkdirs();
this.localFile = new File(dir, filename);
RandomAccessFile raf = new RandomAccessFile(this.localFile, "rw");
raf.setLength(fileSize);
raf.close();
Log.e("tag", "需要下载的文件大小为 :" + this.fileSize + " , 存储位置为: "
+ dirStr + "/" + filename);
} else {
throw new RuntimeException("连接错误 ...");
}
}
private class DownloadThread extends Thread {
/* 下载文件的URI*/
private URL url;
/* 存的本地路径*/
private File localFile;
/* 是否结束*/
private boolean isFinish;
/* 开始的位置*/
private Long startPos;
/* 结束位置*/
private Long endPos;
public DownloadThread(URL url, File savefile, Long startPos, Long endPos) {
this.url = url;
this.localFile = savefile;
this.startPos = startPos;
this.endPos = endPos;
}
@Override
public void run() {
Log.d("tag", Thread.currentThread().getName() + "开始下载...");
try {
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
conn.setConnectTimeout(15 * 1000);
conn.setRequestMethod("GET");
conn.setRequestProperty(
"Accept",
"image/gif, image/jpeg, image/pjpeg, image/pjpeg, " +
"application/x-shockwave-flash, application/xaml+xml, " +
"application/vnd.ms-xpsdocument, application/x-ms-xbap," +
" application/x-ms-application, application/vnd.ms-excel, " +
"application/vnd.ms-powerpoint, application/msword, */*");
conn.setRequestProperty("Accept-Language", "zh-CN");
conn.setRequestProperty("Referer", url.toString());
conn.setRequestProperty("Charset", "UTF-8");
// 设置获取实体数据的范围
conn.setRequestProperty("Range", "bytes=" + startPos + "-"
+ endPos);
conn.setRequestProperty(
"User-Agent",
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; " +
".NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; " +
".NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.connect();
/**
* 代表服务器已经成功处理了部分GET请求
*/
if (conn.getResponseCode() == 206) {
InputStream is = conn.getInputStream();
int len = 0;
byte[] buf = new byte[1024];
RandomAccessFile raf = new RandomAccessFile(localFile,
"rwd");
raf.seek(startPos);
while ((len = is.read(buf)) != -1) {
raf.write(buf, 0, len);
}
raf.close();
is.close();
Log.e("tag", Thread.currentThread().getName()
+ "完成下载 : " + startPos + " -- " + endPos);
setMessageForUI();
this.isFinish = true;
} else {
throw new RuntimeException(
"url 连接错误 ...");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void setMessageForUI(){
new Thread(new Runnable() {
@Override
public void run() {
Message msg = Message.obtain();
msg.what = 0x123;
msg.obj = threadCount;
handler.sendMessage(msg);
}
}).start();
}
}
MainAcivity.java :
package com.crazy.multidownload;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
private Handler handler;
private int threadCount;
private int count = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
threadCount = 2;
initHandler();
downLoad();
}
private void initHandler(){
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0x123) {
count++;
if (count == threadCount)
Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
}
}
};
}
private void downLoad() {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
new MultiThreadDownload(
"http://192.168.248.2/zip/multidownload.zip",
Environment.getExternalStorageDirectory() + "/multidownload",
"multidownload.zip",
threadCount,
handler).download();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}.execute();
}
}
开始下载输出的内容见下图:
从上图中可以看到线程的数量和我们所设定的线程数量是相等的。
完成下载后的输出:
从图中可以看出,下载完成的的输出和线程数量相同都为2,所以为了让其真正完成下载(线程数量和输出数量相等时)时在进行提升,才有了 count 计数器,记录接收到的次数。
最后可以打开 ADM 查看下载的路径:
从上面的几张图片中可以看到,文件的总大小是相同的。
最后不要忘了添加相应的权限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>