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

前言

前面介紹了很多關(guān)于Retrofit2的基本使用硕淑,下面就單獨(dú)介紹一下如何使用Retrofit2實(shí)現(xiàn)文件上傳和文件下載,并且做了一點(diǎn)拓展笋额,重點(diǎn)介紹了一下上傳和下載過(guò)程中進(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);

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

我的做法是直接擴(kuò)展一個(gè)RequestBody來(lái)實(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的請(qǐng)求體巍糯,編寫(xiě)包裝類FileRequestBody,對(duì)RequestBody進(jìn)行包裝

public final class FileRequestBody<T> extends RequestBody {
  /**
   * 實(shí)際請(qǐng)求體
   */
  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));
    
    //寫(xiě)入
    requestBody.writeTo(bufferedSink);
    //必須調(diào)用flush客扎,否則最后一部分?jǐn)?shù)據(jù)可能不會(huì)被寫(xiě)入
    bufferedSink.flush();
  }
  /**
   * 寫(xiě)入祟峦,回調(diào)進(jìn)度接口
   * @param sink Sink
   * @return Sink
   */
  private Sink sink(Sink sink) {
    return new ForwardingSink(sink) {
      //當(dāng)前寫(xiě)入字節(jié)數(shù)
      long bytesWritten = 0L;
      //總字節(jié)長(zhǎng)度,避免多次調(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)前寫(xiě)入的字節(jié)數(shù)
        bytesWritten += byteCount;
        //回調(diào)
        callback.onLoading(contentLength, bytesWritten);
      }
    };
  }
}

最后,通過(guò)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);
//通過(guò)該行代碼將RequestBody轉(zhuǎn)換成特定的FileRequestBody
FileRequestBody body = new FileRequestBody(resquestBody, callback);
Call<String> call = fileService.uploadOneFile(body);
call.enqueue(callback);

文件下載

接口定義

文件下載請(qǐng)求與普通的Get和Post請(qǐng)求是一樣的绞绒,只是他們的返回值不一樣而已蓬衡,文件下載請(qǐng)求的返回值一般定義成ResponseBody

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

發(fā)起請(qǐng)求

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)度顯示

通過(guò)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 ;
}
//通過(guò)上面的設(shè)置后家肯,我們需要在回調(diào)RetrofitCallback中實(shí)現(xiàn)onLoading方法來(lái)進(jìn)行進(jìn)度的更新操作龄砰,與上傳文件的方法相同

FileResponseBody

/**
 * 擴(kuò)展OkHttp的請(qǐng)求體,實(shí)現(xiàn)上傳時(shí)的進(jìn)度提示
 *
 * @param <T>
 */
public final class FileResponseBody<T> extends ResponseBody {
  /**
   * 實(shí)際請(qǐng)求體
   */
  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反镇,再來(lái)說(shuō)一下使用過(guò)程中的感受固蚤,從本文中也感受的到立帖,就是retrofit大量的引入了對(duì)okhttp 應(yīng)用香椎,所以要想用好retrofit 缓淹,學(xué)好okhttp 也是必須的肴掷,不說(shuō)了铝噩,啃代碼去了其屏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末臭脓,一起剝皮案震驚了整個(gè)濱河市镜盯,隨后出現(xiàn)的幾起案子尸昧,更是在濱河造成了極大的恐慌揩页,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烹俗,死亡現(xiàn)場(chǎng)離奇詭異爆侣,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)幢妄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)兔仰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人磁浇,你說(shuō)我怎么就攤上這事斋陪。” “怎么了置吓?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵无虚,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我衍锚,道長(zhǎng)友题,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任戴质,我火速辦了婚禮度宦,結(jié)果婚禮上踢匣,老公的妹妹穿的比我還像新娘。我一直安慰自己戈抄,他們只是感情好离唬,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著划鸽,像睡著了一般输莺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上裸诽,一...
    開(kāi)封第一講書(shū)人閱讀 52,441評(píng)論 1 310
  • 那天嫂用,我揣著相機(jī)與錄音,去河邊找鬼丈冬。 笑死嘱函,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的埂蕊。 我是一名探鬼主播往弓,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼粒梦!你這毒婦竟也來(lái)了亮航?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤匀们,失蹤者是張志新(化名)和其女友劉穎缴淋,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體泄朴,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡重抖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祖灰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钟沛。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖局扶,靈堂內(nèi)的尸體忽然破棺而出恨统,到底是詐尸還是另有隱情,我是刑警寧澤三妈,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布畜埋,位于F島的核電站,受9級(jí)特大地震影響畴蒲,放射性物質(zhì)發(fā)生泄漏悠鞍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一模燥、第九天 我趴在偏房一處隱蔽的房頂上張望咖祭。 院中可真熱鬧掩宜,春花似錦、人聲如沸么翰。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)浩嫌。三九已至慧瘤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間固该,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工糖儡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伐坏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓握联,卻偏偏與公主長(zhǎng)得像桦沉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子金闽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容