网络请求框架Retrofit的下载时的进度提示

172912_illc_2987490.png

用Retrofit下载时想得到下载的进度可以重写ResponseBody,和设置拦截器:

1.设置请求方式以及Url:(注解streaming是用来下载大文件的,防止内存溢出,下载小文件就可以不用这个注解)

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Streaming;
import retrofit2.http.Url;

/**
 * Created by Zzm丶Fiona on 2017/7/27.
 */

public interface DownloadFileInterfaceForRetrofit {
    @Streaming 
    @GET
    Call<ResponseBody> getCall(@Url String path);

}

2.建立请求:(其中builder是OkHttpClient.Builder,其中子线程是将文件写入本地

private final String mBaseUrl = "http://hc39.aipai.com/user/";
private final String videoPath = "499/56065499/1006/card/45789929/card.mp4?l=j";

Call<ResponseBody> call = getDownloadFileInterfaceForRetrofit(mBaseUrl, builder).getCall(videoPath);
        call.enqueue(new Callback<ResponseBody>() {

            @Override
            public void onResponse(Call<ResponseBody> call, final Response<ResponseBody> response) {
                Log.i("zzmzzm", "onResponse:  thread:"+Thread.currentThread().getName());
                new Thread(){
                    @Override
                    public void run() {
                        super.run();
                        try {
                            InputStream is = response.body().byteStream();
                            File file = new File(Environment.getExternalStorageDirectory(), "12345.mp4");
                            FileOutputStream fos = new FileOutputStream(file);
                            BufferedInputStream bis = new BufferedInputStream(is);
                            byte[] buffer = new byte[1024*10];
                            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();
                        }
                    }
                }.start();
            }
            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Log.i("zzmzzm", "连接失败!!");
            }
        });

方法getDownloadFileInterfaceForRetrofit:(其中设置client是关键)

private DownloadFileInterfaceForRetrofit getDownloadFileInterfaceForRetrofit(String mBaseUrl, OkHttpClient.Builder builder) {
    return new Retrofit
            .Builder()
            .baseUrl(mBaseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .client(builder.build()) 
            .build()
            .create(DownloadFileInterfaceForRetrofit.class);
}

3.其中设置client(builder.build())中的builder的来源:

OkHttpClient.Builder builder = RetrofitDownloadProgressUtil.getOkHttpClientBuilder(new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case 1:
                 ProgressBean progressBean = (ProgressBean) msg.obj;
 long total = progressBean.getTotal();//下载的文件的总字节数 long bytesReading = progressBean.getBytesReading();//目前已经读的字节数 boolean isDone = progressBean.getIsDone();//是否下载完成
                progressBean = null;
        }
    }
});

上面的ProgressBean是用来储存下载文件的相关信息,handler是用来传递信息给主线程的,因为下载的信息的获取是在子线程中:

/**
 * Created by Zzm丶Fiona on 2017/7/28.
 */

public class ProgressBean {
    private long total;
    private long bytesReading;
    private boolean isDone;

    public long getTotal() {
        return total;
    }

    public void setTotal(long total) {
        this.total = total;
    }

    public long getBytesReading() {
        return bytesReading;
    }

    public void setBytesReading(long bytesReading) {
        this.bytesReading = bytesReading;
    }

    public boolean getIsDone() {
        return isDone;
    }

    public void setIsDone(boolean done) {
        isDone = done;
    }
}

其中的RetrofitDownloadProgressUtil.getOkHttpClientBuilder如下:

import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import zzm.zzmotherthingsshouldknowfortheinterview.ProgressBean;
import zzm.zzmotherthingsshouldknowfortheinterview.ProgressResponseBody;
import zzm.zzmotherthingsshouldknowfortheinterview.RetrofitProgressListener;

/**
 * Created by Zzm丶Fiona on 2017/7/28.
 */

public class RetrofitDownloadProgressUtil {
    public static ProgressBean progressBean = new ProgressBean();//完成的时候赋值为null,免得内存溢出

    public static OkHttpClient.Builder getOkHttpClientBuilder(final Handler handler) {
        OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
        okHttpBuilder.addNetworkInterceptor(new Interceptor() {
            @Override
            public okhttp3.Response intercept(Chain chain) throws IOException {
                okhttp3.Response originalResponse = chain.proceed(chain.request());
                return originalResponse.newBuilder().body(new ProgressResponseBody(originalResponse.body(), new RetrofitProgressListener() {
                            @Override
                            public void onProgress(long progress, long total, boolean isDone) {
                                //每次有进度的变化就会回调这个方法
                                if (null == progressBean) {
                                    progressBean = new ProgressBean();
                                }
                                progressBean.setTotal(total);
                                progressBean.setBytesReading(progress);
                                progressBean.setDone(isDone);
                                Log.i("zzmzzm", "progress:  " + "开始回调下载进度了!!");
                                handler.sendMessage(handler.obtainMessage(1, progressBean));
                            }
                        })
                ).build();
            }
        });
        return okHttpBuilder;
    }
}

上面的粉红色的字就是关键其中的ProgressResponseBody就是继承于ResponseBody:

import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;

/**
 * Created by Zzm丶Fiona on 2017/7/28.
 */

public class ProgressResponseBody extends ResponseBody {
    private final ResponseBody responseBody;
    private final RetrofitProgressListener retrofitProgressListener;
    private BufferedSource bufferedSource;

    public ProgressResponseBody(ResponseBody responseBody, RetrofitProgressListener retrofitProgressListener) {
        this.responseBody = responseBody;
        this.retrofitProgressListener = retrofitProgressListener;
    }

    @Override
    public MediaType contentType() {
        return responseBody.contentType();
    }

    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(getSource(responseBody.source()));
        }
        return bufferedSource;
    }

    private Source getSource(Source source) {
        return new ForwardingSource(source) {
            long totalBytesRead = 0L;

            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesReading = super.read(sink, byteCount);
                totalBytesRead += bytesReading != -1 ? bytesReading : 0;//读取数据完成后bytesReading=-1;
                retrofitProgressListener.onProgress(totalBytesRead, contentLength(), bytesReading == -1);
                return bytesReading;
            }
        };
    }
}

就是重写了几个方法,让后作为参数设置给了OkHttpClientBuilder的拦截器。

 

最后总结一下,可能有点凌乱,最主要就是三点:

1.写个类ProgressResponseBody继承ResponseBody,然后重写它的三个法,如上。

2.在重写方法@Override public BufferedSource source() {}中调用自己写的接口,方便回调后可以得到下载进度数据,如上面提供的代码所示retrofitProgressListener调用onProgress方法。

3.okHttpBuilder 设置拦截器,里面会用ProgressResponseBody作为参数,如上面提供的代码所示。

4.设置client:Retrofit.Builder().client(okHttpBuilder.build())。

 

需要注意的点

完成以上的4点就可以得到下载进度的数据了,用retrofit下载东西时候,就可以设置下载进度了。

但是根据自己的测试,在请求接口中有没有@Streaming这个注解是有区别的,当没有的时候,用于请求下载小文件,只需要在请求返回onResponse()方法中如下:

@Override
public void onResponse(Call<ResponseBody> call, final Response<ResponseBody> response) {
     ResponseBody responseBody = response.body();
     byte[] bytes = responseBody.bytes();//这个就是文件的全部内容,只是针对小文件,
                                         //因为这个方法会把整个文件全部加载在内存中,所以避免内存溢出,
                                         //这个方法只适用于请求小文件的情况。
}

responseBody.bytes()方法的源码如下:

/**
 * Returns the response as a byte array.
 *   把整个body加载在内存中
 * <p>This method loads entire response body into memory. If the response body is very large this
 * may trigger an {@link OutOfMemoryError}. Prefer to stream the response body if this is a
 * possibility for your response.
 */
public final byte[] bytes() throws IOException{......}

但是在请求接口中有@Streaming这个注解的时候(用于请求大文件,返回的不是整个文件而是‘流’):

175137_9TFz_2987490.png

,必须结合上面给出的子线程中读取文件的方法,才能够得到下载进的数据。发现获取下载进度的子线程和请求返回onResponse()方法,是同时运行的。但是没有@Streaming这个注解的时候是onResponse()方法后运行的,并且如果读写文件的那个线程一停,获取下载进度数据的那个子线程也会停止(亲测)。当然也可以在加上@Streaming这个注解的时候,可以在读写文件的子线程中设置进度提示,也是可以的。但能达到有没有@Streaming这个注解的时候都通用还是用上面的方法比较好。

补充(解释上面所诉情况并且给出证据----读写文件的那个线程一停,获取下载进度数据的那个子线程也会停止,能达到有没有@Streaming这个注解的时候都通用还是用上面的方法比较好。):

  • 其实就是有@Streaming这个注解的时候:

             请求返回onResponse()方法(在主线程中运行)会立马执行,因为@Streaming这个注解                的时候请求的返回的数据是以流的形式立马返回,就像知道了文件的具体目录位置一样,所以适用              于请求大文件。所以你要去读取它才会给你返回下载进度的数据。

  • 没有@Streaming这个注解的时候:

            请求返回onResponse()方法(在主线程中运行)不会立马执行,因为没有@Streaming这个注             解的时候数据是全部加载在内存中的,加载完了才会调用onResponse()方法(亲测),这个时               候你去在用流的形式去读写取文件是在内存中读写,会立马写到手机中,所以没能反应出下载的进               度提示,所以综上所诉,可以用本文的方法来达到下载进度的提示,通用于大小文件的请求下载。

转载于:https://my.oschina.net/u/2987490/blog/1491935

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值