教你如何在 Android 使用多線程下載文件 =============================================== 前言 在 Android 日常開發中,我們會經常遇到下載文件需求,這裡我們也可以用系統自帶的 api 來解決這個問題,當然我們也可以自己來寫。在這裡我將教大 ...
# 教你如何在 Android 使用多線程下載文件
前言
在 Android 日常開發中,我們會經常遇到下載文件需求,這裡我們也可以用系統自帶的 api DownloadManager
來解決這個問題,當然我們也可以自己來寫。在這裡我將教大家如何在 Android 使用多線程下載文件。
實現原理
- 獲取目標文件的文件大小
- 根據線程的個數以及文件大小來分配每個線程下載文件的大小
如:文件大小:9M 線程個數:3,那麼每條線程下載的大小為 3M。
在這裡給出計算公式:blockSize=totalSize%countThread==0?totalSize/countThread:totalSize/countThread+1 ----blockSize 為每個線程下載的大小 totalSize 文件大小 countThread 線程個數
3.開啟線程下載(這裡要處理比較多的事)
具體實現
1.獲取文件的大小
這一步比較簡單我直接給出代碼:
URL url = null;
HttpURLConnection http = null;
try {
url = new URL(this.apk_url);
http = (HttpURLConnection) url
.openConnection();
http.setConnectTimeout(5 * 1000);
http.setReadTimeout(5 * 1000);
http.setRequestMethod("GET");
if (http.getResponseCode() == 200) {
this.filesize = http.getContentLength();//文件大小
} else {
this.filesize = -1;
}
} catch (Exception e) {
e.printStackTrace();
this.filesize = -1;
} finally {
http.disconnect();
}
2.分配線程
既然要對各個線程分配對應的下載大小,我們就有必要知道各個線程對應的信息,那麼我麽先來定義 bean
類來表示這些信息
package com.h.kidbot.download;
public class DownLoadInfo {
private int threadid;//線程id
private long startpos;//下載的起始位置
private long endpos;//下載的結束位置
private long block;//每條下載的大小
private long downpos;//該條線程已經下載的大小
private String downloadurl;//下載地址
public int getThreadid() {
return threadid;
}
public void setThreadid(int threadid) {
this.threadid = threadid;
}
public long getStartpos() {
return startpos;
}
public void setStartpos(long startpos) {
this.startpos = startpos;
}
public long getEndpos() {
return endpos;
}
public void setEndpos(long endpos) {
this.endpos = endpos;
}
public long getBlock() {
return block;
}
public void setBlock(long block) {
this.block = block;
}
public long getDownpos() {
return downpos;
}
public void setDownpos(long downpos) {
this.downpos = downpos;
}
public String getDownloadurl() {
return downloadurl;
}
public void setDownloadurl(String downloadurl) {
this.downloadurl = downloadurl;
}
}
定義好了這個類我們就可以根據剛纔獲取的文件大小來分配單個線程文件下載的大小了,為了方便起見呢 我就設置一條線程吧!
for (int i = 0; i < this.threadcount; i++) {
DownLoadInfo info = new DownLoadInfo();
long startpos = 0, endpos = 0;
if (i == this.threadcount - 1) {
startpos = i * block;
endpos = this.filesize - 1;
} else {
startpos = i * block;
endpos = (i + 1) * block - 1;
}
info.setBlock(block);
info.setDownpos(0);
info.setStartpos(startpos);
info.setEndpos(endpos);
info.setDownloadurl(this.apk_url);
info.setThreadid(i);
DownDbUtils.insert(this.context, info);
infos.add(info);
info = null;
}
得到每條線程對應的數據之後,我們就可以開啟線程啦!下麵的做法我和一般的不一樣 因為我沒有用到 RandomAccessFile
這個類,而是直接用 File
+ FileOutputStream
這兩個類來實現的,原因呢 我發現 RandomAccessFile
這個類的性能非常的差,非常的差,非常的差!重要的是說三遍!因為這原因,我在我司的平板是下載文件的速度很慢很慢!都要哭了!
當然之後我瞭解了一下 可以用 RandomAccessFile
+ nio
來提升文件的寫入速度!!
好啦!現在開始介紹下載類啦
public class DownLoadThread extends Thread {
private String apkurl;//下載地址
private long startpos;//起始地址
private long endpos;//結束地址
private long downpos;//已經下載的大小
private String apkpath;//保存地址
private long block;//每塊大小
private int threadid;//線程ID
private boolean finish = false; // 是否已經下載完成
private boolean error = false; // 是否出錯
private Context context;
private DownLoader loader;
private int downstate;//下載狀態
public static final int PAUSE = 2;//暫停
public static final int RUNNING = 1;//正在下載
public static final int STOP = 0;//停止
public DownLoadThread(Context context, String apkurl, long startpos, long endpos, long downpos, String apkpath, long block, int threadid, DownLoader loader) {
this.context = context;
this.apkurl = apkurl;
this.startpos = startpos;
this.endpos = endpos;
this.downpos = downpos;
this.apkpath = apkpath;
this.block = block;
this.threadid = threadid;
this.loader = loader;
this.downstate = RUNNING;
}
public DownLoadThread() {
}
@Override
public void run() {
File file=null;
FileOutputStream fout=null;
InputStream in = null;
if (downpos < block) {
try {
URL url = new URL(apkurl);
HttpURLConnection http = (HttpURLConnection) url
.openConnection();
http.setConnectTimeout(5 * 1000);
http.setReadTimeout(5 * 1000);
http.setRequestMethod("GET");
http.setRequestProperty(
"Accept",
"image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x- shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
http.setRequestProperty("Accept-Language", "zh-CN");
http.setRequestProperty("Referer", url.toString());
http.setRequestProperty("Charset", "UTF-8");
http.setRequestProperty("Connection", "Keep-Alive");
long startPos = startpos + downpos;
long endPos = endpos;
http.setRequestProperty("Range", "bytes=" + startPos + "-");// 設置獲取實體數據的範圍
file=new File(apkpath);
if (file.length()>0){
fout=new FileOutputStream(file,true);
}else{
fout=new FileOutputStream(file);
}
byte[] bytes = new byte[2048];
int len = 0;
in = http.getInputStream();
LogUtils.e("開始");
while ((len = in.read(bytes, 0, bytes.length)) != -1) {
if (PAUSE == this.downstate || STOP == this.downstate) {
DownDbUtils.update(this.context, this.threadid, this.apkurl, downpos);
break;
}
fout.write(bytes,0,len);
downpos += len;//已下載的大小
this.loader.setDownlength(len);
}
DownDbUtils.update(this.context, this.threadid, this.apkurl, downpos);
if (!DeviceUtils.isNet(context)) {
this.finish = false;
this.downstate=PAUSE;
} else {
this.finish = true;
}
} catch (Exception e) {
DownDbUtils.update(this.context, this.threadid, this.apkurl, downpos);
LogUtils.e(e.toString());
e.printStackTrace();
downpos = -1;
this.error = true;
this.finish = false;
} finally {
closeIO(in,fout);
}
}
}
//得到每條線程已經下載的大小
public long getDownpos() {
return downpos;
}
public int getDownstate() {
return downstate;
}
public void setDownstate(int downstate) {
this.downstate = downstate;
}
//是否下載完成
public boolean isFinish() {
return finish;
}
public void setFinish(boolean finish) {
this.finish = finish;
}
//是否下載出錯
public boolean isError() {
return error;
}
public void setError(boolean error) {
this.error = error;
}
public void setDownpos(long downpos) {
this.downpos = downpos;
}
//關閉流
public static void closeIO(Closeable... closeables) {
if (null == closeables || closeables.length <= 0) {
return;
}
for (Closeable cb : closeables) {
try {
if (null == cb) {
continue;
}
cb.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
這裡大家得瞭解下 http
中這個 Range
這個欄位的含義:用戶請求頭中,指定第一個位元組的位置和最後一個位元組的位置,如(Range:200-300)! 這樣就可以指定下載文件的位置了呢!到了這裡核心的部分已經說完了!
其他
上面已經把核心的都說完了,其實還有其他的可以說呢:
- 實現斷點下載(保存下載的長度,用資料庫或者文件保存都可以)
- 多線程下載的管理 (需要讀者實現管理器了)
- 可以把下載這個模塊放到另外一個進程中,這樣可以是主進程更加的流暢。當然這涉及到了進程見通信的問題啦
- 一般下載的時候都會有下載進度條,這裡要註意下更新的頻率的問題,在
listview
中更新太快會造成頁面卡頓的哦!