成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Android Okhttp 斷點(diǎn)續(xù)傳面試解析

ACb0y / 3070人閱讀

摘要:怎么支持?jǐn)帱c(diǎn)續(xù)傳的協(xié)議中默認(rèn)支持獲取文件的部分內(nèi)容,這其中主要是通過(guò)頭部的兩個(gè)參數(shù)和來(lái)實(shí)現(xiàn)的。

我們?cè)谒⒁幌旅嬖囶}的時(shí)候,有時(shí)候會(huì)看到一些大廠會(huì)問(wèn)關(guān)于斷點(diǎn)續(xù)傳的原理,那么今天在這里從 HTTP 斷點(diǎn)續(xù)傳知識(shí)和 Android 中如何實(shí)現(xiàn)斷點(diǎn)續(xù)傳的思路來(lái)做一個(gè)關(guān)于 Android 斷點(diǎn)續(xù)傳原理的總結(jié)。

Http 斷點(diǎn)續(xù)傳知識(shí)點(diǎn) 什么是斷點(diǎn)續(xù)傳

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

Http 怎么支持?jǐn)帱c(diǎn)續(xù)傳的?

Http 1.1 協(xié)議中默認(rèn)支持獲取文件的部分內(nèi)容,這其中主要是通過(guò)頭部的兩個(gè)參數(shù):Range 和 Content Range 來(lái)實(shí)現(xiàn)的。客戶端發(fā)請(qǐng)求時(shí)對(duì)應(yīng)的是 Range ,服務(wù)器端響應(yīng)時(shí)對(duì)應(yīng)的是 Content-Range。

Range

客戶端想要獲取文件的部分內(nèi)容,那么它就需要請(qǐng)求頭部中的 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     表示第一個(gè)和最后一個(gè)字節(jié) 
Range: bytes=500-600,601-999 同時(shí)指定幾個(gè)范圍
Content Range

在收到客戶端中攜帶 Range 的請(qǐng)求后,服務(wù)器會(huì)在響應(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 是指當(dāng)前發(fā)送的數(shù)據(jù)的范圍,而 22400 則是文件的總大小。
使用斷點(diǎn)續(xù)傳和不使用斷點(diǎn)續(xù)傳的響應(yīng)內(nèi)容區(qū)別

不使用斷點(diǎn)續(xù)傳

HTTP/1.1 200 Ok

使用斷點(diǎn)續(xù)傳

HTTP/1.1 206 Partial Content
處理請(qǐng)求資源發(fā)生改變的問(wèn)題

在現(xiàn)實(shí)的場(chǎng)景中,服務(wù)器中的文件是會(huì)有發(fā)生變化的情況的,那么我們發(fā)起續(xù)傳的請(qǐng)求肯定是失敗的,那么為了處理這種服務(wù)器文件資源發(fā)生改變的問(wèn)題,在 RFC2616 中定義了 Last-ModifiedEtag 來(lái)判斷續(xù)傳文件資源是否發(fā)生改變。

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

Last-Modified:記錄 Http 頁(yè)面最后修改時(shí)間的 Http 頭部參數(shù),Last-Modified 是由服務(wù)端發(fā)送給客戶端的

If-Modified-Since:記錄 Http 頁(yè)面最后修改時(shí)間的 Http 頭部參數(shù),If-Modified-Since 是有客戶端發(fā)送給客戶端的

驗(yàn)證過(guò)程

step 1:客戶端緩存從服務(wù)端獲取的頁(yè)面

step 1:客戶端訪問(wèn)相同頁(yè)面時(shí),客戶端將服務(wù)器發(fā)送過(guò)來(lái)的 Last-Modified 通過(guò) If-Modified-Since 發(fā)送給服務(wù)器

step 2:服務(wù)器通過(guò)客戶端發(fā)送過(guò)來(lái)的 If-Modified-Since 進(jìn)行判斷客戶端當(dāng)前的緩存的頁(yè)面是否為最新的

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

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

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

Etag:作為文件的唯一標(biāo)志,這個(gè)標(biāo)志可以是文件的 hash 值或者是一個(gè)版本

if-Range:用于判斷實(shí)體是否發(fā)生改變,如果實(shí)體未改變,服務(wù)器發(fā)送客戶端丟失的部分,否則發(fā)送整個(gè)實(shí)體。一般格式:

If-Range: Etag | HTTP-Date

If-Range 可以使用 Etag 或者 Last-Modified 返回的值。當(dāng)沒(méi)有 ETage 卻有 Last-modified 時(shí),可以把 Last-modified 作為 If-Range 字段的值

驗(yàn)證過(guò)程

step 1:客戶端發(fā)起續(xù)傳請(qǐng)求,頭部包含 Range 和 if-Range 參數(shù)

step 2:服務(wù)器中收到客戶端的請(qǐng)求之后,將客戶端和服務(wù)器的 Etag 進(jìn)行比對(duì)

相等:請(qǐng)求文件資源沒(méi)有發(fā)生變化,應(yīng)答報(bào)文為 206

不相等:請(qǐng)求文件資源發(fā)生變化,應(yīng)答報(bào)文為 200

檢查服務(wù)器是否支持?jǐn)帱c(diǎn)續(xù)傳

我們使用 curl 進(jìn)行檢測(cè),可以看出以下的幾個(gè)關(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 斷點(diǎn)下載 斷點(diǎn)下載思路

step 1:判斷檢查本地是否有下載文件,若存在,則獲取已下載的文件大小 downloadLength,若不存在,那么本地已下載文件的長(zhǎng)度為 0

step 2:獲取將要下載的文件總大小(HTTP 響應(yīng)頭部的 content-Length)

step 3:比對(duì)已下載文件大小和將要下載的文件總大?。╟ontentLength),判斷要下載的長(zhǎng)度

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

Okhttp 簡(jiǎn)單短斷點(diǎn)下載代碼示例

DownloadTask.java

/**
 * String 在執(zhí)行AsyncTask時(shí)需要傳入的參數(shù),可用于在后臺(tái)任務(wù)中使用。
 * Integer 后臺(tái)任務(wù)執(zhí)行時(shí),如果需要在界面上顯示當(dāng)前的進(jìn)度,則使用這里指定的泛型作為進(jìn)度單位。
 * Integer 當(dāng)任務(wù)執(zhí)行完畢后,如果需要對(duì)結(jié)果進(jìn)行返回,則使用這里指定的泛型作為返回值類型。
 */
public class DownloadTask extends AsyncTask {

    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;
    }

    /**
     * 這個(gè)方法中的所有代碼都會(huì)在子線程中運(yùn)行,我們應(yīng)該在這里處理所有的耗時(shí)任務(wù)。
     *
     * @param params
     * @return
     */
    @Override
    protected Integer doInBackground(String... params) {
        InputStream is = null;
        RandomAccessFile savedFile = null;
        File file = null;
        long downloadLength = 0;   //記錄已經(jīng)下載的文件長(zhǎng)度
        //文件下載地址
        String downloadUrl = params[0];
        //下載文件的名稱
        String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
        //下載文件存放的目錄
        String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
        //創(chuàng)建一個(gè)文件
        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é)相等,說(shuō)明已經(jīng)下載完成了
            return TYPE_SUCCESS;
        }
        OkHttpClient client = new OkHttpClient();
        /**
         * HTTP請(qǐng)求是有一個(gè)Header的,里面有個(gè)Range屬性是定義下載區(qū)域的,它接收的值是一個(gè)區(qū)間范圍,
         * 比如:Range:bytes=0-10000。這樣我們就可以按照一定的規(guī)則,將一個(gè)大文件拆分為若干很小的部分,
         * 然后分批次的下載,每個(gè)小塊下載完成之后,再合并到文件中;這樣即使下載中斷了,重新下載時(shí),
         * 也可以通過(guò)文件的字節(jié)長(zhǎng)度來(lái)判斷下載的起始點(diǎn),然后重啟斷點(diǎn)續(xù)傳的過(guò)程,直到最后完成下載過(guò)程。
         */
        Request request = new Request.Builder()
                .addHeader("RANGE", "bytes=" + downloadLength + "-" + contentLength)  //斷點(diǎn)續(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);//跳過(guò)已經(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ì)算已經(jīng)下載的百分比
                        int progress = (int) ((total + downloadLength) * 100 / contentLength);
                        //注意:在doInBackground()中是不可以進(jìn)行UI操作的,如果需要更新UI,比如說(shuō)反饋當(dāng)前任務(wù)的執(zhí)行進(jìn)度,
                        //可以調(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;
    }

    /**
     * 當(dāng)在后臺(tái)任務(wù)中調(diào)用了publishProgress(Progress...)方法之后,onProgressUpdate()方法
     * 就會(huì)很快被調(diào)用,該方法中攜帶的參數(shù)就是在后臺(tái)任務(wù)中傳遞過(guò)來(lái)的。在這個(gè)方法中可以對(duì)UI進(jìn)行操作,利用參數(shù)中的數(shù)值就可以
     * 對(duì)界面進(jìn)行相應(yīng)的更新。
     *
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        int progress = values[0];
        if (progress > lastProgress) {
            listener.onProgress(progress);
            lastProgress = progress;
        }
    }

    /**
     * 當(dāng)后臺(tái)任務(wù)執(zhí)行完畢并通過(guò)Return語(yǔ)句進(jìn)行返回時(shí),這個(gè)方法就很快被調(diào)用。返回的數(shù)據(jù)會(huì)作為參數(shù)
     * 傳遞到此方法中,可以利用返回的數(shù)據(jù)來(lái)進(jìn)行一些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 {


    /**
     * 通知當(dāng)前的下載進(jìn)度
     * @param progress
     */
    void onProgress(int progress);

    /**
     * 通知下載成功
     */
    void onSuccess();

    /**
     * 通知下載失敗
     */
    void onFailed();

    /**
     * 通知下載暫停
     */
    void onPaused();

    /**
     * 通知下載取消事件
     */
    void onCanceled();

}

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/7860.html

相關(guān)文章

  • 四年來(lái)Android面試大綱,作為一個(gè)Android程序員

    摘要:再附一部分架構(gòu)面試視頻講解本文已被開源項(xiàng)目學(xué)習(xí)筆記總結(jié)移動(dòng)架構(gòu)視頻大廠面試真題項(xiàng)目實(shí)戰(zhàn)源碼收錄 Java反射(一)Java反射(二)Java反射(三)Java注解Java IO(一)Java IO(二)RandomAccessFileJava NIOJava異常詳解Java抽象類和接口的區(qū)別Java深拷貝和淺拷...

    不知名網(wǎng)友 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<