使用单线程下载文件会比较慢,如果使用n个线程,那么时间就会缩短为1/n,每个线程只下载其中的一段,都下载完以后再把这n段拼接起来就ok了。下面简单介绍一下多线程下载的原理和实现。
多线程下载的关键是怎样从服务器只获取文件的一部分,而不是整个文件。这里需要设置request header的一个属性:Range。
conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
还有下载完毕后怎么把多个文件片段合并到一起的问题。可以使用RandomAccessFile,但要注意:每个线程需要建自己的RandomAccessFile对象,它们可以共同操作一个File,但不能使用同一个RandomAccessFile。
完整代码:
public class MainActivity extends ActionBarActivity {
public static final String TEST_URL = "http://gdown.baidu.com/data/wisegame/e59f42264a98b05e/WeChat_861.apk";
private TextView[] tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv1 = (TextView) findViewById(R.id.tv1);
TextView tv2 = (TextView) findViewById(R.id.tv2);
TextView tv3 = (TextView) findViewById(R.id.tv3);
tv = new TextView[3];
tv[0] = tv1; tv[1] = tv2; tv[2] = tv3;
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
@Override
public void run() {
try {
URL url = new URL(TEST_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727)");
String fileName = TEST_URL.substring(TEST_URL.lastIndexOf("/") + 1);
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), fileName);
int totalLength = conn.getContentLength();
int subLength = totalLength / 3;
for(int i = 0; i < 3; i++){
int start = i * subLength;
int end = (i+1) * subLength - 1;
if(i == 2){
end = totalLength;
}
new DownloadThread(i, start, end, file, url).start();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
});
}
class DownloadThread extends Thread{
int threadId;
int start;
int end;
File file;
URL url;
int currLength = 0;
int totalLength;
public DownloadThread(int threadId, int start, int end, File file, URL url){
this.threadId = threadId;
this.start = start;
this.end = end;
this.file = file;
this.url = url;
totalLength = end - start + 1;
}
@Override
public void run() {
try {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727)");
conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
randomAccessFile.seek(start);
InputStream is = conn.getInputStream();
byte[] buf = new byte[4 * 1024];
int len;
while((len = is.read(buf)) != -1){
currLength += len;
runOnUiThread(new Runnable() {
@Override
public void run() {
tv[threadId].setText("进程" + (threadId + 1) + " 下载进度:" + currLength + "/" + totalLength);
}
});
randomAccessFile.write(buf, 0, len);
}
is.close();
randomAccessFile.close();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}