当前位置: 移动技术网 > 移动技术>移动开发>Android > Android Retrofit文件下载进度显示问题的解决方法

Android Retrofit文件下载进度显示问题的解决方法

2019年07月24日  | 移动技术网移动技术  | 我要评论

综述

  在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。  

源码下载:

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网