多線程斷點續傳下載

此方法實現的下載,下載速度達到網速上限十幾兆/S。

先講大體實現思路,再講其中各種導致下載速度上不去的坑。

 

原理:下載的時候多個線程併發可以佔用服務器端更多資源,從而加快下載速度。

 

1.   請求下載鏈接地址,獲取getContentLength,也就是文件總大小。

 

public boolean initDownLoadFileSize() {
		try {
			HttpURLConnection conn = (HttpURLConnection) new URL(downLoadUrl)
                    .openConnection();
			conn.setConnectTimeout(5000);
			conn.setReadTimeout(5000);
			conn.connect();
			int responseCode = conn.getResponseCode();
			if (responseCode == 200) {
                fileSize = conn.getContentLength();
                return true;
            }
		} catch (IOException e) {
		}
		return false;
	}

 

 

 

 

2.  根據文件總大小,決定分幾個子線程塊去下載。

 

private void convertChunks(){
		int MaximumUserCHUNKS = chunks/2;
		chunks = 1;

		for (int f=1 ; f <=MaximumUserCHUNKS ; f++)
			if (fileSize > MegaByte*f)
				chunks = f*2;

		data = FileDB.getInstance().getFileDownChunkLog(this.package_name);
		if (data.size() == chunks) {
			for (int i = 0; i < chunks; i++) {
				alreadyDownSize += data.get(i + 1);
			}
		} else {
			data.clear();
			for (int i = 0; i < chunks; i++) {
				data.put(i + 1, 0);// 初始化每條線程已經下載的數據長度爲0
			}
			FileDB.getInstance().deleteFileDownChunksLog(this.package_name);
			FileDB.getInstance().saveFileDownChunkLog(this.package_name, data);
		}

		workList = new DownloadThread[chunks];

	}

這一步驟核心:使用數據庫維護各線程的下載進度,從而實現斷點續傳的功能。下載之前先讀取數據庫,查詢是否有未完成的記錄,有就繼續下載,沒有則創建新記錄插入數據庫

 

 

3.    根據分好的塊開啓子線程開始下載。

 

void startDownloadChunk(){
		InputStream inStream = null;
		RandomAccessFile threadfile = null;
		try {
			HttpURLConnection http = (HttpURLConnection) new URL(downUrl)
					.openConnection();
			http.setConnectTimeout( 10 * 1000 );
			http.setReadTimeout( 10 * 1000 );
			int startPos = block * (threadId - 1) + downLength;
			int endPos;
			if (threadId == downloader.getThreadNum()) {
				endPos = downloader.getFileSize();
			} else {
				endPos = block * threadId - 1;
			}
			http.setRequestProperty("Range", "bytes=" + startPos + "-"
					+ endPos);
			inStream = http.getInputStream();
			byte[] buffer = new byte[1024];
			int offset;
			threadfile = new RandomAccessFile(this.saveFile, "rw");
			threadfile.seek(startPos);
			while ( (offset = inStream.read(buffer)) > 0 ) {
				threadfile.write(buffer, 0, offset);
				downLength += offset;
				downloader.append(offset);

				if (isPause) {
					downloader.Pause();
					return;
				}
			}
			downloader.checkDownloadFinish(this);
		} catch (Exception e) {
			LogUtil.i(e.toString());
			if (tryTime < MAX_RETRY_TIMES) {
				tryTime++;
				startDownloadChunk();
			}else {
				downloader.Pause();
				MyDownloadManager.getInstance().downFail(downloader, "下載失敗,請重試",
						true);
			}
		} finally {
			downloader.update(threadId, downLength);
			try {
				if (threadfile != null)
					threadfile.close();
				if (inStream != null)
					inStream.close();
			} catch (IOException e) {
			}
		}
	}

 

 

 

 

 

 

這一塊核心主要是兩個 

一: 設置http請求的文件長度,http.setRequestProperty("Range", "bytes=" + startPos + "-"     + endPos);

二:使用RandomAccessFile來實現多線程寫入同一文件。

 

=======================================分割線=============================================

 

大體實現思路就是如上文所述,是不是感覺沒幾步,很簡單? 0.0 

但其實真正做起來還有很多細節,很多坑在。接下來,作爲一個老司機,就談談我踩過的一些坑。


1.第一個問題就是分塊,到底怎麼分,分幾個塊合適。

    博主最先開始從單線程下載改成多線程下載時,分的子線程都是2個...或者3個.... 改完後對比,差別真的不大....很尷尬,現在想想,當初太小氣了。優化後的分塊邏輯文章上半部分已貼,16子線程上限。

 

2.第二個問題是RandomAccessFile的創建。

    網上帖子大多數是這麼創建的:new RandomAccessFile(file, "rws");  

    rws模式下文件的寫入是同步的,不是異步的,會大大降低多線程寫入文件的速度哦。

    所以,正確的姿勢是這樣子的: new RandomAccessFile(file, "rw");  

 

3.第三個問題是各子線程下載進度的保存問題。

    有些同學爲了實現斷點續傳這個功能,跟博主年輕的時候一樣,想當然的在寫入文件時更新數據庫。navie

    頻繁讀寫數據庫同樣也是降低下載速度的因素。

    優雅的做法是在異常時纔去更新保存進度。或者設置一個閥值。

    



 

 

 

 

 

發佈了37 篇原創文章 · 獲贊 31 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章