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

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

程序员文章站 2024-02-22 11:51:22
综述   在retrofit2.0使用详解这篇文章中详细介绍了retrofit的用法。并且在retrofit中我们可以通过responsebody进行对文件的下载。但是在...

综述

  在retrofit2.0使用详解这篇文章中详细介绍了retrofit的用法。并且在retrofit中我们可以通过responsebody进行对文件的下载。但是在retrofit中并没有为我们提供显示下载进度的接口。在项目中,若是用户下载一个文件,无法实时给用户显示下载进度,这样用户的体验也是非常差的。那么下面就介绍一下在retrofit用于文件的下载如何实时跟踪下载进度。

演示

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

源码下载:

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