本篇文章以你文件下载中的多线程下载以及断点续传为问题出发点,主要回顾一下多线程在实际开发中的应用和具体实现。
多线程下载的关键点在于对一个下载任务进行切分,即计算每个任务线程对应的实际文件中的起始点和终止点。在每个线程中采用数据流方式对远程文件进行连接,这里有个知识点,即http头Rander参数,详见
http://guoba6688-sina-com.iteye.com/blog/786036,通过该参数可以实现读取远程文件的指定部分。
下面的实例的应用环境为android,具体看代码:
package com.fsti.android.foyer.net;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;
import android.util.Log;
/**
* 文件下载线程
*
* @author ilikeido
* @modifyAuthor
*
* @creatTime 2011-4-7 下午02:59:04
*/
public class FileDownloadThread extends Thread {
private static final int BUFFER_SIZE = 1024;
private URL url;//地址
private File file;//保存文件
private int startPosition;//开始位置
private int endPosition;//结束位置
private int curPosition;//当前位置
private ThreadCutter cutter;
// 用于标识当前线程是否下载完成
private boolean finished = false;
private int downloadSize = 0;//下载的文件大小
public FileDownloadThread(URL url, File file, int startPosition,
int endPosition,ThreadCutter cutter) {
this.url = url;
this.file = file;
this.startPosition = startPosition;
this.curPosition = startPosition;
this.endPosition = endPosition;
this.cutter = cutter;
}
@Override
public void run() {
BufferedInputStream bis = null;
RandomAccessFile fos = null;
byte[] buf = new byte[BUFFER_SIZE];
URLConnection con = null;
try {
fos = new RandomAccessFile(file, "rw");
downloadSize = (int) fos.length();
con = url.openConnection();
con.setAllowUserInteraction(true);
// 设置当前线程下载的起点,终点
con.setRequestProperty("Range", "bytes=" + (startPosition+downloadSize) + "-"
+ endPosition);
// 使用java中的RandomAccessFile对文件进行随机读写操作
curPosition = startPosition + downloadSize;
// 设置开始写文件的位置
fos.seek(downloadSize);
bis = new BufferedInputStream(con.getInputStream());
// 开始循环以流的形式读写文件
while (cutter.getStatu() == 1 && 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;
}
}
// 下载完成设为true
this.finished = true;
bis.close();
fos.close();
} catch (IOException e) {
Log.d(getName() + "Error:", e.getMessage());
}
}
public boolean isFinished() {
return finished;
}
public int getDownloadSize() {
return downloadSize;
}
public File getFile(){
return file;
}
}
以上为线程下载任务,主要负责下载任务,下面看一下管理器
package com.fsti.android.foyer.net;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import android.util.Log;
/**
* 文件分割器(用于多线程下载)
*
* @author ilikeido
* @modifyAuthor
*
* @creatTime 2011-4-7 下午03:05:53
*/
public class DownThreadManager {
private static final String TAG = "ThreadCutter";
private int threadSize;// 线程数
private URL url;// 地址
private String fileName;
private int totalProgress;// 整体进度
private int filelength;// 文件大小
File dir;
File saveFile;
private int statu;// 状态 0:停止 1:正常 2:暂停 3:完成 -1:错误
private List<FileDownloadThread> threads;
public DownThreadManager(int threadSize, String urlStr, File dir)
throws IOException {
this.dir = dir;
this.threadSize = threadSize;
this.url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
filelength = conn.getContentLength();
if(filelength>0){
initFileName(conn,urlStr);
cutThreads();
}else{
throw new RuntimeException("file not find");
}
}
/**
* 分割下载任务
*/
private void cutThreads() {
threads = new ArrayList<FileDownloadThread>();
int cutsize = filelength / threadSize;
for (int i = 0; i < threadSize; i++) {
File tempFile = new File(dir, fileName + i + ".temp");
int startPosition = cutsize * i;
int endPosition = 0;
FileDownloadThread thread = null;
if (i < threadSize - 1) {
endPosition = cutsize * i + cutsize - 1;
thread = new FileDownloadThread(url, tempFile, startPosition,
endPosition, this);
} else {
thread = new FileDownloadThread(url, tempFile, startPosition,
filelength, this);
}
threads.add(thread);
}
}
/**
* 开始下载任务
*/
public void start() {
Iterator<FileDownloadThread> itertor = threads.iterator();
statu = 1;
while (itertor.hasNext())
itertor.next().start();
}
/**
* 获取当前下载的文件大小
*
* @return
*/
public int getDownloadSize() {
Iterator<FileDownloadThread> itertor = threads.iterator();
int downloadSize = 0;
while (itertor.hasNext()) {
downloadSize += itertor.next().getDownloadSize();
}
if (downloadSize > 0) {
this.totalProgress = (int) (((float) downloadSize / filelength) * 100);
}
if (downloadSize == filelength) {
this.statu = 3;
}
return downloadSize;
}
public void merge() throws IOException {
RandomAccessFile ok = new RandomAccessFile(saveFile,"rw");
Iterator<FileDownloadThread> itertor = threads.iterator();
while (itertor.hasNext()) {
File file = itertor.next().getFile();
RandomAccessFile read = new RandomAccessFile(file, "r");
byte[] b = new byte[1024];
int n = 0;
while ((n = read.read(b)) != -1) {
ok.write(b, 0, n);
}
read.close();
file.delete();
}
ok.close();
}
public void stop() {
this.statu = 2;
}
public int getStatu() {
return statu;
}
public void setStatu(int statu) {
this.statu = statu;
}
public int getTotalProgress() {
return totalProgress;
}
/**
* 获取文件名
*
* @param http
*/
public void initFileName(HttpURLConnection http,String urlStr) {
String filename = urlStr.substring(urlStr.lastIndexOf('/') + 1);
if(filename.indexOf(".")>=0){
fileName = filename;
}else{
Map<String, String> header = getHttpResponseHeader(http);
for (Map.Entry<String, String> entry : header.entrySet()) {
String key = entry.getKey() != null ? entry.getKey() + ":" : "";
print(key + entry.getValue());
}
String dispostion = header.get("content-disposition");
int index = 0;
if((index = dispostion.indexOf("filename")) >-1){
String filenametemp = dispostion.substring(index,dispostion.length());
String[] temp = filenametemp.split("\"");
fileName = temp[1];
}
}
saveFile = new File(dir, fileName);
}
/**
* 获取Http响应头字段
*
* @param http
* @return
*/
public static Map<String, String> getHttpResponseHeader(
HttpURLConnection http) {
Map<String, String> header = new LinkedHashMap<String, String>();
for (int i = 0;; i++) {
String mine = http.getHeaderField(i);
if (mine == null)
break;
header.put(http.getHeaderFieldKey(i), mine);
}
return header;
}
private static void print(String msg) {
Log.i(TAG, msg);
}
public File getSaveFile(){
return this.getSaveFile();
}
}
该管理器主要的任务是分割下载任务,对线程的管理(通过statu参数)、以及各线程下载完成后文件的合并等。
下面是实际中的调用:
package com.fsti.android.foyer;
import java.io.File;
import java.io.IOException;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import com.fsti.android.foyer.net.ThreadCutter;
public class MainActivity extends Activity implements OnClickListener,DialogInterface.OnClickListener{
/** Called when the activity is first created. */
ProgressDialog dialog = null;
int threadNum = 2;
String urlStr= "http://www.piaoao.com/resources/updatefiles/alldown/WingLetter_GPiaoao.apk";//"http://nutla.googlecode.com/files/Nuta9.pdf";//"http://dl_dir.qq.com/qqfile/qq/Android/Tencent_Wblog%20V2.0.1.apk";//
ThreadCutter cutter = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button = (Button) findViewById(R.id.button1);
button.setOnClickListener(this);
}
@Override
public void onClick(View arg0) {
dialog = new ProgressDialog(this);
dialog.setProgress(59);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setTitle("下载中");
dialog.setMessage("正在下载应用程序");
dialog.setButton("取消", this);
dialog.show();
download();
}
@Override
public void onClick(DialogInterface arg0, int arg1) {
cutter.stop();
dialog.dismiss();
}
public void download(){
final File dir = Environment.getExternalStorageDirectory();
int cutterNum = 3;
try {
cutter = new ThreadCutter(cutterNum, urlStr, dir);
Runnable runnable = new Runnable() {
@Override
public void run() {
cutter.start();
while(cutter.getTotalProgress() < 100){
cutter.getDownloadSize();
try {
Thread.sleep(900);
} catch (InterruptedException e) {
e.printStackTrace();
}
dialog.setProgress(cutter.getTotalProgress());
}
try {
cutter.merge();
cutter.setStatu(3);
} catch (IOException e) {
e.printStackTrace();
}
dialog.dismiss();
try {
if(cutter.getSaveFile().getAbsolutePath().endsWith(".apk")){
Intent localIntent1 = new Intent("android.intent.action.VIEW");
Uri localUri = Uri.fromFile(cutter.getSaveFile());
localIntent1.setDataAndType(localUri, "application/vnd.android.package-archive");
startActivity(localIntent1);
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
new Thread(runnable).start();
} catch (IOException e) {
cutter.setStatu(4);
}
}
}