網絡請求框架Retrofit的下載時的進度提示

用Retrofit下載時想獲得下載的進度能夠重寫ResponseBody,和設置攔截器:java

1.設置請求方式以及Url:(註解streaming是用來下載大文件的,防止內存溢出,下載小文件就能夠不用這個註解)android

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Streaming;
import retrofit2.http.Url;

/**
 * Created by Zzm丶Fiona on 2017/7/27.
 */

public interface DownloadFileInterfaceForRetrofit {
    @Streaming 
    @GET
    Call<ResponseBody> getCall(@Url String path);

}

2.創建請求:(其中builder是OkHttpClient.Builder,其中子線程是將文件寫入本地ssh

private final String mBaseUrl = "http://hc39.aipai.com/user/";
private final String videoPath = "499/56065499/1006/card/45789929/card.mp4?l=j";

Call<ResponseBody> call = getDownloadFileInterfaceForRetrofit(mBaseUrl, builder).getCall(videoPath);
        call.enqueue(new Callback<ResponseBody>() {

            @Override
            public void onResponse(Call<ResponseBody> call, final Response<ResponseBody> response) {
                Log.i("zzmzzm", "onResponse:  thread:"+Thread.currentThread().getName());
                new Thread(){
                    @Override
                    public void run() {
                        super.run();
                        try {
                            InputStream is = response.body().byteStream();
                            File file = new File(Environment.getExternalStorageDirectory(), "12345.mp4");
                            FileOutputStream fos = new FileOutputStream(file);
                            BufferedInputStream bis = new BufferedInputStream(is);
                            byte[] buffer = new byte[1024*10];
                            int len;
                            while ((len = bis.read(buffer)) != -1) {
                                fos.write(buffer, 0, len);
                                fos.flush();
                            }
                            fos.close();
                            bis.close();
                            is.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }.start();
            }
            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Log.i("zzmzzm", "鏈接失敗!!");
            }
        });

方法getDownloadFileInterfaceForRetrofit:(其中設置client是關鍵)ide

private DownloadFileInterfaceForRetrofit getDownloadFileInterfaceForRetrofit(String mBaseUrl, OkHttpClient.Builder builder) {
    return new Retrofit
            .Builder()
            .baseUrl(mBaseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .client(builder.build()) 
            .build()
            .create(DownloadFileInterfaceForRetrofit.class);
}

3.其中設置client(builder.build())中的builder的來源:測試

OkHttpClient.Builder builder = RetrofitDownloadProgressUtil.getOkHttpClientBuilder(new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case 1:
                 ProgressBean progressBean = (ProgressBean) msg.obj;
 long total = progressBean.getTotal();//下載的文件的總字節數 long bytesReading = progressBean.getBytesReading();//目前已經讀的字節數 boolean isDone = progressBean.getIsDone();//是否下載完成
                progressBean = null;
        }
    }
});

上面的ProgressBean是用來儲存下載文件的相關信息,handler是用來傳遞信息給主線程的,由於下載的信息的獲取是在子線程中:ui

/**
 * Created by Zzm丶Fiona on 2017/7/28.
 */

public class ProgressBean {
    private long total;
    private long bytesReading;
    private boolean isDone;

    public long getTotal() {
        return total;
    }

    public void setTotal(long total) {
        this.total = total;
    }

    public long getBytesReading() {
        return bytesReading;
    }

    public void setBytesReading(long bytesReading) {
        this.bytesReading = bytesReading;
    }

    public boolean getIsDone() {
        return isDone;
    }

    public void setIsDone(boolean done) {
        isDone = done;
    }
}

其中的RetrofitDownloadProgressUtil.getOkHttpClientBuilder以下:this

import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import zzm.zzmotherthingsshouldknowfortheinterview.ProgressBean;
import zzm.zzmotherthingsshouldknowfortheinterview.ProgressResponseBody;
import zzm.zzmotherthingsshouldknowfortheinterview.RetrofitProgressListener;

/**
 * Created by Zzm丶Fiona on 2017/7/28.
 */

public class RetrofitDownloadProgressUtil {
    public static ProgressBean progressBean = new ProgressBean();//完成的時候賦值爲null,省得內存溢出

    public static OkHttpClient.Builder getOkHttpClientBuilder(final Handler handler) {
        OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
        okHttpBuilder.addNetworkInterceptor(new Interceptor() {
            @Override
            public okhttp3.Response intercept(Chain chain) throws IOException {
                okhttp3.Response originalResponse = chain.proceed(chain.request());
                return originalResponse.newBuilder().body(new ProgressResponseBody(originalResponse.body(), new RetrofitProgressListener() {
                            @Override
                            public void onProgress(long progress, long total, boolean isDone) {
                                //每次有進度的變化就會回調這個方法
                                if (null == progressBean) {
                                    progressBean = new ProgressBean();
                                }
                                progressBean.setTotal(total);
                                progressBean.setBytesReading(progress);
                                progressBean.setDone(isDone);
                                Log.i("zzmzzm", "progress:  " + "開始回調下載進度了!!");
                                handler.sendMessage(handler.obtainMessage(1, progressBean));
                            }
                        })
                ).build();
            }
        });
        return okHttpBuilder;
    }
}

上面的粉紅色的字就是關鍵其中的ProgressResponseBody就是繼承於ResponseBody:spa

import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;

/**
 * Created by Zzm丶Fiona on 2017/7/28.
 */

public class ProgressResponseBody extends ResponseBody {
    private final ResponseBody responseBody;
    private final RetrofitProgressListener retrofitProgressListener;
    private BufferedSource bufferedSource;

    public ProgressResponseBody(ResponseBody responseBody, RetrofitProgressListener retrofitProgressListener) {
        this.responseBody = responseBody;
        this.retrofitProgressListener = retrofitProgressListener;
    }

    @Override
    public MediaType contentType() {
        return responseBody.contentType();
    }

    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(getSource(responseBody.source()));
        }
        return bufferedSource;
    }

    private Source getSource(Source source) {
        return new ForwardingSource(source) {
            long totalBytesRead = 0L;

            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesReading = super.read(sink, byteCount);
                totalBytesRead += bytesReading != -1 ? bytesReading : 0;//讀取數據完成後bytesReading=-1;
                retrofitProgressListener.onProgress(totalBytesRead, contentLength(), bytesReading == -1);
                return bytesReading;
            }
        };
    }
}

就是重寫了幾個方法,讓後做爲參數設置給了OkHttpClientBuilder的攔截器。.net

 

最後總結一下,可能有點凌亂,最主要就是三點:

1.寫個類ProgressResponseBody繼承ResponseBody,而後重寫它的三個法,如上。線程

2.在重寫方法@Override public BufferedSource source() {}中調用本身寫的接口,方便回調後能夠獲得下載進度數據,如上面提供的代碼所示retrofitProgressListener調用onProgress方法。

3.okHttpBuilder 設置攔截器,裏面會用ProgressResponseBody做爲參數,如上面提供的代碼所示。

4.設置client:Retrofit.Builder().client(okHttpBuilder.build())。

 

須要注意的點

完成以上的4點就能夠獲得下載進度的數據了,用retrofit下載東西時候,就能夠設置下載進度了。

可是根據本身的測試,在請求接口中有沒有@Streaming這個註解是有區別的,當沒有的時候,用於請求下載小文件,只須要在請求返回onResponse()方法中以下:

@Override
public void onResponse(Call<ResponseBody> call, final Response<ResponseBody> response) {
     ResponseBody responseBody = response.body();
     byte[] bytes = responseBody.bytes();//這個就是文件的所有內容,只是針對小文件,
                                         //由於這個方法會把整個文件所有加載在內存中,因此避免內存溢出,
                                         //這個方法只適用於請求小文件的狀況。
}

responseBody.bytes()方法的源碼以下:

/**
 * Returns the response as a byte array.
 *   把整個body加載在內存中
 * <p>This method loads entire response body into memory. If the response body is very large this
 * may trigger an {@link OutOfMemoryError}. Prefer to stream the response body if this is a
 * possibility for your response.
 */
public final byte[] bytes() throws IOException{......}

可是在請求接口中有@Streaming這個註解的時候(用於請求大文件,返回的不是整個文件而是‘流’):

,必須結合上面給出的子線程中讀取文件的方法,纔可以獲得下載進的數據。發現獲取下載進度的子線程和請求返回onResponse()方法,是同時運行的。可是沒有@Streaming這個註解的時候是onResponse()方法後運行的,而且若是讀寫文件的那個線程一停,獲取下載進度數據的那個子線程也會中止(親測)。固然也能夠在加上@Streaming這個註解的時候,能夠在讀寫文件的子線程中設置進度提示,也是能夠的。但能達到有沒有@Streaming這個註解的時候都通用仍是用上面的方法比較好。

補充(解釋上面所訴狀況而且給出證據----讀寫文件的那個線程一停,獲取下載進度數據的那個子線程也會中止,能達到有沒有@Streaming這個註解的時候都通用仍是用上面的方法比較好。):

  • 其實就是有@Streaming這個註解的時候:

             請求返回onResponse()方法(在主線程中運行)會立馬執行,由於@Streaming這個註解                的時候請求的返回的數據是以流的形式立馬返回,就像知道了文件的具體目錄位置同樣,因此適用              於請求大文件。因此你要去讀取它纔會給你返回下載進度的數據。

  • 沒有@Streaming這個註解的時候:

            請求返回onResponse()方法(在主線程中運行)不會立馬執行,由於沒有@Streaming這個注             解的時候數據是所有加載在內存中的,加載完了纔會調用onResponse()方法(親測),這個時               候你去在用流的形式去讀寫取文件是在內存中讀寫,會立馬寫到手機中,因此沒能反應出下載的進               度提示,因此綜上所訴,能夠用本文的方法來達到下載進度的提示,通用於大小文件的請求下載。

相關文章
相關標籤/搜索