Retrofit+RxJava實(shí)現(xiàn)帶進(jìn)度條的文件下載
項(xiàng)目中需要使用到更新版本,因此研究了一下Retrofit的下載文件,和進(jìn)度條效果,其間也遇到了一些坑,寫(xiě)出來(lái)加深一下記憶,也為別的同學(xué)提供一下思路。
先說(shuō)一下版本控制吧,通用做法基本上是通過(guò)接口獲取服務(wù)器存儲(chǔ)的app版本號(hào),與應(yīng)用的版本號(hào)進(jìn)行比較,版本較低就去更新,先看一下如何獲取應(yīng)用版本號(hào)吧
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方法來(lái)獲取PackageManager 對(duì)象,該對(duì)象可用于獲取版本的一些信息。
上面的屬于附內(nèi)容,接下來(lái)就是關(guān)于Retrofit+RxJava實(shí)現(xiàn)進(jìn)度條下載文件的功能,Retrofit本身不提供進(jìn)度條顯示的功能,但Retrofit默認(rèn)使用Okhttp來(lái)進(jìn)行網(wǎng)絡(luò)請(qǐng)求,這里就可以自定義攔截器來(lái)進(jìn)行攔截,實(shí)現(xiàn)進(jìn)度。Okhttp的Demo中也為我們提供了一份代碼,需要的可以去參考一下Progress.javar,可以看到攔截器的設(shè)置:
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 {
//當(dāng)前讀取字節(jié)數(shù)
long bytesRead = super.read(sink, byteCount);
//增加當(dāng)前讀取的字節(jié)數(shù),如果讀取完成了bytesRead會(huì)返回-1
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
//回調(diào),如果contentLength()不知道長(zhǎng)度,會(huì)返回-1
progressListener.onProgress(totalBytesRead,responseBody.contentLength(),bytesRead,bytesRead==-1);
return bytesRead;
}
};
}
}
ProgressListener 用來(lái)監(jiān)聽(tīng)進(jìn)度變化,回調(diào)到ProgressInterceptor中,ProgressInterceptor是一個(gè)自定義的攔截器,可以看一下代碼
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,可以通過(guò)OkHttpClient的addNetworkInterceptor方法直接添加一個(gè)自定義的攔截器,例如:
//為Okhttp設(shè)置攔截器
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();
//進(jìn)度回調(diào)監(jiān)聽(tīng)
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);
}
};
這里通過(guò)一個(gè)創(chuàng)建一個(gè)繼承自Handler的ProgressHandler靜態(tài)內(nèi)部類(lèi)用于在主線程中刷新進(jìn)度,順帶提一下,使用static修飾ProgressHandler是因?yàn)殪o態(tài)內(nèi)部類(lèi)默認(rèn)不持有外部類(lèi)對(duì)象的引用,需要注意一下Handler的內(nèi)存泄漏,使用一下寫(xiě)法:
//處理下載版本進(jìn)度
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;
}
}
}
繼續(xù)回到下載文件中,我才用的是Retrofit+RxJava的方法來(lái)實(shí)現(xiàn),寫(xiě)之前也看了一下別人寫(xiě)的,好像不全,下滿也遇到了一些小坑,講一下吧:
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("請(qǐng)到應(yīng)用市場(chǎng)下載最新版本");
}
@Override
public void onNext(ResponseBody responseBody) {
}
});
}
通過(guò)RxJava的doOnNext在subscribe方法之前存儲(chǔ)文件,這里需要注意的是doOnNext方法需要在子線程中執(zhí)行,調(diào)用.observeOn(Schedulers.io())方法,然后再切換到主線程,否則文件下載不下來(lái)。當(dāng)文件下載完成時(shí),在onCompleted方法中執(zhí)行installApk()方法安裝app。需要注意的是這里需要做權(quán)限的適配,因?yàn)槲业氖亲约悍庋b的因?yàn)榫筒荒贸鰜?lái)了,挺簡(jiǎn)單就自己寫(xiě)吧。保存文件的代碼給大家放出來(lái)了,通俗的語(yǔ)言:
/**
* 保存文件
*/
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();
}
}
在安裝文件的時(shí)候,需要注意7.0以后的適配,代碼看看就好,和拍照適配的原理一直,都是Android對(duì)私密性文件的權(quán)限問(wèn)題
/**
* 安裝apk
*
*/
private void installApk() {
File apkfile = new File(saveFileName);
if (!apkfile.exists()) {
return;
}
//判斷版本號(hào)
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);
//添加這一句表示對(duì)目標(biāo)應(yīng)用臨時(shí)授權(quán)該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);
}
}
基本上就這些,后續(xù)我會(huì)在此篇文章上繼續(xù)補(bǔ)充。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Spring Boot企業(yè)常用的starter示例詳解
這篇文章主要給大家介紹了關(guān)于Spring Boot企業(yè)常用starter的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
springboot無(wú)法從靜態(tài)上下文中引用非靜態(tài)變量的解決方法
這篇文章主要介紹了springboot無(wú)法從靜態(tài)上下文中引用非靜態(tài)變量的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-06-06
Spring Boot管理用戶數(shù)據(jù)的操作步驟
SpringBoot結(jié)合Thymeleaf模板引擎,可以快速搭建Web應(yīng)用,介紹了使用SpringBoot處理JSON數(shù)據(jù)的基本過(guò)程,包括創(chuàng)建實(shí)體類(lèi)、視圖頁(yè)面和控制器,通過(guò)這些步驟,即可完成基于SpringBoot和Thymeleaf的簡(jiǎn)單Web開(kāi)發(fā),感興趣的朋友跟隨小編一起看看吧2024-09-09
基于java枚舉類(lèi)綜合應(yīng)用的說(shuō)明
一個(gè)枚舉類(lèi),可以看成包括它的一些子類(lèi)(枚舉)的一個(gè)類(lèi),而且枚舉類(lèi)的構(gòu)造方法只能是私有的2013-05-05
Activiti如何啟動(dòng)流程并使流程前進(jìn)
這篇文章主要介紹了Activiti如何啟動(dòng)流程并使流程前進(jìn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
關(guān)于springboot-starter-undertow和tomcat的區(qū)別說(shuō)明
這篇文章主要介紹了關(guān)于springboot-starter-undertow和tomcat的區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03

