Android Retrofit 파일 다운로드 진행 상황 표시 문제 해결 방법

종합 하여 서술 하 다
Retrofit 2.0 에서 상세 하 게 이 글 을 사용 하여 retrofit 의 용법 을 상세 하 게 소개 하 였 다.또한 retrofit 에서 우 리 는 Response Body 를 통 해 파일 을 다운로드 할 수 있 습 니 다.그러나 retrofit 에 서 는 다운로드 진 도 를 표시 하 는 인 터 페 이 스 를 제공 하지 않 았 다.프로젝트 에서 만약 에 사용자 가 파일 을 다운로드 하면 실시 간 으로 사용자 에 게 다운로드 진 도 를 표시 할 수 없 기 때문에 사용자 의 체험 도 매우 나쁘다.그럼 retrofit 에서 파일 다운로드 에 사용 되 는 다운로드 진 도 를 실시 간 으로 추적 하 는 방법 을 소개 합 니 다.
시범 을 보이다

Retrofit 파일 다운로드 진도 업데이트 실현
retrofit 2.0 에서 그 는 Okhttp 에 의존 하기 때문에 우리 가 이 문 제 를 해결 하려 면 이 OKhttp 에서 시작 해 야 한다.Okhttp 에 의존 패키지 Okio 가 있 습 니 다.Okio 도 square 회사 가 개발 한 것 으로 자바.io 와 자바.nio 의 보충 으로 데 이 터 를 쉽게 접근 하고 저장 하 며 처리 할 수 있 습 니 다.여기 서 는 Okio 의 Source 류 를 사용 해 야 합 니 다.여기 서 Source 는 InputStream 이 라 고 볼 수 있 습 니 다.오 키 오의 자세 한 사용 은 여기 서 소개 하지 않 겠 습 니 다.구체 적 인 실현 을 살 펴 보 자.
여기 서 우 리 는 먼저 인 터 페 이 스 를 써 서 다운로드 의 진 도 를 감청 하 는 데 사용한다.파일 의 다운로드 에 대해 우 리 는 다운로드 의 진도,파일 의 총 크기,그리고 작업 이 완료 되 었 는 지 알 아야 한다.그래서 아래 의 인터페이스 가 생 겼 다.

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);
}
파일 다운로드 에 대해 서 는 Response Body 류 의 일부 방법 을 다시 써 야 합 니 다.

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;
   }
  };
 }
}
위의 ProgressResponse Body 클래스 에서 읽 은 파일 의 바이트 수 를 계산 하고 ProgressListener 인 터 페 이 스 를 호출 했 습 니 다.그래서 이 ProgressListener 인 터 페 이 스 는 하위 스 레 드 에서 실 행 됩 니 다.
이 Progress Response Body 를 어떻게 사용 하 는 지 살 펴 보 겠 습 니 다.

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
",(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 에 차단 기 를 추가 해서 사용자 정의 ProgressResponse Body 를 사용 합 니 다.그리고 여기 서 우 리 는 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;
 }
}
그리고 Progress Handler 클래스 를 만 들 고 있 습 니 다.

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);
  }
 }

}

위의 Progress Handler 는 추상 적 인 부류 이다.여기 서 우 리 는 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
",(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 를 통 해 이 루어 진 것 을 알 수 있다.바로 retrofit 2.0 에서 OkHttp 에 의존 하기 때문에 OkHttp 의 기능 retrofit 에 도 모두 갖 추고 있다.이 기능 을 이용 하여,우 리 는 맞 춤 형 OkhttpClient 를 통 해 우리 의 retrofit 를 설정 할 수 있 습 니 다.
원본 다운로드:https://github.com/lijiangdong/retrofit-example
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기