Retrofit文件上传和文件下载


项目中使用了Retrofit2 网络框架,对Retrofit的文件上传和下载进行记录。

文件上传


文件上传 一般采用POST 的方式,并使用@Multipart 声明为多部分,可同时上传文本和文件,接口设置如下:

interface UploadService {
    @Multipart
    @POST("uploadImg")
    Observable> upload(
            @Part List 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 parts = builder.build().parts();
//进行上传
uploadService.upload(parts)
                .compose(RxSchedulerHepler.>io_main());   //线程切换

文件下载


文件下载相较于 文件上传复杂一点,其中包含了 ResponseBody 流的读取,文件的写入,下载进度的更新

之前进度之类的更新,采用的是接口回调的形式进行更新,自从RxJava的出现改变了这样的方式,采用观察者的订阅方式进行进度的更新。

下载接口的设计如下:

@GET
@Streaming    //使用Streaming 方式 Retrofit 不会一次性将ResponseBody 读取进入内存,否则文件很多容易OOM
Flowable 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>() {
            @Override
            public Publisher apply(@NonNull final ResponseBody responseBody) throws Exception {
                return Flowable.create(new FlowableOnSubscribe() {
                    @Override
                    public void subscribe(FlowableEmitter 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>() {
            @Override
            public Publisher apply(@NonNull final ResponseBody responseBody) throws Exception {
                return Flowable.create(new FlowableOnSubscribe() {
                    @Override
                    public void subscribe(FlowableEmitter 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.io_main());  //线程切换

DownLoadStatus对下载的状态进行了包装,包含了下载长度,总长度 字段

public class DownloadStatus implements Parcelable {
    public static final Creator CREATOR
            = new Creator() {
        @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() {
                    @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() {
                    @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();

你可能感兴趣的:(Retrofit文件上传和文件下载)