Android Retrofit文件下载进度显示问题的解决方法
综述
在retrofit2.0使用详解这篇文章中详细介绍了retrofit的用法。并且在retrofit中我们可以通过responsebody进行对文件的下载。但是在retrofit中并没有为我们提供显示下载进度的接口。在项目中,若是用户下载一个文件,无法实时给用户显示下载进度,这样用户的体验也是非常差的。那么下面就介绍一下在retrofit用于文件的下载如何实时跟踪下载进度。
演示
retrofit文件下载进度更新的实现
在retrofit2.0中他依赖于okhttp,所以如果我们需要解决这个问题还需要从这个okhttp来入手。在okhttp中有一个依赖包okio。okio也是有square公司所开发,它是java.io和java.nio的补充,使用它更容易访问、存储和处理数据。在这里需要使用okio中的source类。在这里source可以看做inputstream。对于okio的详细使用在这里就不在介绍。下面来看一下具体实现。
在这里我们首先写一个接口,用于监听下载的进度。对于文件的下载,我们需要知道下载的进度,文件的总大小,以及是否操作完成。于是有了下面这样一个接口。
package com.ljd.retrofit.progress; /** * created by ljd on 3/29/16. */ public interface progresslistener { /** * @param progress 已经下载或上传字节数 * @param total 总字节数 * @param done 是否完成 */ void onprogress(long progress, long total, boolean done); }
对于文件的下载我们需要重写responsebody类中的一些方法。
package com.ljd.retrofit.progress; 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 ljd on 3/29/16. */ public class progressresponsebody extends responsebody { private final responsebody responsebody; private final progresslistener progresslistener; private bufferedsource bufferedsource; public progressresponsebody(responsebody responsebody, progresslistener progresslistener) { this.responsebody = responsebody; this.progresslistener = progresslistener; } @override public mediatype contenttype() { return responsebody.contenttype(); } @override public long contentlength() { return responsebody.contentlength(); } @override public bufferedsource source() { if (bufferedsource == null) { bufferedsource = okio.buffer(source(responsebody.source())); } return bufferedsource; } 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; progresslistener.onprogress(totalbytesread, responsebody.contentlength(), bytesread == -1); return bytesread; } }; } }
在上面progressresponsebody类中,我们计算已经读取文件的字节数,并且调用了progresslistener接口。所以这个progresslistener接口是在子线程中运行的。
下面就来看一下是如何使用这个progressresponsebody。
package com.ljd.retrofit.progress; import android.util.log; import java.io.ioexception; import okhttp3.interceptor; import okhttp3.okhttpclient; /** * created by ljd on 4/12/16. */ public class progresshelper { private static progressbean progressbean = new progressbean(); private static progresshandler mprogresshandler; public static okhttpclient.builder addprogress(okhttpclient.builder builder){ if (builder == null){ builder = new okhttpclient.builder(); } final progresslistener progresslistener = new progresslistener() { //该方法在子线程中运行 @override public void onprogress(long progress, long total, boolean done) { log.d("progress:",string.format("%d%% done\n",(100 * progress) / total)); if (mprogresshandler == null){ return; } progressbean.setbytesread(progress); progressbean.setcontentlength(total); progressbean.setdone(done); mprogresshandler.sendmessage(progressbean); } }; //添加拦截器,自定义responsebody,添加下载进度 builder.networkinterceptors().add(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(), progresslistener)) .build(); } }); return builder; } public static void setprogresshandler(progresshandler progresshandler){ mprogresshandler = progresshandler; } }
我们通过为okhttpclient添加一个拦截器来使用我们自定义的progressresponsebody。并且在这里我们可以通过实现progresslistener接口。来获取下载进度了。但是在这里依然存在一个问题,刚才说到这个progresslistener接口运行在子线程中。也就是说在progresslistener这个接口中我们无法进行ui操作。而我们获取文件下载的进度往往则是需要一个进度条进行ui显示。显然这并不是我们想要的结果。
在这个时候我们就需要使用handler了。我们可以通过handler将子线程中的progresslistener的数据发送到ui线程中进行处理。也就是说我们在progresslistener接口中的操作只是将其参数通过handler发送出去。很显然在上面的代码中我们通过progresshandler来发送消息。那么就来看一下具体操作。
这里我们创建一个对象,用于存放progresslistener中的参数。
package com.example.ljd.retrofit.pojo; import java.util.arraylist; import java.util.list; /** * created by ljd on 3/29/16. */ public class retrofitbean { private integer total_count; private boolean incompleteresults; private list<item> items = new arraylist<item>(); /** * * @return * the totalcount */ public integer gettotalcount() { return total_count; } /** * * @param totalcount * the total_count */ public void settotalcount(integer totalcount) { this.total_count = totalcount; } /** * * @return * the incompleteresults */ public boolean getincompleteresults() { return incompleteresults; } /** * * @param incompleteresults * the incomplete_results */ public void setincompleteresults(boolean incompleteresults) { this.incompleteresults = incompleteresults; } /** * * @return * the items */ public list<item> getitems() { return items; } }
然后我们在创建一个progresshandler类。
package com.ljd.retrofit.progress; import android.os.handler; import android.os.looper; import android.os.message; /** * created by ljd on 4/12/16. */ public abstract class progresshandler { protected abstract void sendmessage(progressbean progressbean); protected abstract void handlemessage(message message); protected abstract void onprogress(long progress, long total, boolean done); protected static class responsehandler extends handler{ private progresshandler mprogresshandler; public responsehandler(progresshandler mprogresshandler, looper looper) { super(looper); this.mprogresshandler = mprogresshandler; } @override public void handlemessage(message msg) { mprogresshandler.handlemessage(msg); } } }
上面的progresshandler他是一个抽象类。在这里我们需要通过handler对象进行发送和处理消息。于是定义了两个抽象方法sendmessage和handlemessage。之后又定义了一个抽象方法onprogress来处理下载进度的显示,而这个onprogress则是我们需要在ui线程进行调用。最后创建了一个继承自handler的responsehandler内部类。为了避免内存泄露我们使用static关键字。
下面来创建一个downloadprogresshandler类,他继承于progresshandler,用来发送和处理消息。
package com.ljd.retrofit.progress; import android.os.looper; import android.os.message; /** * created by ljd on 4/12/16. */ public abstract class downloadprogresshandler extends progresshandler{ private static final int download_progress = 1; protected responsehandler mhandler = new responsehandler(this, looper.getmainlooper()); @override protected void sendmessage(progressbean progressbean) { mhandler.obtainmessage(download_progress,progressbean).sendtotarget(); } @override protected void handlemessage(message message){ switch (message.what){ case download_progress: progressbean progressbean = (progressbean)message.obj; onprogress(progressbean.getbytesread(),progressbean.getcontentlength(),progressbean.isdone()); } } }
在这里我们接收到消息以后调用抽象方法onprogress,这样一来我们只需要创建一个downloadprogresshandler对象,实现onprogress即可。
对于上面的分析,下面我们就来看一下是如何使用的。
package com.example.ljd.retrofit.download; import android.app.progressdialog; import android.os.environment; import android.os.looper; import android.support.v7.app.appcompatactivity; import android.os.bundle; import android.util.log; import com.example.ljd.retrofit.r; import com.ljd.retrofit.progress.downloadprogresshandler; import com.ljd.retrofit.progress.progresshelper; import java.io.bufferedinputstream; import java.io.file; import java.io.fileoutputstream; import java.io.ioexception; import java.io.inputstream; import butterknife.butterknife; import butterknife.onclick; import okhttp3.okhttpclient; import okhttp3.responsebody; import retrofit2.call; import retrofit2.callback; import retrofit2.response; import retrofit2.retrofit; import retrofit2.adapter.rxjava.rxjavacalladapterfactory; import retrofit2.converter.gson.gsonconverterfactory; public class downloadactivity extends appcompatactivity { @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_download); butterknife.bind(this); } @override protected void ondestroy() { butterknife.unbind(this); super.ondestroy(); } @onclick(r.id.start_download_btn) public void onclickbutton(){ retrofitdownload(); } private void retrofitdownload(){ //监听下载进度 final progressdialog dialog = new progressdialog(this); dialog.setprogressnumberformat("%1d kb/%2d kb"); dialog.settitle("下载"); dialog.setmessage("正在下载,请稍后..."); dialog.setprogressstyle(progressdialog.style_horizontal); dialog.setcancelable(false); dialog.show(); retrofit.builder retrofitbuilder = new retrofit.builder() .addcalladapterfactory(rxjavacalladapterfactory.create()) .addconverterfactory(gsonconverterfactory.create()) .baseurl("http://msoftdl.360.cn"); okhttpclient.builder builder = progresshelper.addprogress(null); downloadapi retrofit = retrofitbuilder .client(builder.build()) .build().create(downloadapi.class); progresshelper.setprogresshandler(new downloadprogresshandler() { @override protected void onprogress(long bytesread, long contentlength, boolean done) { log.e("是否在主线程中运行", string.valueof(looper.getmainlooper() == looper.mylooper())); log.e("onprogress",string.format("%d%% done\n",(100 * bytesread) / contentlength)); log.e("done","--->" + string.valueof(done)); dialog.setmax((int) (contentlength/1024)); dialog.setprogress((int) (bytesread/1024)); if(done){ dialog.dismiss(); } } }); call<responsebody> call = retrofit.retrofitdownload(); call.enqueue(new callback<responsebody>() { @override public void onresponse(call<responsebody> call, response<responsebody> response) { try { inputstream is = response.body().bytestream(); file file = new file(environment.getexternalstoragedirectory(), "12345.apk"); 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) { } }); } }
总结
对于上面的实现我们可以看出是通过okhttpclient实现的。也正是由于在retrofit2.0中它依赖于okhttp,因此对于okhttp的功能retrofit也都具备。利用这一特性,我们可以通过定制okhttpclient来配置我们的retrofit。
源码下载:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: Android 动态菜单实现实例代码
下一篇: Android如何动态改变App桌面图标