溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android Okhttp斷點續(xù)傳的示例分析

發(fā)布時間:2021-06-26 11:38:37 來源:億速云 閱讀:194 作者:小新 欄目:移動開發(fā)

這篇文章主要為大家展示了“Android Okhttp斷點續(xù)傳的示例分析”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“Android Okhttp斷點續(xù)傳的示例分析”這篇文章吧。

Http 斷點續(xù)傳知識點

什么是斷點續(xù)傳

指的是在上傳/下載時,將任務(一個文件或壓縮包)人為的劃分為幾個部分,每一個部分采用一個線程進行上傳/下載,如果碰到網(wǎng)絡故障,可以從已經(jīng)上傳/下載的部分開始繼續(xù)上傳/下載未完成的部分,而沒有必要從頭開始上傳/下載??梢怨?jié)省時間,提高速度。

Http 怎么支持斷點續(xù)傳的?

Http 1.1 協(xié)議中默認支持獲取文件的部分內容,這其中主要是通過頭部的兩個參數(shù):Range 和 Content Range 來實現(xiàn)的??蛻舳税l(fā)請求時對應的是 Range ,服務器端響應時對應的是 Content-Range。

Range

客戶端想要獲取文件的部分內容,那么它就需要請求頭部中的 Range 參數(shù)中指定獲取內容的起始字節(jié)的位置和終止字節(jié)的位置,它的格式一般為:

Range:(unit=first byte pos)-[last byte pos]

例如:

Range: bytes=0-499      表示第 0-499 字節(jié)范圍的內容
Range: bytes=500-999    表示第 500-999 字節(jié)范圍的內容
Range: bytes=-500       表示最后 500 字節(jié)的內容
Range: bytes=500-       表示從第 500 字節(jié)開始到文件結束部分的內容
Range: bytes=0-0,-1     表示第一個和最后一個字節(jié)
Range: bytes=500-600,601-999 同時指定幾個范圍

Content Range

在收到客戶端中攜帶 Range 的請求后,服務器會在響應的頭部中添加 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ù)傳的響應內容區(qū)別

不使用斷點續(xù)傳

HTTP/1.1 200 Ok

使用斷點續(xù)傳

HTTP/1.1 206 Partial Content

處理請求資源發(fā)生改變的問題

在現(xiàn)實的場景中,服務器中的文件是會有發(fā)生變化的情況的,那么我們發(fā)起續(xù)傳的請求肯定是失敗的,那么為了處理這種服務器文件資源發(fā)生改變的問題,在 RFC2616 中定義了 Last-Modified 和  Etag 來判斷續(xù)傳文件資源是否發(fā)生改變。

Last-Modified & If-Modified-Since(文件最后修改時間)

Last-Modified:記錄 Http 頁面最后修改時間的 Http 頭部參數(shù),Last-Modified 是由服務端發(fā)送給客戶端的
If-Modified-Since:記錄 Http 頁面最后修改時間的 Http 頭部參數(shù),If-Modified-Since 是有客戶端發(fā)送給服務端的
驗證過程

  • step 1:客戶端緩存從服務端獲取的頁面

  • step 1:客戶端訪問相同頁面時,客戶端將服務器發(fā)送過來的  Last-Modified 通過 If-Modified-Since 發(fā)送給服務器

  • step 2:服務器通過客戶端發(fā)送過來的 If-Modified-Since 進行判斷客戶端當前的緩存的頁面是否為最新的

    • 如果不是最新的,那么就發(fā)送最新的頁面給客戶端

    • 如果是最新的,那么就發(fā)送 304 告訴客戶端它本地緩存的頁面是最新的

Etag & if-Range(文件唯一標志)

Etag:作為文件的唯一標志,這個標志可以是文件的 hash 值或者是一個版本

if-Range:用于判斷實體是否發(fā)生改變,如果實體未改變,服務器發(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:服務器中收到客戶端的請求之后,將客戶端和服務器的 Etag 進行比對

    • 相等:請求文件資源沒有發(fā)生變化,應答報文為 206

    • 不相等:請求文件資源發(fā)生變化,應答報文為 200

檢查服務器是否支持斷點續(xù)傳

Android Okhttp斷點續(xù)傳的示例分析

我們使用 curl 進行檢測,可以看出以下的幾個關鍵信息

  • 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 響應頭部的 content-Length)

  • step 3:比對已下載文件大小和將要下載的文件總大?。╟ontentLength),判斷要下載的長度

  • step 4:再即將發(fā)起下載請求的 HTTP 頭部中添加即將下載的文件大小范圍(Range: bytes = downloadLength - contentLength)

Okhttp 簡單短斷點下載代碼示例

DownloadTask.java

/**
 * String 在執(zhí)行AsyncTask時需要傳入的參數(shù),可用于在后臺任務中使用。
 * Integer 后臺任務執(zhí)行時,如果需要在界面上顯示當前的進度,則使用這里指定的泛型作為進度單位。
 * Integer 當任務執(zhí)行完畢后,如果需要對結果進行返回,則使用這里指定的泛型作為返回值類型。
 */
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;
  }

  /**
   * 這個方法中的所有代碼都會在子線程中運行,我們應該在這里處理所有的耗時任務。
   *
   * @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();
    }
    //得到下載內容的大小
    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,比如說反饋當前任務的執(zhí)行進度,
            //可以調用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;
  }

  /**
   * 當在后臺任務中調用了publishProgress(Progress...)方法之后,onProgressUpdate()方法
   * 就會很快被調用,該方法中攜帶的參數(shù)就是在后臺任務中傳遞過來的。在這個方法中可以對UI進行操作,利用參數(shù)中的數(shù)值就可以
   * 對界面進行相應的更新。
   *
   * @param values
   */
  @Override
  protected void onProgressUpdate(Integer... values) {
    int progress = values[0];
    if (progress > lastProgress) {
      listener.onProgress(progress);
      lastProgress = progress;
    }
  }

  /**
   * 當后臺任務執(zhí)行完畢并通過Return語句進行返回時,這個方法就很快被調用。返回的數(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;
  }

  /**
   * 得到下載內容的完整大小
   *
   * @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();

}

以上是“Android Okhttp斷點續(xù)傳的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI