原理一:HTTP多线程下载原理
1、发送一个含有Rang头的Head请求,如果返回状态码为206,则允许多线程下载
原理二:多线程下载原理
1、使用HttpClient的Head请求获取请求文件的信息
2、发送一个Rang的Head请求判断是否允许多线程下载
3、通过主任务创建多个分段下载线程,分段下载文件,然后用Java的随机读写文件类保存下载的内容
调度程序(有ProgressBar等待)
代码都有注释,很容易看懂/**
* 异步文件下载器,可开启多线程进行下载
*
* @author zhihong.lanzh
*
*/
public class FileDownloadAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private static String TAG = "FileDownloadAsyncTask";
private int downloadedSize = 0;
private int fileSize = 0;
private int blockSize, downloadSizeMore;
private int threadNum = 5;
private String urlStr, fileName;
private Context mContext;
private ProgressDialog mProgressDialog;
private Exception mException = null;
private Callback<Boolean> pCallback;
private Callback<Exception> pExceptionCallback;
private HttpClient httpClient;
private boolean acceptRanges = false;// 是否支持多线程下载
public FileDownloadAsyncTask(Context context, String urlStr, int threadNum, String fileName, Callback<Boolean> pCallback) {
httpClient = new DefaultHttpClient();
this.urlStr = urlStr;
if(threadNum < 1)
this.threadNum = 1;
else
this.threadNum = threadNum;
this.fileName = fileName;
File file = new File(fileName);
if (file.exists()) {
file.delete();
}
this.mContext = context;
this.pCallback = pCallback;
this.pExceptionCallback = null;
}
public FileDownloadAsyncTask(Context context, String urlStr, int threadNum, String fileName, Callback<Boolean> pCallback,
Callback<Exception> pExceptionCallback) {
this(context, urlStr, threadNum, fileName, pCallback);
this.pExceptionCallback = pExceptionCallback;
}
@Override
protected Boolean doInBackground(Void... params) {
try {
getDownloadFileInfo();
Log.v(TAG, "fileSize:" + fileSize);
if (fileSize == -1) {
return false;
}
if (acceptRanges == false) {
threadNum = 1;
}
FileDownloadThread[] fds = new FileDownloadThread[threadNum];
// 计算每个线程要下载的数据量
blockSize = fileSize / threadNum;
// 解决整除后百分比计算误差
downloadSizeMore = (fileSize % threadNum);
File file = new File(fileName);
Log.v(TAG, "doInBackground2");
for (int i = 0; i < threadNum; i++) {
// 启动线程,分别下载自己需要下载的部分
FileDownloadThread fdt = new FileDownloadThread(httpClient, urlStr, file, i * blockSize, (i + 1) * blockSize - 1);
fdt.setName("Thread" + i);
fdt.start();
fds[i] = fdt;
}
if (threadNum > 1) {
// 启动最后一个线程下载最后一部分
FileDownloadThread fdt = new FileDownloadThread(httpClient, urlStr, file, (threadNum - 1) * blockSize, threadNum * blockSize - 1
+ downloadSizeMore);
fdt.setName("Thread" + (threadNum - 1));
fdt.start();
fds[(threadNum - 1)] = fdt;
}
boolean finished = false;
while (!finished) {
finished = true;
downloadedSize = 0;
for (int i = 0; i < fds.length; i++) {
downloadedSize += fds[i].getDownloadSize();
if (!fds[i].isFinished()) {
finished = false;
}
Log.v(TAG, "fds[" + i + "]:" + fds[i].isFinished());
}
int progress = (int) (((double) downloadedSize / fileSize) * 100);
onProgressUpdate(progress);
Log.v(TAG, "downloadedSize:" + downloadedSize);
// 休息200ms后再读取下载进度
// Thread.sleep(5);
}
boolean result = true;
for (int i = 0; i < threadNum; i++) {
result = result & fds[i].getResult();
}
Log.v(TAG, "Result:" + result);
return result;
} catch (Exception e) {
e.printStackTrace();
this.mException = e;
return false;
} finally {
httpClient.getConnectionManager().shutdown();
}
}
/**
* 获取文件下载信息
*/
private void getDownloadFileInfo() {
try {
HttpHead httpHead = new HttpHead(urlStr);
HttpResponse response = httpClient.execute(httpHead);
if (response.getStatusLine().getStatusCode() != 200) {
fileSize = -1;
}
Log.v(TAG, "response");
// 获取下载文件的总大小
// fileSize = (int) response.getEntity().getContentLength();
Header[] headers = response.getHeaders("Content-Length");
if (headers.length > 0)
fileSize = Integer.parseInt(headers[0].getValue());
httpHead.abort();
if (threadNum != 1) {
// 判断是否允许多线程下载
httpHead = new HttpHead(urlStr);
httpHead.addHeader("Range", "bytes=0-" + (fileSize - 1));
response = httpClient.execute(httpHead);
if (response.getStatusLine().getStatusCode() == 206) {
acceptRanges = true;
}
Log.v(TAG, "acceptRanges:" + response.getStatusLine().getStatusCode());
httpHead.abort();
}
} catch (ClientProtocolException e) {
fileSize = -1;
Log.e(TAG, e.getMessage());
// e.printStackTrace();
} catch (IOException e) {
fileSize = -1;
Log.e(TAG, e.getMessage());
// e.printStackTrace();
}
}
@Override
protected void onPreExecute() {
this.mProgressDialog = new ProgressDialog(mContext);
this.mProgressDialog.setTitle(R.string.update_dialog_download_title);
this.mProgressDialog.setIcon(android.R.drawable.ic_menu_save);
this.mProgressDialog.setIndeterminate(false);
this.mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
this.mProgressDialog.setMax(100);
this.mProgressDialog.show();
super.onPreExecute();
}
@Override
protected void onPostExecute(Boolean result) {
try {
if (result != null)
this.mProgressDialog.dismiss();
} catch (final Exception e) {
Log.e("Error", e.getLocalizedMessage());
/* Nothing. */
}
if (this.isCancelled()) {
this.mException = new CancelledException();
}
if (this.mException == null) {
pCallback.onCallback(result);
} else {
if (pExceptionCallback != null) {
pExceptionCallback.onCallback(this.mException);
}
}
super.onPostExecute(result);
}
@Override
protected void onProgressUpdate(Integer... values) {
this.mProgressDialog.setProgress(values[0]);
super.onProgressUpdate(values);
}
}
各个线程的下载片段
/**
* 文件下载线程
* @author zhihong.lanzh
*
*/
public class FileDownloadThread extends Thread {
private static final String TAG = "FileDownloadThread";
private static final int BUFFER_SIZE = 1024;
private String url;
private File file;
private int startPosition;
private int endPosition;
private int curPosition;
// 用于标识当前线程是否下载完成
private boolean finished = false;
private boolean result = false;
private int downloadSize = 0;
private Object o = new Object();
private HttpClient httpClient;
public FileDownloadThread(HttpClient httpClient , String url, File file, int startPosition, int endPosition) {
this.httpClient = httpClient;
this.url = url;
this.file = file;
this.startPosition = startPosition;
this.curPosition = startPosition;
this.endPosition = endPosition;
}
@Override
public void run() {
if(httpClient == null){
this.finished = true;
this.result = false;
return;
}
BufferedInputStream bis = null;
RandomAccessFile fos = null;
byte[] buf = new byte[BUFFER_SIZE];
try {
HttpGet httpGet = new HttpGet(url);
HttpResponse response = httpClient.execute(httpGet);
if(response.getStatusLine().getStatusCode() != 200){
this.finished = true;
this.result = false;
return;
}
HttpEntity entity = response.getEntity();
// 设置当前线程下载的起点,终点
httpGet.addHeader("Range", "bytes=" + startPosition + "-" + endPosition);
// 使用java中的RandomAccessFile 对文件进行随机读写操作
fos = new RandomAccessFile(file, "rw");
// 设置开始写文件的位置
fos.seek(startPosition);
bis = new BufferedInputStream(entity.getContent());
// 开始循环以流的形式读写文件
while (curPosition < endPosition) {
int len = bis.read(buf, 0, BUFFER_SIZE);
if (len == -1) {
break;
}
fos.write(buf, 0, len);
curPosition = curPosition + len;
synchronized (o) {
if (curPosition > endPosition) {
downloadSize += len - (curPosition - endPosition) + 1;
} else {
downloadSize += len;
}
}
// Log.v(TAG, "curPosition:" + curPosition + ",endPosition:" + endPosition + ",downloadSize:" + downloadSize);
// sleep(100);
}
// 下载完成设为true
this.finished = true;
this.result = true;
bis.close();
fos.close();
httpGet.abort();
} catch (Exception e) {
result = false;
e.printStackTrace();
Log.v(TAG, e.getMessage());
}
}
public boolean isFinished() {
return finished;
}
public int getDownloadSize() {
synchronized (o) {
return downloadSize;
}
}
public boolean getResult() {
return result;
}
public void setResult(boolean result) {
this.result = result;
}
}
Reference:
http://zywang.iteye.com/blog/916489
http://blog.youkuaiyun.com/goodname008/article/details/568668