• Retrofit2文件上传下载及其进度显示


    前面一篇文章介绍了Retrofit2的基本使用,这篇文章接着介绍使用Retrofit2实现文件上传和文件下载,以及上传下载过程中如何实现进度的显示。

    文件上传

    定义接口
    1
    2
    3
    @Multipart
    @POST("fileService")
    Call<User> uploadFile(@Part MultipartBody.Part file);
    构造请求体上传
    1
    2
    3
    4
    5
    File file = new File(filePath);
    RequestBody body = RequestBody.create(MediaType.parse("application/otcet-stream"), file);
    MultipartBody.Part part = MultipartBody.Part.createFormData("file", file.getName(), body);
    Call<User> call = getRetrofitService().uploadOneFile(part);
    call.enqueue(callback);

    这样就可以将这个文件上传到服务器,但就这样上传操作不够友好,最好加上文件上传进度。而Retrofit本身是不支持文件上传进度显示的,所以就需要我们自己扩展OkHttp来实现文件上传进度。

    我的做法是直接扩展一个RequestBody来实现进度显示,实现完成之后只需要将上面body进行包装转换即可

    上传进度显示
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    RetrofitCallback<User> callback = new RetrofitCallback<User>() {
    @Override
    public void onSuccess(Call<User> call, Response<User> response) {
    runOnUIThread(activity, response.body().toString());
    //进度更新结束
    }
    @Override
    public void onFailure(Call<User> call, Throwable t) {
    runOnUIThread(activity, t.getMessage());
    //进度更新结束
    }
    @Override
    public void onLoading(long total, long progress) {
    super.onLoading(total, progress);
    //此处进行进度更新
    }
    };
    RequestBody body1 = RequestBody.create(MediaType.parse("application/otcet-stream"), file);
    //通过该行代码将RequestBody转换成特定的FileRequestBody
    FileRequestBody body = new FileRequestBody(body1, callback);
    MultipartBody.Part part = MultipartBody.Part.createFormData("file", file.getName(), body);
    Call<User> call = getRetrofitService().uploadOneFile(part);
    call.enqueue(callback);
    回调RetrofitCallback
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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);
    public void onLoading(long total, long progress) {
    }
    }
    FileRequestBody
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    /**
    * 扩展OkHttp的请求体,实现上传时的进度提示
    *
    * @param <T>
    */
    public final class FileRequestBody<T> extends RequestBody {
    /**
    * 实际请求体
    */
    private RequestBody requestBody;
    /**
    * 上传回调接口
    */
    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 {
    if (bufferedSink == null) {
    //包装
    bufferedSink = Okio.buffer(sink(sink));
    }
    //写入
    requestBody.writeTo(bufferedSink);
    //必须调用flush,否则最后一部分数据可能不会被写入
    bufferedSink.flush();
    }
    /**
    * 写入,回调进度接口
    * @param sink Sink
    * @return Sink
    */
    private Sink sink(Sink sink) {
    return new ForwardingSink(sink) {
    //当前写入字节数
    long bytesWritten = 0L;
    //总字节长度,避免多次调用contentLength()方法
    long contentLength = 0L;
    @Override
    public void write(Buffer source, long byteCount) throws IOException {
    super.write(source, byteCount);
    if (contentLength == 0) {
    //获得contentLength的值,后续不再调用
    contentLength = contentLength();
    }
    //增加当前写入的字节数
    bytesWritten += byteCount;
    //回调
    callback.onLoading(contentLength, bytesWritten);
    }
    };
    }
    }

    文件下载

    接口定义

    文件下载请求与普通的Get和Post请求是一样的,只是他们的返回值不一样而已,文件下载请求的返回值一般定义成ResponseBody

    1
    2
    3
    4
    //这里只举例POST方式进行文件下载
    @FormUrlEncoded
    @POST("fileService")
    Call<ResponseBody> downloadFile(@Field("param") String param);
    发起请求
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    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());
    }
    };
     
    Call<ResponseBody> call = getRetrofitService(callback).downloadFile(param);
    call.enqueue(callback);
    下载进度显示

    下载进度显示有两种方式实现,一种是通过OkHttp设置拦截器将ResponseBody进行转换成我们扩展后的ResponseBody(稍后介绍),另外一种则是在上面的回调Callback中将ResponseBody的流写入到文件时进行进度处理,下面分别进行介绍。

    扩展ResponseBody设置OkHttp拦截器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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转换成我们需要的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 ;
    }
    //通过上面的设置后,我们需要在回调RetrofitCallback中实现onLoading方法来进行进度的更新操作,与上传文件的方法相同
    FileResponseBody
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    /**
    * 扩展OkHttp的请求体,实现上传时的进度提示
    *
    * @param <T>
    */
    public final class FileResponseBody<T> extends ResponseBody {
    /**
    * 实际请求体
    */
    private ResponseBody mResponseBody;
    /**
    * 下载回调接口
    */
    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();
    }
    /**
    * 回调进度接口
    * @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;
    }
    };
    }
    }
    直接在回调中进行进度更新

    上面介绍了通过扩展ResponseBody同时设置OkHttp拦截器来实现进度条更新显示,另外也可以直接在请求回调onSuccess中将流转换成文件时实现进度更新,下面给出大致实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    RetrofitCallback<ResponseBody> callback = new RetrofitCallback<ResponseBody>() {
    @Override
    public void onSuccess(Call<ResponseBody> call, Response<ResponseBody> response) {
    try {
    InputStream is = response.body().byteStream();
    //获取文件总长度
    long totalLength = is.available();
     
    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);
    //此处进行更新操作
    //len即可理解为已下载的字节数
    //onLoading(len, totalLength);
    }
    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());
    }
    };

    以上就是Retrofit中文件上传下载及其进度更新显示的实现

  • 相关阅读:
    public,protected,private辨析
    转载---Java集合对象的深度复制与普通复制
    SSM框架学习之高并发秒杀业务--笔记4-- web层
    PCB布线总的原则
    模拟电子技术目录
    放大器(PA+LAN)在射频上的应用
    AD软件Bug和自我失误的对战
    二、cadence焊盘与封装制作操作步骤详细说明
    图腾柱电路工作原理
    转载:介绍AD另外一种奇葩的多通道复用的方法
  • 原文地址:https://www.cnblogs.com/android-blogs/p/5983991.html
Copyright © 2020-2023  润新知