Retrofit文件上傳和文件下載


項目中使用了Retrofit2 網(wǎng)絡(luò)框架,對Retrofit的文件上傳和下載進行記錄。

文件上傳


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

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

step1 創(chuàng)建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 構(gòu)建文件RequestBody

// 創(chuàng)建 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 構(gòu)建MultipartBody 進行上傳

MultipartBody.Builder builder = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)//表單類型 "multipart/form-data"    
        .addFormDataPart("version", Contacts.version)   //   項目的接口公共參數(shù)
        .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());   //線程切換

文件下載


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

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

下載接口的設(shè)計如下:

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

Step 1 創(chuàng)建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);
            }
        })

其中包含了一個關(guān)閉資源方法:

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

方法分析:

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

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

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

Step 3 下載進度

Step 2中完成了文件的讀寫,但是缺少了挺重要的進度回調(diào)操作,對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 {
                        //創(chuàng)建文件
                        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);  //讀取完一段就將下載進度發(fā)射出去
                                }

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

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

DownLoadStatus對下載的狀態(tài)進行了包裝,包含了下載長度,總長度 字段

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


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

    /**
     * 獲得下載的百分比, 保留兩位小數(shù)
     *
     * @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);//控制保留小數(shù)點后幾位,2:表示保留2位小數(shù)點
        percent = nf.format(result);
        return percent;
    }

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

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,970評論 25 709
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,544評論 19 139
  • 我從去年開始使用 RxJava ,到現(xiàn)在一年多了。今年加入了 Flipboard 后,看到 Flipboard 的...
    Jason_andy閱讀 5,757評論 7 62
  • 本文包括:1、文件上傳概述2、利用 Commons-fileupload 組件實現(xiàn)文件上傳3、核心API——Dis...
    廖少少閱讀 12,743評論 5 91
  • 最近常??吹胶啎详P(guān)于寫作的文章,好多人把寫作當作一種習慣來培養(yǎng)。內(nèi)容是什么不是重點,堅持每天書寫才是關(guān)鍵。 我突...
    HolyCow閱讀 343評論 4 1

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