Android 斷點(diǎn)續(xù)傳原理以及實(shí)現(xiàn)
Android 斷點(diǎn)續(xù)傳原理以及實(shí)現(xiàn)
0. 前言
在Android開(kāi)發(fā)中,斷點(diǎn)續(xù)傳聽(tīng)起來(lái)挺容易,在下載一個(gè)文件時(shí)點(diǎn)擊暫停任務(wù)暫停,點(diǎn)擊開(kāi)始會(huì)繼續(xù)下載文件。但是真正實(shí)現(xiàn)起來(lái)知識(shí)點(diǎn)還是蠻多的,因此今天有時(shí)間實(shí)現(xiàn)了一下,并進(jìn)行記錄。
1. 斷點(diǎn)續(xù)傳原理
在本地下載過(guò)程中要使用數(shù)據(jù)庫(kù)實(shí)時(shí)存儲(chǔ)到底存儲(chǔ)到文件的哪個(gè)位置了,這樣點(diǎn)擊開(kāi)始繼續(xù)傳遞時(shí),才能通過(guò)HTTP的GET請(qǐng)求中的setRequestProperty()方法可以告訴服務(wù)器,數(shù)據(jù)從哪里開(kāi)始,到哪里結(jié)束。同時(shí)在本地的文件寫(xiě)入時(shí),RandomAccessFile的seek()方法也支持在文件中的任意位置進(jìn)行寫(xiě)入操作。同時(shí)通過(guò)廣播將子線程的進(jìn)度告訴Activity的ProcessBar。
2. Activity的按鈕響應(yīng)
當(dāng)點(diǎn)擊開(kāi)始按鈕時(shí),將url寫(xiě)在了FileInfo類的對(duì)象info中并通過(guò)Intent從Activity傳遞到了Service中。這里使用setAction()來(lái)區(qū)分是開(kāi)始按鈕還是暫停按鈕。
public class FileInfo implements Serializable{
private String url; //URL
private int length; //長(zhǎng)度或結(jié)束位置
private int start; //開(kāi)始位置
private int now;//當(dāng)前進(jìn)度
//構(gòu)造方法,set/get略
}
//開(kāi)始按鈕邏輯,停止邏輯大致相同
strat.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this,DownLoadService.class);
intent.setAction(DownLoadService.ACTION_START);
intent.putExtra("fileUrl",info);
startService(intent);
}
});
3. 在Service中的子線程中獲取文件大小
在Service中的onStartCommand()中,將FileInfo對(duì)象從Intent中取出,如果是開(kāi)始命令,則開(kāi)啟一個(gè)線程,根據(jù)該url去獲得要下載文件的大小,將該大小寫(xiě)入對(duì)象并通過(guò)Handler傳回Service,同時(shí)在本地創(chuàng)建一個(gè)相同大小的本地文件。暫停命令最后會(huì)講到。
public void run() {
HttpURLConnection urlConnection = null;
RandomAccessFile randomFile = null;
try {
URL url = new URL(fileInfo.getUrl());
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setConnectTimeout(3000);
urlConnection.setRequestMethod("GET");
int length = -1;
if (urlConnection.getResponseCode() == HttpStatus.SC_OK) {
//獲得文件長(zhǎng)度
length = urlConnection.getContentLength();
}
if (length <= 0) {
return;
}
//創(chuàng)建相同大小的本地文件
File dir = new File(DOWNLOAD_PATH);
if (!dir.exists()) {
dir.mkdir();
}
File file = new File(dir, FILE_NAME);
randomFile = new RandomAccessFile(file, "rwd");
randomFile.setLength(length);
//長(zhǎng)度給fileInfo對(duì)象
fileInfo.setLength(length);
//通過(guò)Handler將對(duì)象傳遞給Service
mHandle.obtainMessage(0, fileInfo).sendToTarget();
} catch (Exception e) {
e.printStackTrace();
} finally { //流的回收邏輯略
}
}
}
4. 數(shù)據(jù)庫(kù)操作封裝
在Service的handleMessage()方法中拿到有l(wèi)ength屬性的FileInfo對(duì)象,并使用自定義的DownLoadUtil類進(jìn)行具體的文件下載邏輯。這里傳入上下文,因?yàn)閿?shù)據(jù)庫(kù)處理操作需要用到。
downLoadUtil = new DownLoadUtil(DownLoadService.this,info); downLoadUtil.download();
這里有一個(gè)數(shù)據(jù)庫(kù)操作的接口ThreadDAO,內(nèi)部有增刪改查等邏輯,用于記錄下載任務(wù)的信息。自定義一個(gè)ThreadDAOImpl類將這里的邏輯實(shí)現(xiàn),內(nèi)部數(shù)據(jù)庫(kù)創(chuàng)建關(guān)于繼承SQLiteOpenHelper的自定義類的邏輯就不貼了,比較簡(jiǎn)單,該類會(huì)在ThreadDAOImpl類的構(gòu)造方法中創(chuàng)建實(shí)例。完成底層數(shù)據(jù)庫(kù)操作的封裝。
public interface ThreadDAO {
//插入一條數(shù)據(jù)
public void insert(FileInfo info);
//根據(jù)URL刪除一條數(shù)據(jù)
public void delete(String url);
//根據(jù)URL更新一條進(jìn)度
public void update(String url,int finished);
//根據(jù)URL找到一條數(shù)據(jù)
public List<FileInfo> get(String url);
//是否存在
public boolean isExits(String url);
}
5. 具體的文件下載邏輯
public class DownLoadUtil {
//構(gòu)造方法略
public void download(){
List<FileInfo> lists = threadDAO.get(fileInfo.getUrl());
FileInfo info = null;
if(lists.size() == 0){
//第一次下載,創(chuàng)建子線程下載
new MyThread(fileInfo).start();
}else{
//中間開(kāi)始的
info = lists.get(0);
new MyThread(info).start();
}
}
class MyThread extends Thread{
private FileInfo info = null;
public MyThread(FileInfo threadInfo) {
this.info = threadInfo;
}
@Override
public void run() {
//向數(shù)據(jù)庫(kù)添加線程信息
if(!threadDAO.isExits(info.getUrl())){
threadDAO.insert(info);
}
HttpURLConnection urlConnection = null;
RandomAccessFile randomFile =null;
InputStream inputStream = null;
try {
URL url = new URL(info.getUrl());
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setConnectTimeout(3000);
urlConnection.setRequestMethod("GET");
//設(shè)置下載位置
int start = info.getStart() + info.getNow();
urlConnection.setRequestProperty("Range","bytes=" + start + "-" + info.getLength());
//設(shè)置文件寫(xiě)入位置
File file = new File(DOWNLOAD_PATH,FILE_NAME);
randomFile = new RandomAccessFile(file, "rwd");
randomFile.seek(start);
//向Activity發(fā)廣播
Intent intent = new Intent(ACTION_UPDATE);
finished += info.getNow();
if (urlConnection.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT) {
//獲得文件流
inputStream = urlConnection.getInputStream();
byte[] buffer = new byte[512];
int len = -1;
long time = System.currentTimeMillis();
while ((len = inputStream.read(buffer))!= -1){
//寫(xiě)入文件
randomFile.write(buffer,0,len);
//把進(jìn)度發(fā)送給Activity
finished += len;
//看時(shí)間間隔,時(shí)間間隔大于500ms再發(fā)
if(System.currentTimeMillis() - time >500){
time = System.currentTimeMillis();
intent.putExtra("now",finished *100 /fileInfo.getLength());
context.sendBroadcast(intent);
}
//判斷是否是暫停狀態(tài)
if(isPause){
threadDAO.update(info.getUrl(),finished);
return; //結(jié)束循環(huán)
}
}
//刪除線程信息
threadDAO.delete(info.getUrl());
}
}catch (Exception e){
e.printStackTrace();
}finally {//回收工作略
}
}
}
}
上面也講到使用自定義的DownLoadUtil類進(jìn)行具體的文件下載邏輯,這也是最關(guān)鍵的部分了,在該類的構(gòu)造方法中進(jìn)行ThreadDAOImpl實(shí)例的創(chuàng)建。并在download()中通過(guò)數(shù)據(jù)庫(kù)查詢的操作,判斷是否是第一次開(kāi)始下載任務(wù),如果是,則開(kāi)啟一個(gè)子線程MyThread進(jìn)行下載任務(wù),否則將進(jìn)度信息從數(shù)據(jù)庫(kù)中取出,并將該信息傳遞給MyThread。
在MyThread中,通過(guò)info.getStart() + info.getNow()設(shè)置開(kāi)始下載的位置,如果是第一次下載兩個(gè)數(shù)將都是0,如果是暫停后再下載,則info.getNow()會(huì)取出非0值,該值來(lái)自數(shù)據(jù)庫(kù)存儲(chǔ)。使用setRequestProperty告知服務(wù)器從哪里開(kāi)始傳遞數(shù)據(jù),傳遞到哪里結(jié)束,本地使用RandomAccessFile的seek()方法進(jìn)行數(shù)據(jù)的本地存儲(chǔ)。使用廣播將進(jìn)度的百分比傳遞給Activity,Activity再改變ProcessBar進(jìn)行UI調(diào)整。
這里很關(guān)鍵的一點(diǎn)是在用戶點(diǎn)擊暫停后會(huì)在Service中調(diào)用downLoadUtil.isPause = true,因此上面while循環(huán)會(huì)結(jié)束,停止下載并通過(guò)數(shù)據(jù)庫(kù)的update()保存進(jìn)度值。從而在續(xù)傳時(shí)取出該值,重新對(duì)服務(wù)器發(fā)起文件起始點(diǎn)的下載任務(wù)請(qǐng)求,同時(shí)也在本地文件的相應(yīng)位置繼續(xù)寫(xiě)入操作。
6. 效果如下所示

感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
- 詳解Android使用OKHttp3實(shí)現(xiàn)下載(斷點(diǎn)續(xù)傳、顯示進(jìn)度)
- android實(shí)現(xiàn)多線程下載文件(支持暫停、取消、斷點(diǎn)續(xù)傳)
- android使用OkHttp實(shí)現(xiàn)下載的進(jìn)度監(jiān)聽(tīng)和斷點(diǎn)續(xù)傳
- Android FTP 多線程斷點(diǎn)續(xù)傳下載\上傳的實(shí)例
- Android多線程斷點(diǎn)續(xù)傳下載功能實(shí)現(xiàn)代碼
- Android多線程+單線程+斷點(diǎn)續(xù)傳+進(jìn)度條顯示下載功能
- Android 斷點(diǎn)續(xù)傳的原理剖析與實(shí)例講解
- Android實(shí)現(xiàn)網(wǎng)絡(luò)多線程斷點(diǎn)續(xù)傳下載實(shí)例
- Android編程開(kāi)發(fā)實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳下載器實(shí)例
- Android快速實(shí)現(xiàn)斷點(diǎn)續(xù)傳的方法
相關(guān)文章
設(shè)置Android設(shè)備WIFI在休眠時(shí)永不斷開(kāi)的代碼實(shí)現(xiàn)
這篇文章主要介紹了設(shè)置Android設(shè)備WIFI在休眠時(shí)永不斷開(kāi)的代碼實(shí)現(xiàn),需要的朋友可以參考下2014-07-07
Flutter實(shí)現(xiàn)頁(yè)面切換后保持原頁(yè)面狀態(tài)的3種方法
這篇文章主要給大家介紹了關(guān)于Flutter實(shí)現(xiàn)頁(yè)面切換后保持原頁(yè)面狀態(tài)的3種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Flutter具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
android 使用okhttp可能引發(fā)OOM的一個(gè)點(diǎn)
這篇文章主要介紹了android 使用okhttp可能引發(fā)OOM的一個(gè)點(diǎn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10
Android通過(guò)ksoap2傳遞復(fù)雜數(shù)據(jù)類型及CXF發(fā)布的webservice詳細(xì)介紹
這篇文章主要介紹了 Android通過(guò)ksoap2傳遞復(fù)雜數(shù)據(jù)類型詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2017-02-02
Android入門(mén)之使用OKHttp多線程下載文件
OkHttp是一個(gè)神器。OkHttp分為異步、同步兩種調(diào)用。今天我們就會(huì)基于OkHttp的異步調(diào)用實(shí)現(xiàn)一個(gè)多線程并行下載文件并以進(jìn)度條展示總進(jìn)度的實(shí)用例子,需要的可以參考一下2023-01-01
Android點(diǎn)擊按鈕返回頂部實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了Android返回頂部實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02

