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類(lèi)的對(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類(lèi)進(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類(lèi)將這里的邏輯實(shí)現(xiàn),內(nèi)部數(shù)據(jù)庫(kù)創(chuàng)建關(guān)于繼承SQLiteOpenHelper的自定義類(lèi)的邏輯就不貼了,比較簡(jiǎn)單,該類(lèi)會(huì)在ThreadDAOImpl類(lèi)的構(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類(lèi)進(jìn)行具體的文件下載邏輯,這也是最關(guān)鍵的部分了,在該類(lèi)的構(gòu)造方法中進(jìn)行ThreadDAOImpl實(shí)例的創(chuàng)建。并在download()中通過(guò)數(shù)據(jù)庫(kù)查詢(xún)的操作,判斷是否是第一次開(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-07Flutter實(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-03android 使用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-10Android通過(guò)ksoap2傳遞復(fù)雜數(shù)據(jù)類(lèi)型及CXF發(fā)布的webservice詳細(xì)介紹
這篇文章主要介紹了 Android通過(guò)ksoap2傳遞復(fù)雜數(shù)據(jù)類(lèi)型詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2017-02-02Android入門(mén)之使用OKHttp多線程下載文件
OkHttp是一個(gè)神器。OkHttp分為異步、同步兩種調(diào)用。今天我們就會(huì)基于OkHttp的異步調(diào)用實(shí)現(xiàn)一個(gè)多線程并行下載文件并以進(jìn)度條展示總進(jìn)度的實(shí)用例子,需要的可以參考一下2023-01-01Android點(diǎn)擊按鈕返回頂部實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了Android返回頂部實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02