Retrofit文件上傳和文件下載


項目中使用了Retrofit2 網絡框架,對Retrofit的文件上傳和下載進行記錄。

文件上傳


文件上傳 一般采用POST 的方式,并使用@Multipart 聲明為多部分,可同時上傳文本和文件,接口設置如下:

interface UploadService {
    @Multipart
    @POST("uploadImg")
    Observable<BaseResult<String>> upload(
            @Part List<MultipartBody.Part> partList);
}

step1 創建UploadService

OkHttpClient httpClient = new OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)  //連接超時
        .readTimeout(10, TimeUnit.SECONDS)   //讀取超時
        .build();

Retrofit retrofit = new Retrofit.Builder().baseUrl(Contacts.BASE_URL)  //配置Retrofit 端
        .addConverterFactory(FastJsonConvertFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .client(httpClient)
        .build();

UploadService uploadService = retrofit.create(UploadService.class);

step2 構建文件RequestBody

// 創建 RequestBody,用于封裝 請求RequestBody
File imageFile = new File(filePath);  //上傳文件對象
RequestBody requestFile =
        RequestBody.create(guessMimeType(imageFile.getName()), imageFile);  //這里使用了工具類,獲取文件類型

guseeMimeType方法為:

private static MediaType guessMimeType(String path) {
    FileNameMap fileNameMap = URLConnection.getFileNameMap();
    path = path.replace("#", "");   //解決文件名中含有#號異常的問題
    String contentType = fileNameMap.getContentTypeFor(path);
    if (contentType == null) {
        contentType = "application/octet-stream";
    }
    return MediaType.parse(contentType);
}

step3 構建MultipartBody 進行上傳

MultipartBody.Builder builder = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)//表單類型 "multipart/form-data"    
        .addFormDataPart("version", Contacts.version)   //   項目的接口公共參數
        .addFormDataPart("data", data_json)   //json 字符串
        .addFormDataPart("sign", sign)
        .addFormDataPart("file", imageFile.getName(), requestFile);   //上傳的文件

List<MultipartBody.Part> parts = builder.build().parts();
//進行上傳
uploadService.upload(parts)
                .compose(RxSchedulerHepler.<BaseResult<String>>io_main());   //線程切換

文件下載


文件下載相較于 文件上傳復雜一點,其中包含了 ResponseBody 流的讀取,文件的寫入,下載進度的更新

之前進度之類的更新,采用的是接口回調的形式進行更新,自從RxJava的出現改變了這樣的方式,采用觀察者的訂閱方式進行進度的更新。

下載接口的設計如下:

@GET
@Streaming    //使用Streaming 方式 Retrofit 不會一次性將ResponseBody 讀取進入內存,否則文件很多容易OOM
Flowable<ResponseBody> download2(@Url String url);  //返回值使用 ResponseBody 之后會對ResponseBody 進行讀取

Step 1 創建ApiService (so easy!)

OkHttpClient httpClient = new OkHttpClient.Builder()
        .build();


Retrofit retrofit = new Retrofit.Builder().baseUrl(Contacts.BASE_URL)
        .addConverterFactory(FastJsonConvertFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .client(httpClient)
        .build();

ApiService apiService = retrofit.create(ApiService.class);

Step2 獲取ResponseBody 進行文件流的讀寫

apiService.download2(url)
        .flatMap(new Function<ResponseBody, Publisher<Boolean>>() {
            @Override
            public Publisher<Boolean> apply(@NonNull final ResponseBody responseBody) throws Exception {
                return Flowable.create(new FlowableOnSubscribe<Boolean>() {
                    @Override
                    public void subscribe(FlowableEmitter<Boolean> e) throws Exception {
                    
                        File saveFile = new File(filePath, fileName);
                        InputStream inputStream = null;
                        OutputStream outputStream = null;
                        try {
                            try {
                                int readLen;
                            
                                byte[] buffer = new byte[1024];

                                inputStream = responseBody.byteStream();
                                outputStream = new FileOutputStream(saveFile);

                          
                                while ((readLen = inputStream.read(buffer)) != -1 && !e.isCancelled()) {
                                    outputStream.write(buffer, 0, readLen);
                                }
                                outputStream.flush(); // This is important!!!
                                e.onComplete();
                            } finally {
                                closeQuietly(inputStream);
                                closeQuietly(outputStream);
                                closeQuietly(responseBody);
                            }
                        } catch (Exception exception) {
                            e.onError(exception);
                        }
                    }
                }, BackpressureStrategy.LATEST);
            }
        })

其中包含了一個關閉資源方法:

public static void closeQuietly(Closeable closeable) {
    if (closeable != null) {
        try {
            closeable.close();
        } catch (RuntimeException rethrown) {
            throw rethrown;
        } catch (Exception ignored) {
        }
    }
}

方法分析:

采用RxJava2 的Flowable ,Flowable 在Observable 的基礎上進行了背壓的處理

接著獲取到ResponseBody,使用RxJava的flatMap 對數據源進行變換,利用FlowableEmitter可以將下載成功的消息發射出去

接著就是熟悉的文件讀寫操作文件讀寫完成之后,發射 成功的信息

Step 3 下載進度

Step 2中完成了文件的讀寫,但是缺少了挺重要的進度回調操作,對Step 2 的代碼進行改進

apiService.download2(url)
        .flatMap(new Function<ResponseBody, Publisher<DownloadStatus>>() {
            @Override
            public Publisher<DownloadStatus> apply(@NonNull final ResponseBody responseBody) throws Exception {
                return Flowable.create(new FlowableOnSubscribe<DownloadStatus>() {
                    @Override
                    public void subscribe(FlowableEmitter<DownloadStatus> e) throws Exception {
                        //創建文件
                        File saveFile = new File(filePath, fileName);

                        InputStream inputStream = null;
                        OutputStream outputStream = null;
                        try {
                            try {
                                int readLen;
                                int downloadSize = 0;
                                byte[] buffer = new byte[8192];

                                DownloadStatus status = new DownloadStatus();
                                inputStream = responseBody.byteStream();  //獲取 輸入流
                                outputStream = new FileOutputStream(saveFile);  //文件的輸出流

                                long contentLength = responseBody.contentLength();  //文件的總長度

                                status.setTotalSize(contentLength);   

                                while ((readLen = inputStream.read(buffer)) != -1 && !e.isCancelled()) {
                                    outputStream.write(buffer, 0, readLen);
                                    downloadSize += readLen;  
                                    status.setDownloadSize(downloadSize);
                                    e.onNext(status);  //讀取完一段就將下載進度發射出去
                                }

                                outputStream.flush(); // This is important!!!
                                e.onComplete();   //發射下載完成的信息
                            } finally {
                                closeQuietly(inputStream);
                                closeQuietly(outputStream);
                                closeQuietly(responseBody);
                            }
                        } catch (Exception exception) {

                        }
                    }
                }, BackpressureStrategy.LATEST);
            }
        })
        .toObservable()  //轉換成我們熟悉的Observable 
        .debounce(200, TimeUnit.MICROSECONDS)  //進行200秒的過濾操作
        .compose(RxSchedulerHepler.<DownloadStatus>io_main());  //線程切換

DownLoadStatus對下載的狀態進行了包裝,包含了下載長度,總長度 字段

public class DownloadStatus implements Parcelable {
    public static final Creator<DownloadStatus> CREATOR
            = new Creator<DownloadStatus>() {
        @Override
        public DownloadStatus createFromParcel(Parcel source) {
            return new DownloadStatus(source);
        }

        @Override
        public DownloadStatus[] newArray(int size) {
            return new DownloadStatus[size];
        }
    };


    private long totalSize;
    private long downloadSize;

    public DownloadStatus() {
    }

    public DownloadStatus(long downloadSize, long totalSize) {
        this.downloadSize = downloadSize;
        this.totalSize = totalSize;
    }

    public DownloadStatus(boolean isChunked, long downloadSize, long totalSize) {

        this.downloadSize = downloadSize;
        this.totalSize = totalSize;
    }

    protected DownloadStatus(Parcel in) {

        this.totalSize = in.readLong();
        this.downloadSize = in.readLong();
    }

    public long getTotalSize() {
        return totalSize;
    }

    public void setTotalSize(long totalSize) {
        this.totalSize = totalSize;
    }

    public long getDownloadSize() {
        return downloadSize;
    }

    public void setDownloadSize(long downloadSize) {
        this.downloadSize = downloadSize;
    }

    /**
     * 獲得格式化的總Size
     *
     * @return example: 2KB , 10MB
     */
    public String getFormatTotalSize() {
        return formatSize(totalSize);
    }

    public String getFormatDownloadSize() {
        return formatSize(downloadSize);
    }

    public static String formatSize(long size) {
        String hrSize;
        double b = size;
        double k = size / 1024.0;
        double m = ((size / 1024.0) / 1024.0);
        double g = (((size / 1024.0) / 1024.0) / 1024.0);
        double t = ((((size / 1024.0) / 1024.0) / 1024.0) / 1024.0);
        DecimalFormat dec = new DecimalFormat("0.00");
        if (t > 1) {
            hrSize = dec.format(t).concat(" TB");
        } else if (g > 1) {
            hrSize = dec.format(g).concat(" GB");
        } else if (m > 1) {
            hrSize = dec.format(m).concat(" MB");
        } else if (k > 1) {
            hrSize = dec.format(k).concat(" KB");
        } else {
            hrSize = dec.format(b).concat(" B");
        }
        return hrSize;
    }


    /**
     * 獲得格式化的狀態字符串
     *
     * @return example: 2MB/36MB
     */
    public String getFormatStatusString() {
        return getFormatDownloadSize() + "/" + getFormatTotalSize();
    }

    /**
     * 獲得下載的百分比, 保留兩位小數
     *
     * @return example: 5.25%
     */
    public String getPercent() {
        String percent;
        Double result;
        if (totalSize == 0L) {
            result = 0.0;
        } else {
            result = downloadSize * 1.0 / totalSize;
        }
        NumberFormat nf = NumberFormat.getPercentInstance();
        nf.setMinimumFractionDigits(2);//控制保留小數點后幾位,2:表示保留2位小數點
        percent = nf.format(result);
        return percent;
    }

    /**
     * 獲得下載的百分比數值
     *
     * @return example: 5%  will return 5, 10% will return 10.
     */
    public long getPercentNumber() {
        double result;
        if (totalSize == 0L) {
            result = 0.0;
        } else {
            result = downloadSize * 1.0 / totalSize;
        }
        return (long) (result * 100);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeLong(this.totalSize);
        dest.writeLong(this.downloadSize);
    }
}

Step 4在Activity中進行文件下載測試

String url = "http://www.bz55.com/uploads/allimg/150701/140-150F1142638.jpg";
Disposable    mSubscribe = DownLoadUtil3.download(url, Environment.getExternalStorageDirectory().getAbsolutePath(), "140-150F1142638.jpg")
                .subscribe(new Consumer<DownloadStatus>() {
                    @Override
                    public void accept(@NonNull DownloadStatus downloadStatus) throws Exception {
                        long downloadSize = downloadStatus.getDownloadSize();
                        long totalSize = downloadStatus.getTotalSize();
                        Log.e("TAG", "onNext  -->" + downloadSize + "------" + totalSize);
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(@NonNull Throwable throwable) throws Exception {
                        Log.e("TAG", "onError:--->" + throwable);
                    }
                }, new Action() {
                    @Override
                    public void run() throws Exception {
                        Log.e("TAG", "下載完成");
                    }
                });
    //停止下載的方式為 mSubscribe.dispose();
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,714評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,818評論 18 139
  • 我從去年開始使用 RxJava ,到現在一年多了。今年加入了 Flipboard 后,看到 Flipboard 的...
    Jason_andy閱讀 5,554評論 7 62
  • 本文包括:1、文件上傳概述2、利用 Commons-fileupload 組件實現文件上傳3、核心API——Dis...
    廖少少閱讀 12,569評論 5 91
  • 最近常常看到簡書上關于寫作的文章,好多人把寫作當作一種習慣來培養。內容是什么不是重點,堅持每天書寫才是關鍵。 我突...
    HolyCow閱讀 304評論 4 1