欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Retrofit+RxJava实现带进度条的文件下载

程序员文章站 2022-04-06 13:25:02
项目中需要使用到更新版本,因此研究了一下retrofit的下载文件,和进度条效果,其间也遇到了一些坑,写出来加深一下记忆,也为别的同学提供一下思路。 先说一下版本控制吧,...

项目中需要使用到更新版本,因此研究了一下retrofit的下载文件,和进度条效果,其间也遇到了一些坑,写出来加深一下记忆,也为别的同学提供一下思路。

先说一下版本控制吧,通用做法基本上是通过接口获取服务器存储的app版本号,与应用的版本号进行比较,版本较低就去更新,先看一下如何获取应用版本号吧

packagemanager packagemanager = mactivity.getpackagemanager();

 packageinfo packageinfo = null;

 try {
 packageinfo = packagemanager.getpackageinfo(mactivity.getpackagename(), 0);
 } catch (packagemanager.namenotfoundexception e) {
 e.printstacktrace();
 }

 string versionname = packageinfo.versionname;

可以看到使用的是context中的getpackagemanager方法来获取packagemanager 对象,该对象可用于获取版本的一些信息。

上面的属于附内容,接下来就是关于retrofit+rxjava实现进度条下载文件的功能,retrofit本身不提供进度条显示的功能,但retrofit默认使用okhttp来进行网络请求,这里就可以自定义拦截器来进行拦截,实现进度。okhttp的demo中也为我们提供了一份代码,需要的可以去参考一下progress.javar,可以看到拦截器的设置:

public class progressresponsebody extends responsebody {

 private responsebody responsebody;

 private 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);
 //增加当前读取的字节数,如果读取完成了bytesread会返回-1
 totalbytesread += bytesread != -1 ? bytesread : 0;
 //回调,如果contentlength()不知道长度,会返回-1

 progresslistener.onprogress(totalbytesread,responsebody.contentlength(),bytesread,bytesread==-1);
 return bytesread;
 }
 };
 }
}

progresslistener 用来监听进度变化,回调到progressinterceptor中,progressinterceptor是一个自定义的拦截器,可以看一下代码

public class progressinterceptor implements interceptor {

 @override
 public response intercept(chain chain) throws ioexception {

 response response=chain.proceed(chain.request());
 return response.newbuilder().body(new progressresponsebody(response.body(),progresslistener)).build();
 }
 static final progresslistener progresslistener=new progresslistener() {
 @override
 public void onprogress(long progress, long total, long speed, boolean done) {

 log.i("log","progress="+progress+"total="+total);
 }
 };
}

为了便于获取progress,可以通过okhttpclient的addnetworkinterceptor方法直接添加一个自定义的拦截器,例如:

 //为okhttp设置拦截器
 okhttpclient client = new okhttpclient.builder()
 .addnetworkinterceptor(new interceptor() {
 @override public response intercept(chain chain) throws ioexception {
  response originalresponse = chain.proceed(chain.request());
  return originalresponse.newbuilder()
  .body(new progressresponsebody(originalresponse.body(), progresslistener))
  .build();
 }
 })
 .build();

 //进度回调监听
 progresslistener progresslistener=new progresslistener() {
 @override
 public void onprogress(long progress, long total, long speed, boolean done) {

 message message=new message();

 message.obj=new amallloadbean(progress,total);

 progresshandler.sendmessage(message);
 }
 };

这里通过一个创建一个继承自handler的progresshandler静态内部类用于在主线程中刷新进度,顺带提一下,使用static修饰progresshandler是因为静态内部类默认不持有外部类对象的引用,需要注意一下handler的内存泄漏,使用一下写法:

//处理下载版本进度
 public class progresshandler extends handler{

 private weakreference<activity> mactivityweakreference;
 public progresshandler(activity activity){

 mactivityweakreference=new weakreference<activity>(activity);

 }

 @override
 public void handlemessage(message msg) {
 if(mactivityweakreference.get()!=null){
 amallloadbean amallloadbean= (amallloadbean) msg.obj;
 long progress=amallloadbean.getprogress();
 long total=amallloadbean.gettotal();
 float cp=(float)progress/(float)total;

 }
 }
 }

继续回到下载文件中,我才用的是retrofit+rxjava的方法来实现,写之前也看了一下别人写的,好像不全,下满也遇到了一些小坑,讲一下吧:

observable.subscribeon(schedulers.io())
 .observeon(schedulers.io())
 .doonnext(new action1<responsebody>() {
  @override
  public void call(responsebody responsebody) {

  savefiles(responsebody);
  }
 })
 .observeon(androidschedulers.mainthread())
 .subscribe(new observer<responsebody>() {
  @override
  public void oncompleted() {
  installapk();

  }

  @override
  public void onerror(throwable e) {

  toastutils.getinstance().showtoast("请到应用市场下载最新版本");
  }

  @override
  public void onnext(responsebody responsebody) {

  }
 });
 }

通过rxjava的doonnext在subscribe方法之前存储文件,这里需要注意的是doonnext方法需要在子线程中执行,调用.observeon(schedulers.io())方法,然后再切换到主线程,否则文件下载不下来。当文件下载完成时,在oncompleted方法中执行installapk()方法安装app。需要注意的是这里需要做权限的适配,因为我的是自己封装的因为就不拿出来了,挺简单就自己写吧。保存文件的代码给大家放出来了,通俗的语言:

/**
 * 保存文件
 */
 public void savefiles(responsebody responsebody){

 inputstream inputstream = null;

 fileoutputstream fileoutputstream = null;

 byte[] buffer=new byte[2048];

 int len;

 file file=new file(savefilename);

 if(!file.exists()){

 file.mkdirs();
 }

 try {
 inputstream=responsebody.bytestream();

 fileoutputstream=new fileoutputstream(file);

 while ((len=inputstream.read(buffer))!=-1){

 fileoutputstream.write(buffer,0,len);
 }

 inputstream.close();

 fileoutputstream.close();

 } catch (exception e) {
 e.printstacktrace();
 }


 }

在安装文件的时候,需要注意7.0以后的适配,代码看看就好,和拍照适配的原理一直,都是android对私密性文件的权限问题

 /**
 * 安装apk
 *
 */
 private void installapk() {
 file apkfile = new file(savefilename);
 if (!apkfile.exists()) {
 return;
 }
 //判断版本号

 if(build.version.sdk_int>=build.version_codes.n){
 uri apkuri = fileprovider.geturiforfile(activity, "******.fileprovider", apkfile);
 intent install = new intent(intent.action_view);
 install.setflags(intent.flag_activity_new_task);
 //添加这一句表示对目标应用临时授权该uri所代表的文件
 install.addflags(intent.flag_grant_read_uri_permission);
 install.setdataandtype(apkuri, "application/vnd.android.package-archive");
 activity.startactivity(install);

 }else{

 intent i = new intent(intent.action_view);
 i.setdataandtype(uri.parse("file://" + apkfile.tostring()), "application/vnd.android.package-archive");
 activity.startactivity(i);
 }

 }

基本上就这些,后续我会在此篇文章上继续补充。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。