Retrofit+RxJava實現帶進度條的文件下載

這篇文章主要爲大家詳細介紹了Retrofit+RxJava實現帶進度條的文件下載,具有一定的參考價值,感興趣的小夥伴們可以參考一下

項目中需要使用到更新版本,因此研究了一下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);
 }

 }

基本上就這些,後續我會在此篇文章上繼續補充。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持神馬文庫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章