此方法實現的下載,下載速度達到網速上限十幾兆/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
頻繁讀寫數據庫同樣也是降低下載速度的因素。
優雅的做法是在異常時纔去更新保存進度。或者設置一個閥值。