再談Retrofit:文件的上傳下載及進(jìn)度顯示

前言

前面介紹了很多關(guān)于Retrofit2的基本使用,下面就單獨(dú)介紹一下如何使用Retrofit2實(shí)現(xiàn)文件上傳和文件下載,并且做了一點(diǎn)拓展,重點(diǎn)介紹了一下上傳和下載過程中進(jìn)度的顯示。

文件上傳

定義接口

@Multipart
@POST("url")
Call<Result> uploadFile(@Part RequestBody file);

構(gòu)造所要上傳的RequestBody

File file = new File(filePath);
RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data"), file);
Call<String> call = fileService.uploadFile(body);
call.enqueue(callback);

通過Retrofit提供的方法就可以很簡單的將文件上傳到服務(wù)器,但通常上傳文件時(shí),都會加上文件的上傳進(jìn)度,這樣交互會顯得更加友好。而Retrofit本身是不支持文件上傳進(jìn)度顯示的,所以就需要我們自己擴(kuò)展OkHttp來實(shí)現(xiàn)文件上傳進(jìn)度。

我的做法是直接擴(kuò)展一個(gè)RequestBody來實(shí)現(xiàn)進(jìn)度顯示,實(shí)現(xiàn)完成之后只需要將上面body進(jìn)行包裝轉(zhuǎn)換即可。

首先封裝一個(gè)RetrofitCallback,用于進(jìn)度的回調(diào)。

public abstract class RetrofitCallback<T> implements Callback<T> {
    @Override
    public void onResponse(Call<T> call, Response<T> response) {
        if(response.isSuccessful()) {
            onSuccess(call, response);
        } else {
            onFailure(call, new Throwable(response.message()));
        }
    }
    public abstract void onSuccess(Call<T> call, Response<T> response);
   //用于進(jìn)度的回調(diào)
    public abstract void onLoading(long total, long progress) ;
}

第二步,擴(kuò)展OkHttp的請求體,編寫包裝類FileRequestBody,對RequestBody進(jìn)行包裝

public final class FileRequestBody<T> extends RequestBody {
  /**
   * 實(shí)際請求體
   */
  private RequestBody requestBody;
  /**
   * 上傳回調(diào)接口
   */
  private RetrofitCallback<T> callback;
  /**
   * 包裝完成的BufferedSink
   */
  private BufferedSink bufferedSink;
  public FileRequestBody(RequestBody requestBody, RetrofitCallback<T> callback) {
    super();
    this.requestBody = requestBody;
    this.callback = callback;
  }
  @Override
  public long contentLength() throws IOException {
    return requestBody.contentLength();
  }
  @Override
  public MediaType contentType() {
    return requestBody.contentType();
  }
  @Override
  public void writeTo(BufferedSink sink) throws IOException {
    bufferedSink = Okio.buffer(sink(sink));
    
    //寫入
    requestBody.writeTo(bufferedSink);
    //必須調(diào)用flush,否則最后一部分?jǐn)?shù)據(jù)可能不會被寫入
    bufferedSink.flush();
  }
  /**
   * 寫入,回調(diào)進(jìn)度接口
   * @param sink Sink
   * @return Sink
   */
  private Sink sink(Sink sink) {
    return new ForwardingSink(sink) {
      //當(dāng)前寫入字節(jié)數(shù)
      long bytesWritten = 0L;
      //總字節(jié)長度,避免多次調(diào)用contentLength()方法
      long contentLength = 0L;
      @Override
      public void write(Buffer source, long byteCount) throws IOException {
        super.write(source, byteCount);
        if (contentLength == 0) {
          //獲得contentLength的值,后續(xù)不再調(diào)用
          contentLength = contentLength();
        }
        //增加當(dāng)前寫入的字節(jié)數(shù)
        bytesWritten += byteCount;
        //回調(diào)
        callback.onLoading(contentLength, bytesWritten);
      }
    };
  }
}

最后,通過onLoading(long total, long progress) ,更新上傳進(jìn)度

RetrofitCallback< String > callback = new RetrofitCallback< Result >() {
   @Override
    public void onSuccess(Call< String > call, Response< String > response) {
        runOnUIThread(activity, response.body().toString());
        //進(jìn)度更新結(jié)束
    }
    @Override
    public void onFailure(Call< String > call, Throwable t) {
        runOnUIThread(activity, t.getMessage());
        //進(jìn)度更新結(jié)束
    }
    @Override
    public void onLoading(long total, long progress) {
        super.onLoading(total, progress);
        //此處進(jìn)行進(jìn)度更新
    }
};
RequestBody resquestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
//通過該行代碼將RequestBody轉(zhuǎn)換成特定的FileRequestBody
FileRequestBody body = new FileRequestBody(resquestBody, callback);
Call<String> call = fileService.uploadOneFile(body);
call.enqueue(callback);

文件下載

接口定義

文件下載請求與普通的Get和Post請求是一樣的,只是他們的返回值不一樣而已,文件下載請求的返回值一般定義成ResponseBody

//這里只舉例POST方式進(jìn)行文件下載
@FormUrlEncoded
@POST("fileService")
Call<ResponseBody> downloadFile(@Field("param") String param);

發(fā)起請求

RetrofitCallback<ResponseBody> callback = new RetrofitCallback<ResponseBody>() {
    @Override
    public void onSuccess(Call<ResponseBody> call, Response<ResponseBody> response) {
        try {
            InputStream is = response.body().byteStream();
            String path = Util.getSdCardPath();
            File file = new File(path, "download.jpg");
            FileOutputStream fos = new FileOutputStream(file);
            BufferedInputStream bis = new BufferedInputStream(is);
            byte[] buffer = new byte[1024];
            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();
        }
    }
    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        runOnUIThread(activity, t.getMessage());
    }

   @Override
   public void onLoading(long total, long progress){
    //更新下載進(jìn)度
  }

};
        
Call<ResponseBody> call = getRetrofitService(callback).downloadFile(param);
call.enqueue(callback);

下載進(jìn)度顯示

通過OkHttp設(shè)置攔截器將ResponseBody進(jìn)行轉(zhuǎn)換成我們擴(kuò)展后的ResponseBody

** 擴(kuò)展ResponseBody設(shè)置OkHttp攔截器**

private <T> RetrofitService getRetrofitService(final RetrofitCallback<T> callback) {
    OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
    clientBuilder.addInterceptor(new Interceptor() {
        @Override
        public okhttp3.Response intercept(Chain chain) throws IOException {
            okhttp3.Response response = chain.proceed(chain.request());
            //將ResponseBody轉(zhuǎn)換成我們需要的FileResponseBody
            return response.newBuilder().body(new FileResponseBody<T>(response.body(), callback)).build();
        }
    });
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(clientBuilder.build())
            .addConverterFactory(GsonConverterFactory.create())
            .build();
    RetrofitService service = retrofit.create(RetrofitService.class);
    return service ;
}
//通過上面的設(shè)置后,我們需要在回調(diào)RetrofitCallback中實(shí)現(xiàn)onLoading方法來進(jìn)行進(jìn)度的更新操作,與上傳文件的方法相同

FileResponseBody

/**
 * 擴(kuò)展OkHttp的請求體,實(shí)現(xiàn)上傳時(shí)的進(jìn)度提示
 *
 * @param <T>
 */
public final class FileResponseBody<T> extends ResponseBody {
  /**
   * 實(shí)際請求體
   */
  private ResponseBody mResponseBody;
  /**
   * 下載回調(diào)接口
   */
  private RetrofitCallback<T> mCallback;
  /**
   * BufferedSource
   */
  private BufferedSource mBufferedSource;
  public FileResponseBody(ResponseBody responseBody, RetrofitCallback<T> callback) {
    super();
    this.mResponseBody = responseBody;
    this.mCallback = callback;
  }
  @Override
  public BufferedSource source() {
    if (mBufferedSource == null) {
      mBufferedSource = Okio.buffer(source(mResponseBody.source()));
    }
    return mBufferedSource;
  }
  @Override
  public long contentLength() {
    return mResponseBody.contentLength();
  }
  @Override
  public MediaType contentType() {
    return mResponseBody.contentType();
  }
  /**
   * 回調(diào)進(jìn)度接口
   * @param source
   * @return Source
   */
  private Source source(Source source) {
    return new ForwardingSource(source) {
      long totalBytesRead = 0L;
      @Override
      public long read(Buffer sink, long byteCount) throws IOException {
        long bytesRead = super.read(sink, byteCount);
        totalBytesRead += bytesRead != -1 ? bytesRead : 0;
        mCallback.onLoading(mResponseBody.contentLength(), totalBytesRead);
        return bytesRead;
      }
    };
  }
}

總結(jié)

依照慣例,最后都要有一個(gè)總結(jié),先感嘆一下retrofit的強(qiáng)大。ok,再來說一下使用過程中的感受,從本文中也感受的到,就是retrofit大量的引入了對okhttp 應(yīng)用,所以要想用好retrofit ,學(xué)好okhttp 也是必須的,不說了,啃代碼去了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容