Android Okhttp斷點續(xù)傳面試深入解析
前言
我們在刷一下面試題的時候,有時候會看到一些大廠會問關(guān)于斷點續(xù)傳的原理,那么今天在這里從 HTTP 斷點續(xù)傳知識和 Android 中如何實現(xiàn)斷點續(xù)傳的思路來做一個關(guān)于 Android 斷點續(xù)傳原理的總結(jié)。
Http 斷點續(xù)傳知識點
什么是斷點續(xù)傳
指的是在上傳/下載時,將任務(wù)(一個文件或壓縮包)人為的劃分為幾個部分,每一個部分采用一個線程進行上傳/下載,如果碰到網(wǎng)絡(luò)故障,可以從已經(jīng)上傳/下載的部分開始繼續(xù)上傳/下載未完成的部分,而沒有必要從頭開始上傳/下載??梢怨?jié)省時間,提高速度。
Http 怎么支持斷點續(xù)傳的?
Http 1.1 協(xié)議中默認支持獲取文件的部分內(nèi)容,這其中主要是通過頭部的兩個參數(shù):Range 和 Content Range 來實現(xiàn)的??蛻舳税l(fā)請求時對應(yīng)的是 Range ,服務(wù)器端響應(yīng)時對應(yīng)的是 Content-Range。
Range
客戶端想要獲取文件的部分內(nèi)容,那么它就需要請求頭部中的 Range 參數(shù)中指定獲取內(nèi)容的起始字節(jié)的位置和終止字節(jié)的位置,它的格式一般為:
Range:(unit=first byte pos)-[last byte pos]
例如:
Range: bytes=0-499 表示第 0-499 字節(jié)范圍的內(nèi)容
Range: bytes=500-999 表示第 500-999 字節(jié)范圍的內(nèi)容
Range: bytes=-500 表示最后 500 字節(jié)的內(nèi)容
Range: bytes=500- 表示從第 500 字節(jié)開始到文件結(jié)束部分的內(nèi)容
Range: bytes=0-0,-1 表示第一個和最后一個字節(jié)
Range: bytes=500-600,601-999 同時指定幾個范圍
Content Range
在收到客戶端中攜帶 Range 的請求后,服務(wù)器會在響應(yīng)的頭部中添加 Content Range 參數(shù),返回可接受的文件字節(jié)范圍及其文件的總大小。它的格式如下:
Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth]
例如:
Content-Range: bytes 0-499/22400 // 0-499 是指當前發(fā)送的數(shù)據(jù)的范圍,而 22400 則是文件的總大小。
使用斷點續(xù)傳和不使用斷點續(xù)傳的響應(yīng)內(nèi)容區(qū)別
不使用斷點續(xù)傳
HTTP/1.1 200 Ok
使用斷點續(xù)傳
HTTP/1.1 206 Partial Content
處理請求資源發(fā)生改變的問題
在現(xiàn)實的場景中,服務(wù)器中的文件是會有發(fā)生變化的情況的,那么我們發(fā)起續(xù)傳的請求肯定是失敗的,那么為了處理這種服務(wù)器文件資源發(fā)生改變的問題,在 RFC2616 中定義了 Last-Modified 和 Etag 來判斷續(xù)傳文件資源是否發(fā)生改變。
Last-Modified & If-Modified-Since(文件最后修改時間)
Last-Modified:記錄 Http 頁面最后修改時間的 Http 頭部參數(shù),Last-Modified 是由服務(wù)端發(fā)送給客戶端的
If-Modified-Since:記錄 Http 頁面最后修改時間的 Http 頭部參數(shù),If-Modified-Since 是有客戶端發(fā)送給服務(wù)端的
驗證過程
- step 1:客戶端緩存從服務(wù)端獲取的頁面
- step 1:客戶端訪問相同頁面時,客戶端將服務(wù)器發(fā)送過來的 Last-Modified 通過 If-Modified-Since 發(fā)送給服務(wù)器
- step 2:服務(wù)器通過客戶端發(fā)送過來的 If-Modified-Since 進行判斷客戶端當前的緩存的頁面是否為最新的
- 如果不是最新的,那么就發(fā)送最新的頁面給客戶端
- 如果是最新的,那么就發(fā)送 304 告訴客戶端它本地緩存的頁面是最新的
Etag & if-Range(文件唯一標志)
Etag:作為文件的唯一標志,這個標志可以是文件的 hash 值或者是一個版本
if-Range:用于判斷實體是否發(fā)生改變,如果實體未改變,服務(wù)器發(fā)送客戶端丟失的部分,否則發(fā)送整個實體。一般格式:
If-Range: Etag | HTTP-Date
If-Range 可以使用 Etag 或者 Last-Modified 返回的值。當沒有 ETage 卻有 Last-modified 時,可以把 Last-modified 作為 If-Range 字段的值
驗證過程
- step 1:客戶端發(fā)起續(xù)傳請求,頭部包含 Range 和 if-Range 參數(shù)
- step 2:服務(wù)器中收到客戶端的請求之后,將客戶端和服務(wù)器的 Etag 進行比對
- 相等:請求文件資源沒有發(fā)生變化,應(yīng)答報文為 206
- 不相等:請求文件資源發(fā)生變化,應(yīng)答報文為 200
檢查服務(wù)器是否支持斷點續(xù)傳
我們使用 curl 進行檢測,可以看出以下的幾個關(guān)鍵信息
- HTTP/1.1 206 Partial Content
- Content-Range: bytes 10-222/7877
- Etag: "1ec5-502264e2ae4c0"
- Last-Modified: Wed, 03 Sep 2014 10:00:27 GMT
OkHttp 斷點下載
斷點下載思路
- step 1:判斷檢查本地是否有下載文件,若存在,則獲取已下載的文件大小 downloadLength,若不存在,那么本地已下載文件的長度為 0
- step 2:獲取將要下載的文件總大?。℉TTP 響應(yīng)頭部的 content-Length)
- step 3:比對已下載文件大小和將要下載的文件總大小(contentLength),判斷要下載的長度
- step 4:再即將發(fā)起下載請求的 HTTP 頭部中添加即將下載的文件大小范圍(Range: bytes = downloadLength - contentLength)
Okhttp 簡單短斷點下載代碼示例
DownloadTask.java
/** * String 在執(zhí)行AsyncTask時需要傳入的參數(shù),可用于在后臺任務(wù)中使用。 * Integer 后臺任務(wù)執(zhí)行時,如果需要在界面上顯示當前的進度,則使用這里指定的泛型作為進度單位。 * Integer 當任務(wù)執(zhí)行完畢后,如果需要對結(jié)果進行返回,則使用這里指定的泛型作為返回值類型。 */ public class DownloadTask extends AsyncTask<String, Integer, Integer> { public static final int TYPE_SUCCESS = 0; public static final int TYPE_FAILED = 1; public static final int TYPE_PAUSED = 2; public static final int TYPE_CANCELED = 3; private DownloadListener listener; private boolean isCanceled = false; private boolean isPaused = false; private int lastProgress; public DownloadTask(DownloadListener listener) { this.listener = listener; } /** * 這個方法中的所有代碼都會在子線程中運行,我們應(yīng)該在這里處理所有的耗時任務(wù)。 * * @param params * @return */ @Override protected Integer doInBackground(String... params) { InputStream is = null; RandomAccessFile savedFile = null; File file = null; long downloadLength = 0; //記錄已經(jīng)下載的文件長度 //文件下載地址 String downloadUrl = params[0]; //下載文件的名稱 String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/")); //下載文件存放的目錄 String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath(); //創(chuàng)建一個文件 file = new File(directory + fileName); if (file.exists()) { //如果文件存在的話,得到文件的大小 downloadLength = file.length(); } //得到下載內(nèi)容的大小 long contentLength = getContentLength(downloadUrl); if (contentLength == 0) { return TYPE_FAILED; } else if (contentLength == downloadLength) { //已下載字節(jié)和文件總字節(jié)相等,說明已經(jīng)下載完成了 return TYPE_SUCCESS; } OkHttpClient client = new OkHttpClient(); /** * HTTP請求是有一個Header的,里面有個Range屬性是定義下載區(qū)域的,它接收的值是一個區(qū)間范圍, * 比如:Range:bytes=0-10000。這樣我們就可以按照一定的規(guī)則,將一個大文件拆分為若干很小的部分, * 然后分批次的下載,每個小塊下載完成之后,再合并到文件中;這樣即使下載中斷了,重新下載時, * 也可以通過文件的字節(jié)長度來判斷下載的起始點,然后重啟斷點續(xù)傳的過程,直到最后完成下載過程。 */ Request request = new Request.Builder() .addHeader("RANGE", "bytes=" + downloadLength + "-" + contentLength) //斷點續(xù)傳要用到的,指示下載的區(qū)間 .url(downloadUrl) .build(); try { Response response = client.newCall(request).execute(); if (response != null) { is = response.body().byteStream(); savedFile = new RandomAccessFile(file, "rw"); savedFile.seek(downloadLength);//跳過已經(jīng)下載的字節(jié) byte[] b = new byte[1024]; int total = 0; int len; while ((len = is.read(b)) != -1) { if (isCanceled) { return TYPE_CANCELED; } else if (isPaused) { return TYPE_PAUSED; } else { total += len; savedFile.write(b, 0, len); //計算已經(jīng)下載的百分比 int progress = (int) ((total + downloadLength) * 100 / contentLength); //注意:在doInBackground()中是不可以進行UI操作的,如果需要更新UI,比如說反饋當前任務(wù)的執(zhí)行進度, //可以調(diào)用publishProgress()方法完成。 publishProgress(progress); } } response.body().close(); return TYPE_SUCCESS; } } catch (IOException e) { e.printStackTrace(); } finally { try { if (is != null) { is.close(); } if (savedFile != null) { savedFile.close(); } if (isCanceled && file != null) { file.delete(); } } catch (Exception e) { e.printStackTrace(); } } return TYPE_FAILED; } /** * 當在后臺任務(wù)中調(diào)用了publishProgress(Progress...)方法之后,onProgressUpdate()方法 * 就會很快被調(diào)用,該方法中攜帶的參數(shù)就是在后臺任務(wù)中傳遞過來的。在這個方法中可以對UI進行操作,利用參數(shù)中的數(shù)值就可以 * 對界面進行相應(yīng)的更新。 * * @param values */ @Override protected void onProgressUpdate(Integer... values) { int progress = values[0]; if (progress > lastProgress) { listener.onProgress(progress); lastProgress = progress; } } /** * 當后臺任務(wù)執(zhí)行完畢并通過Return語句進行返回時,這個方法就很快被調(diào)用。返回的數(shù)據(jù)會作為參數(shù) * 傳遞到此方法中,可以利用返回的數(shù)據(jù)來進行一些UI操作。 * * @param status */ @Override protected void onPostExecute(Integer status) { switch (status) { case TYPE_SUCCESS: listener.onSuccess(); break; case TYPE_FAILED: listener.onFailed(); break; case TYPE_PAUSED: listener.onPaused(); break; case TYPE_CANCELED: listener.onCanceled(); break; default: break; } } public void pauseDownload() { isPaused = true; } public void cancelDownload() { isCanceled = true; } /** * 得到下載內(nèi)容的完整大小 * * @param downloadUrl * @return */ private long getContentLength(String downloadUrl) { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder().url(downloadUrl).build(); try { Response response = client.newCall(request).execute(); if (response != null && response.isSuccessful()) { long contentLength = response.body().contentLength(); response.body().close(); return contentLength; } } catch (IOException e) { e.printStackTrace(); } return 0; } }
DownloadListener.java
public class DownloadListener { /** * 通知當前的下載進度 * @param progress */ void onProgress(int progress); /** * 通知下載成功 */ void onSuccess(); /** * 通知下載失敗 */ void onFailed(); /** * 通知下載暫停 */ void onPaused(); /** * 通知下載取消事件 */ void onCanceled(); }
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,謝謝大家對腳本之家的支持。
- OkHttp攔截器在Android網(wǎng)絡(luò)中的使用和工作原理
- Android入門之使用OKHttp多線程下載文件
- Android 使用 okhttp3和retrofit2 進行單文件和多文件上傳
- Android基于OkHttp實現(xiàn)文件上傳功能
- Android使用OKhttp3實現(xiàn)登錄注冊功能+springboot搭建后端的詳細過程
- Android的簡單前后端交互(okHttp+springboot+mysql)
- Android使用OkHttp發(fā)送post請求
- Android使用OkHttp進行網(wǎng)絡(luò)同步異步操作
- Android視頻/音頻緩存框架AndroidVideoCache(Okhttp)詳解
- Android OkHttp實現(xiàn)全局過期token自動刷新示例
- OkHttp原理分析小結(jié)
相關(guān)文章
Android編程實現(xiàn)應(yīng)用程序開機自啟動的方法
這篇文章主要介紹了Android編程實現(xiàn)應(yīng)用程序開機自啟動的方法,涉及Android權(quán)限控制及廣播操作相關(guān)技巧,需要的朋友可以參考下2017-02-02Android Chronometer控件實現(xiàn)計時器函數(shù)詳解
這篇文章主要為大家詳細介紹了Android Chronometer控件實現(xiàn)計時器函數(shù),具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-04-04詳解Android使用OKHttp3實現(xiàn)下載(斷點續(xù)傳、顯示進度)
本篇文章主要介紹了詳解Android使用OKHttp3實現(xiàn)下載(斷點續(xù)傳、顯示進度),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02Android音頻處理之通過AudioRecord去保存PCM文件進行錄制,播放,停止,刪除功能
這篇文章主要介紹了Android音頻處理之通過AudioRecord去保存PCM文件進行錄制,播放,停止,刪除功能的相關(guān)資料,需要的朋友可以參考下2016-11-11android開發(fā)中ListView與Adapter使用要點介紹
項目用到ListView,由于要用到 ImageView ,圖片源不是在資源里面的,沒法使用資源 ID,因此無法直接使用SimpleAdapter,要自己寫一個Adapter。 在使用ListView和Adapter需要注意以下幾點2013-06-06Android程序開發(fā)之防止密碼輸入錯誤 密碼明文顯示功能
在使用App的時候,首次登錄都需要用戶輸入密碼的,有些朋友為了安全起見密碼設(shè)置的比較長,導(dǎo)致很多次密碼都輸入錯誤,嚴重影響了用戶體驗效果,下面通過本文給大家介紹Android程序開發(fā)之防止密碼輸入錯誤 密碼明文顯示功能,需要的朋友參考下2016-02-02