Retrofit+RxJava实现带进度条的文件下载
项目中需要使用到更新版本,因此研究了一下Retrofit的下载文件,和进度条效果,其间也遇到了一些坑,写出来加深一下记忆,也为别的同学提供一下思路。
先说一下版本控制吧,通用做法基本上是通过接口获取服务器存储的app版本号,与应用的版本号进行比较,版本较低就去更新,先看一下如何获取应用版本号吧
PackageManagerpackageManager=mActivity.getPackageManager(); PackageInfopackageInfo=null; try{ packageInfo=packageManager.getPackageInfo(mActivity.getPackageName(),0); }catch(PackageManager.NameNotFoundExceptione){ e.printStackTrace(); } StringversionName=packageInfo.versionName;
可以看到使用的是Context中的getPackageManager方法来获取PackageManager对象,该对象可用于获取版本的一些信息。
上面的属于附内容,接下来就是关于Retrofit+RxJava实现进度条下载文件的功能,Retrofit本身不提供进度条显示的功能,但Retrofit默认使用Okhttp来进行网络请求,这里就可以自定义拦截器来进行拦截,实现进度。Okhttp的Demo中也为我们提供了一份代码,需要的可以去参考一下Progress.javar,可以看到拦截器的设置:
publicclassProgressResponseBodyextendsResponseBody{ privateResponseBodyresponseBody; privateProgressListenerprogressListener; privateBufferedSourcebufferedSource; publicProgressResponseBody(ResponseBodyresponseBody,ProgressListenerprogressListener){ this.responseBody=responseBody; this.progressListener=progressListener; } @Override publicMediaTypecontentType(){ returnresponseBody.contentType(); } @Override publiclongcontentLength(){ returnresponseBody.contentLength(); } @Override publicBufferedSourcesource(){ if(bufferedSource==null){ bufferedSource=Okio.buffer(source(responseBody.source())); } returnbufferedSource; } privateSourcesource(Sourcesource){ returnnewForwardingSource(source){ longtotalBytesRead=0L; @Override publiclongread(Buffersink,longbyteCount)throwsIOException{ //当前读取字节数 longbytesRead=super.read(sink,byteCount); //增加当前读取的字节数,如果读取完成了bytesRead会返回-1 totalBytesRead+=bytesRead!=-1?bytesRead:0; //回调,如果contentLength()不知道长度,会返回-1 progressListener.onProgress(totalBytesRead,responseBody.contentLength(),bytesRead,bytesRead==-1); returnbytesRead; } }; } }
ProgressListener用来监听进度变化,回调到ProgressInterceptor中,ProgressInterceptor是一个自定义的拦截器,可以看一下代码
publicclassProgressInterceptorimplementsInterceptor{ @Override publicResponseintercept(Chainchain)throwsIOException{ Responseresponse=chain.proceed(chain.request()); returnresponse.newBuilder().body(newProgressResponseBody(response.body(),progressListener)).build(); } staticfinalProgressListenerprogressListener=newProgressListener(){ @Override publicvoidonProgress(longprogress,longtotal,longspeed,booleandone){ Log.i("log","progress="+progress+"total="+total); } }; }
为了便于获取progress,可以通过OkHttpClient的addNetworkInterceptor方法直接添加一个自定义的拦截器,例如:
//为Okhttp设置拦截器 OkHttpClientclient=newOkHttpClient.Builder() .addNetworkInterceptor(newInterceptor(){ @OverridepublicResponseintercept(Chainchain)throwsIOException{ ResponseoriginalResponse=chain.proceed(chain.request()); returnoriginalResponse.newBuilder() .body(newProgressResponseBody(originalResponse.body(),progressListener)) .build(); } }) .build(); //进度回调监听 ProgressListenerprogressListener=newProgressListener(){ @Override publicvoidonProgress(longprogress,longtotal,longspeed,booleandone){ Messagemessage=newMessage(); message.obj=newAmallLoadBean(progress,total); progressHandler.sendMessage(message); } };
这里通过一个创建一个继承自Handler的ProgressHandler静态内部类用于在主线程中刷新进度,顺带提一下,使用static修饰ProgressHandler是因为静态内部类默认不持有外部类对象的引用,需要注意一下Handler的内存泄漏,使用一下写法:
//处理下载版本进度 publicclassProgressHandlerextendsHandler{ privateWeakReferencemActivityWeakReference; publicProgressHandler(Activityactivity){ mActivityWeakReference=newWeakReference (activity); } @Override publicvoidhandleMessage(Messagemsg){ if(mActivityWeakReference.get()!=null){ AmallLoadBeanamallLoadBean=(AmallLoadBean)msg.obj; longprogress=amallLoadBean.getProgress(); longtotal=amallLoadBean.getTotal(); floatcp=(float)progress/(float)total; } } }
继续回到下载文件中,我才用的是Retrofit+RxJava的方法来实现,写之前也看了一下别人写的,好像不全,下满也遇到了一些小坑,讲一下吧:
observable.subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) .doOnNext(newAction1(){ @Override publicvoidcall(ResponseBodyresponseBody){ saveFiles(responseBody); } }) .observeOn(AndroidSchedulers.mainThread()) .subscribe(newObserver (){ @Override publicvoidonCompleted(){ installApk(); } @Override publicvoidonError(Throwablee){ ToastUtils.getInstance().showToast("请到应用市场下载最新版本"); } @Override publicvoidonNext(ResponseBodyresponseBody){ } }); }
通过RxJava的doOnNext在subscribe方法之前存储文件,这里需要注意的是doOnNext方法需要在子线程中执行,调用.observeOn(Schedulers.io())方法,然后再切换到主线程,否则文件下载不下来。当文件下载完成时,在onCompleted方法中执行installApk()方法安装app。需要注意的是这里需要做权限的适配,因为我的是自己封装的因为就不拿出来了,挺简单就自己写吧。保存文件的代码给大家放出来了,通俗的语言:
/** *保存文件 */ publicvoidsaveFiles(ResponseBodyresponseBody){ InputStreaminputStream=null; FileOutputStreamfileOutputStream=null; byte[]buffer=newbyte[2048]; intlen; Filefile=newFile(saveFileName); if(!file.exists()){ file.mkdirs(); } try{ inputStream=responseBody.byteStream(); fileOutputStream=newFileOutputStream(file); while((len=inputStream.read(buffer))!=-1){ fileOutputStream.write(buffer,0,len); } inputStream.close(); fileOutputStream.close(); }catch(Exceptione){ e.printStackTrace(); } }
在安装文件的时候,需要注意7.0以后的适配,代码看看就好,和拍照适配的原理一直,都是Android对私密性文件的权限问题
/** *安装卆pk * */ privatevoidinstallApk(){ Fileapkfile=newFile(saveFileName); if(!apkfile.exists()){ return; } //判断版本号 if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){ UriapkUri=FileProvider.getUriForFile(activity,"******.fileprovider",apkfile); Intentinstall=newIntent(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{ Intenti=newIntent(Intent.ACTION_VIEW); i.setDataAndType(Uri.parse("file://"+apkfile.toString()),"application/vnd.android.package-archive"); activity.startActivity(i); } }
基本上就这些,后续我会在此篇文章上继续补充。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。