Android 多线程文件下载

原理一: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.csdn.net/goodname008/article/details/568668


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值