在 Android 應用開發中,文件下載是常見需求。為了提升下載效率並支援斷點續傳,採用多執行緒分段下載是主流方案。本文提供一個實用的 FileDownloadThread 類別,採用 HTTP Range 請求頭實現分段下載,並透過 RandomAccessFile 實現隨機寫入,適合整合至下載管理器中。
Connection.setRequestProperty("Range", "bytes=start-end") 向伺服器請求特定區塊。RandomAccessFile.seek(position) 定位寫入位置,避免多執行緒寫入衝突。package android.mooc.tools;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;
import android.util.Log;
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 boolean finished = false;
private int downloadSize;
private boolean state;
private boolean destory;
public boolean isDestory() { return destory; }
public void setDestory(boolean destory) { this.destory = destory; }
public FileDownloadThread(URL url, File file, int startPosition, int endPosition) {
this.url = url;
this.file = file;
this.startPosition = startPosition;
this.curPosition = startPosition;
this.endPosition = endPosition;
this.downloadSize = 0;
}
@Override
public void run() {
destory = false;
state = true;
BufferedInputStream bis = null;
RandomAccessFile fos = null;
byte[] buf = new byte[BUFFER_SIZE];
URLConnection con = null;
try {
con = url.openConnection();
con.setAllowUserInteraction(true);
con.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition);
con.setRequestProperty("accept", "*/*");
con.setRequestProperty("connection", "Keep-Alive");
con.setRequestProperty("Accept-Language", "zh-CN");
con.setRequestProperty("Charset", "UTF-8");
con.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
fos = new RandomAccessFile(file, "rw");
fos.seek(startPosition);
bis = new BufferedInputStream(con.getInputStream());
while ((curPosition < endPosition) && (!destory)) {
while (!state) {
sleep(2000);
}
int len = bis.read(buf, 0, BUFFER_SIZE);
if (len != -1) {
fos.write(buf, 0, len);
curPosition += len;
if (curPosition > endPosition) {
downloadSize += len - (curPosition - endPosition);
} else {
downloadSize += len;
}
}
Log.i("DownloadThread", "run: len=" + len);
}
finished = true;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bis != null) bis.close();
if (fos != null) fos.close();
} catch (Exception ignored) {}
}
}
public boolean isState() { return state; }
public void setState(boolean state) { this.state = state; }
public boolean isFinished() { return finished; }
public int getDownloadSize() { return downloadSize; }
public void setDownloadSize(int downloadSize) { this.downloadSize = downloadSize; }
}
con.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition); 告訴伺服器只傳回指定位元組範圍,這是分段下載的基礎。伺服器需支援 Accept-Ranges: bytes。
fos.seek(startPosition) 將檔案指標移至該執行緒負責的起始位置,各執行緒寫入各自區塊,互不干擾。
state 旗標控制下載迴圈。呼叫 setState(false) 可暫停,執行緒進入 sleep(2000) 輪詢;呼叫 setState(true) 即可繼續。
設定 destory = true 可讓執行緒在下一次迴圈檢查時退出,並在 finally 區塊關閉流,釋放資源。
// 在下載管理器中建立多個執行緒
int threadCount = 4;
int fileSize = getFileSize(url);
int blockSize = fileSize / threadCount;
for (int i = 0; i < threadCount; i++) {
int start = i * blockSize;
int end = (i == threadCount - 1) ? fileSize - 1 : (i + 1) * blockSize - 1;
FileDownloadThread thread = new FileDownloadThread(url, saveFile, start, end);
thread.start();
threadList.add(thread);
}
long 型別儲存位置與大小,避免整數溢位。