Android 斷點(diǎn)續(xù)傳原理以及實(shí)現(xiàn)
Android 斷點(diǎn)續(xù)傳原理以及實(shí)現(xiàn)
0. 前言
在Android開發(fā)中,斷點(diǎn)續(xù)傳聽起來挺容易,在下載一個文件時點(diǎn)擊暫停任務(wù)暫停,點(diǎn)擊開始會繼續(xù)下載文件。但是真正實(shí)現(xiàn)起來知識點(diǎn)還是蠻多的,因此今天有時間實(shí)現(xiàn)了一下,并進(jìn)行記錄。
1. 斷點(diǎn)續(xù)傳原理
在本地下載過程中要使用數(shù)據(jù)庫實(shí)時存儲到底存儲到文件的哪個位置了,這樣點(diǎn)擊開始繼續(xù)傳遞時,才能通過HTTP的GET請求中的setRequestProperty()方法可以告訴服務(wù)器,數(shù)據(jù)從哪里開始,到哪里結(jié)束。同時在本地的文件寫入時,RandomAccessFile的seek()方法也支持在文件中的任意位置進(jìn)行寫入操作。同時通過廣播將子線程的進(jìn)度告訴Activity的ProcessBar。
2. Activity的按鈕響應(yīng)
當(dāng)點(diǎn)擊開始按鈕時,將url寫在了FileInfo類的對象info中并通過Intent從Activity傳遞到了Service中。這里使用setAction()來區(qū)分是開始按鈕還是暫停按鈕。
public class FileInfo implements Serializable{ private String url; //URL private int length; //長度或結(jié)束位置 private int start; //開始位置 private int now;//當(dāng)前進(jìn)度 //構(gòu)造方法,set/get略 } //開始按鈕邏輯,停止邏輯大致相同 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對象從Intent中取出,如果是開始命令,則開啟一個線程,根據(jù)該url去獲得要下載文件的大小,將該大小寫入對象并通過Handler傳回Service,同時在本地創(chuàng)建一個相同大小的本地文件。暫停命令最后會講到。
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) { //獲得文件長度 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); //長度給fileInfo對象 fileInfo.setLength(length); //通過Handler將對象傳遞給Service mHandle.obtainMessage(0, fileInfo).sendToTarget(); } catch (Exception e) { e.printStackTrace(); } finally { //流的回收邏輯略 } } }
4. 數(shù)據(jù)庫操作封裝
在Service的handleMessage()方法中拿到有l(wèi)ength屬性的FileInfo對象,并使用自定義的DownLoadUtil類進(jìn)行具體的文件下載邏輯。這里傳入上下文,因?yàn)閿?shù)據(jù)庫處理操作需要用到。
downLoadUtil = new DownLoadUtil(DownLoadService.this,info); downLoadUtil.download();
這里有一個數(shù)據(jù)庫操作的接口ThreadDAO,內(nèi)部有增刪改查等邏輯,用于記錄下載任務(wù)的信息。自定義一個ThreadDAOImpl類將這里的邏輯實(shí)現(xiàn),內(nèi)部數(shù)據(jù)庫創(chuàng)建關(guān)于繼承SQLiteOpenHelper的自定義類的邏輯就不貼了,比較簡單,該類會在ThreadDAOImpl類的構(gòu)造方法中創(chuàng)建實(shí)例。完成底層數(shù)據(jù)庫操作的封裝。
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{ //中間開始的 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ù)庫添加線程信息 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è)置文件寫入位置 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){ //寫入文件 randomFile.write(buffer,0,len); //把進(jìn)度發(fā)送給Activity finished += len; //看時間間隔,時間間隔大于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()中通過數(shù)據(jù)庫查詢的操作,判斷是否是第一次開始下載任務(wù),如果是,則開啟一個子線程MyThread進(jìn)行下載任務(wù),否則將進(jìn)度信息從數(shù)據(jù)庫中取出,并將該信息傳遞給MyThread。
在MyThread中,通過info.getStart() + info.getNow()設(shè)置開始下載的位置,如果是第一次下載兩個數(shù)將都是0,如果是暫停后再下載,則info.getNow()會取出非0值,該值來自數(shù)據(jù)庫存儲。使用setRequestProperty告知服務(wù)器從哪里開始傳遞數(shù)據(jù),傳遞到哪里結(jié)束,本地使用RandomAccessFile的seek()方法進(jìn)行數(shù)據(jù)的本地存儲。使用廣播將進(jìn)度的百分比傳遞給Activity,Activity再改變ProcessBar進(jìn)行UI調(diào)整。
這里很關(guān)鍵的一點(diǎn)是在用戶點(diǎn)擊暫停后會在Service中調(diào)用downLoadUtil.isPause = true,因此上面while循環(huán)會結(jié)束,停止下載并通過數(shù)據(jù)庫的update()保存進(jìn)度值。從而在續(xù)傳時取出該值,重新對服務(wù)器發(fā)起文件起始點(diǎn)的下載任務(wù)請求,同時也在本地文件的相應(yīng)位置繼續(xù)寫入操作。
6. 效果如下所示
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
- 詳解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)聽和斷點(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編程開發(fā)實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳下載器實(shí)例
- Android快速實(shí)現(xiàn)斷點(diǎn)續(xù)傳的方法
相關(guān)文章
設(shè)置Android設(shè)備WIFI在休眠時永不斷開的代碼實(shí)現(xiàn)
這篇文章主要介紹了設(shè)置Android設(shè)備WIFI在休眠時永不斷開的代碼實(shí)現(xiàn),需要的朋友可以參考下2014-07-07Flutter實(shí)現(xiàn)頁面切換后保持原頁面狀態(tài)的3種方法
這篇文章主要給大家介紹了關(guān)于Flutter實(shí)現(xiàn)頁面切換后保持原頁面狀態(tài)的3種方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者使用Flutter具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03android 使用okhttp可能引發(fā)OOM的一個點(diǎn)
這篇文章主要介紹了android 使用okhttp可能引發(fā)OOM的一個點(diǎn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10Android通過ksoap2傳遞復(fù)雜數(shù)據(jù)類型及CXF發(fā)布的webservice詳細(xì)介紹
這篇文章主要介紹了 Android通過ksoap2傳遞復(fù)雜數(shù)據(jù)類型詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2017-02-02Android點(diǎn)擊按鈕返回頂部實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了Android返回頂部實(shí)現(xiàn)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-02-02