美文网首页鱼乐DevSupport面试题
Retrofit文件上传和文件下载

Retrofit文件上传和文件下载

作者: 写代码的解先生 | 来源:发表于2017-08-16 20:25 被阅读132次

    项目中使用了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();
    

    相关文章

      网友评论

        本文标题:Retrofit文件上传和文件下载

        本文链接:https://www.haomeiwen.com/subject/onrwrxtx.html