本文将介绍在Android平台下如何实现多线程下载,Java中支持的多线程下载方式在Android平台下都支持,其中主要有两种方式:一种方式是使用N多个线程分别下载文件的不同部分,最后把所有下载完的文件合并成一个文件;另一种方式是使用Java提供的RandomAccessFile类来实现文件的多线程下载,顾名思义,这个类的主要作用就是随机访问,也就是可以随时指定不同的读取、写入位置,本文就是用的这个类,用N个线程同时去下载文件的不同部分,最终合成一个文件,大大加快文件的下载。
第一步,我们先写一个线程类,来完成对指定的url进行下载:
private static final int BUFFER_SIZE = 1024;
private URL url;
private File file;
private int startPosition;
private int endPosition;
private int curPosition;
// 是否下载完成
private boolean finished = false;
private int downloadSize = 0;
public FileDownloadThread(URL url, File file, int startPosition,
int endPosition) {
this.url = url;
this.file = file;
this.startPosition = startPosition;
this.curPosition = startPosition;
this.endPosition = endPosition;
}
@Override
public void run() {
BufferedInputStream bis = null;
RandomAccessFile fos = null;
byte[] buf = new byte[BUFFER_SIZE];
URLConnection con = null;
try {
con = url.openConnection();
con.setAllowUserInteraction(true);
// 设置当前线程下载的起点,终点
con.setRequestProperty("Range", "bytes=" + startPosition + "-"
+ endPosition);
// 使用java中的RandomAccessFile 对文件进行随机读写操作
fos = new RandomAccessFile(file, "rw");
// 设置开始写文件的位置
fos.seek(startPosition);
bis = new BufferedInputStream(con.getInputStream());
// 开始循环以流的形式读写文件
while (curPosition < endPosition) {
int len = bis.read(buf, 0, BUFFER_SIZE);
if (len == -1) {
break;
}
fos.write(buf, 0, len);
curPosition = curPosition + len;
if (curPosition > endPosition) {
downloadSize += len - (curPosition - endPosition) + 1;
} else {
downloadSize += len;
}
}
// 下载完成
this.finished = true;
bis.close();
fos.close();
} catch (IOException e) {
Log.w(getName() + " yilee:", e.getMessage());
}
}
public boolean isFinished() {
return finished;
}
public int getDownloadSize() {
return downloadSize;
}
}
接下来就是Activity类了,实时更新下载进度条:
private EditText downloadUrl;
private EditText downloadFileName;
private EditText downloadThreadNum;
private Button downloadBt;
private ProgressBar downloadProgressBar;
private TextView progressMessage;
private int downloadedSize = 0;
private int fileSize = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
downloadUrl = (EditText) findViewById(R.id.downloadUrl);
downloadFileName = (EditText) findViewById(R.id.downloadFileName);
downloadThreadNum = (EditText) findViewById(R.id.downloadThreadNum);
progressMessage = (TextView) findViewById(R.id.progressMessage);
downloadBt = (Button) findViewById(R.id.downloadBt);
downloadProgressBar = (ProgressBar) findViewById(R.id.downloadProgressBar);
downloadProgressBar.setVisibility(View.VISIBLE);
downloadProgressBar.setMax(100);
downloadProgressBar.setProgress(0);
downloadBt.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
download();
}
});
}
private void download() {
// 获取SD卡目录
String dowloadDir = Environment.getExternalStorageDirectory()
+ "/yilee/";
File file = new File(dowloadDir);
// 创建下载目录
if (!file.exists()) {
file.mkdirs();
}
// 读取下载线程数,如果为空,则单线程下载
int downloadTN = Integer.valueOf("".equals(downloadThreadNum.getText()
.toString()) ? "1" : downloadThreadNum.getText().toString());
// 如果下载文件名为空则获取Url尾为文件名
int fileNameStart = downloadUrl.getText().toString().lastIndexOf("/");
String fileName = "".equals(downloadFileName.getText().toString()) ? downloadUrl
.getText().toString().substring(fileNameStart)
: downloadFileName.getText().toString();
// 开始下载前把下载按钮设置为不可用
downloadBt.setClickable(false);
// 启动文件下载线程
new downloadTask(downloadUrl.getText().toString(),
Integer.valueOf(downloadTN), dowloadDir + fileName).start();
}
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 当收到更新视图消息时,计算已完成下载百分比,同时更新进度条信息
int progress = (Double
.valueOf((downloadedSize * 1.0 / fileSize * 100)))
.intValue();
if (progress == 100) {
downloadBt.setClickable(true);
progressMessage.setText("下载完成");
} else {
progressMessage.setText("进度:" + progress + "%");
}
downloadProgressBar.setProgress(progress);
}
};
/**
* 主下载线程
*/
public class downloadTask extends Thread {
private int blockSize, downloadSizeMore;
private int threadNum;
String urlStr, threadNo, fileName;
public downloadTask(String urlStr, int threadNum, String fileName) {
this.urlStr = urlStr;
this.threadNum = threadNum;
this.fileName = fileName;
}
@Override
public void run() {
FileDownloadThread[] fds = new FileDownloadThread[threadNum];
try {
URL url = new URL(urlStr);
URLConnection conn = url.openConnection();
// 获取下载文件的总大小
fileSize = conn.getContentLength();
// 计算每个线程要下载的数据量
blockSize = fileSize / threadNum;
// 解决整除后百分比计算误差
downloadSizeMore = (fileSize % threadNum);
File file = new File(fileName);
for (int i = 0; i < threadNum; i++) {
// 启动线程,分别下载自己需要下载的部分
FileDownloadThread fdt = new FileDownloadThread(url, file,
i * blockSize, (i + 1) * blockSize - 1);
fdt.setName("Thread" + i);
fdt.start();
fds[i] = fdt;
}
boolean finished = false;
while (!finished) {
// 先把整除的余数搞定
downloadedSize = downloadSizeMore;
finished = true;
for (int i = 0; i < fds.length; i++) {
downloadedSize += fds[i].getDownloadSize();
if (!fds[i].isFinished()) {
finished = false;
}
}
// 通知handler去更新视图组件
handler.sendEmptyMessage(0);
// 休息1秒后再读取下载进度
sleep(100);
}
} catch (Exception e) {
Log.w("yilee", e.getMessage());
}
}
}
}
XMl就不贴代码 了,几个控件而已,别忘了加入权限:
<uses-permission android:name="android.permission.INTERNET"/>
<!- 在SDCard中创建与删除文件权限 ->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!- 往SDCard写入数据权限 ->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
效果图如下:
5是进程数
值得注意的是,Android只是一个演示,其它很多地方如需要多线程下载都可以用到这个方法,感觉RandomAccessFile这个类好强大呀。
PS,经过本天才大量实际测试(文件大小从几K到几百M,地址从内网,国内,国外,线程从1到100),感觉不出快了多少-。-,真是悲剧唉,不过想来想去想不出原因,应该是可以加快很多的,如果哪位大神看到了希望能给个看法,也不知道在真机上效果怎么样,开发Android应用却没有Android手机的程序猿真是伤不起呐~~