前言
在开发项目中不免会遇到下载文件的需求,如果正好使用的是Retrofit网络框架又不想使用其它网络框架或者手写的HttpUrlConnection,那只好在Retrofit的基础上封装下载。
功能描述
- 支持下载大文件
- 支持多个页面监听
- 支持进度回调监听
使用说明
- 例一
通过AppDownLoadHelper设置下载地址、存储位置即可下载文件。
还可以配置AppProgressListener或者设置Tag来满足更多的需求。
public class MainActivity extends AppCompatActivity {
private Button mDownButton;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDownButton = (Button) findViewById(R.id.main_button_down);
mDownButton.setOnClickListener(mListener);
findViewById(R.id.main_button_start).setOnClickListener(new View.OnClickListener() {@Override public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, Main2Activity.class);
startActivity(intent);
}
});
}
View.OnClickListener mListener = new View.OnClickListener() {@Override public void onClick(View view) {
AppDownLoadHelper helper = AppDownLoadManager.getInstance().getHelperByTag("xx");
if (helper != null) {
AppDownLoadManager.getInstance().cancelHelperByTag("xx");
mDownButton.setText("下载");
return;
}
new AppDownLoadHelper.Builder().setPath(SDCardUtil.getLogCacheDir(AppApplication.getContext()) + "/xxx1.apk").setTag("xx").setUrl("http://dl.play.91.com/bigdata/com.ilongyuan.voez.baidu_v1.0.4.apk").setDownLoadListener(new AppProgressListener() {@Override public void onStart() {
Log.i("tag", "========开始");
mDownButton.setText("0%");
}
@Override public void update(long bytesRead, long contentLength, boolean done) {
int read = (int)(bytesRead * 100f / contentLength);
Log.i("tag", "========" + read);
mDownButton.setText(read + "%");
}
@Override public void onCompleted() {
mDownButton.setText("完成");
Log.i("tag", "========" + Thread.currentThread().getName());
}
@Override public void onError(String err) {
mDownButton.setText("失败");
Log.i("tag", "========失败" + err);
}
}).create().execute();
}
};
@Override protected void onDestroy() {
super.onDestroy();
AppDownLoadManager.getInstance().unsubscribe();
}
}
- 例二
根据Tag获取指定的AppDownLoadHelper,AppDownLoadHelper是下载的总控制器只要获取到它就可以对正在下载的文件进行取消、进度监听操作。
public class Main2Activity extends AppCompatActivity {
private AppDownLoadHelper mHelper;
private Button mDownButton;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mDownButton = (Button) findViewById(R.id.main2_button_down);
mHelper = AppDownLoadManager.getInstance().getHelperByTag("xx");
if (mHelper != null) {
mHelper.registerListener(mProgressLinstener);
}
}
AppProgressListener mProgressLinstener = new AppProgressListener() {
@Override public void onStart() {}
@Override public void update(long bytesRead, long contentLength, boolean done) {
int read = (int)(bytesRead * 100f / contentLength);
mDownButton.setText(read + "%");
}
@Override public void onCompleted() {
mDownButton.setText("完成");
}
@Override public void onError(String err) {}
};
@Override protected void onDestroy() {
super.onDestroy();
if (mHelper != null) {
mHelper.unRegisterListener(mProgressLinstener);
}
}
}
源码解读
- 一、定义下载器:负责下载功能
public class AppDownLoadHelper {
省略...
private AppDownLoadHelper() {
mDownloadListeners = new HashSet < >();
mManager = AppDownLoadManager.getInstance();
}
public void setCancel(boolean cancel) {
isCancel = cancel;
}
public void setmUrl(String url) {
if (url != null) {
int lastIndex = url.lastIndexOf("/");
if (lastIndex != -1) {
mApkName = url.substring(lastIndex + 1);
mBaseUrl = url.substring(0, lastIndex + 1);
}
}
}
/**
* 默认分配tag
*/
public void setTag(Object tag) {
if (tag != null) {
mTag = tag;
} else {
mTag = UUID.randomUUID().toString();
}
}
public void registerListener(AppProgressListener listener) {
mDownloadListeners.add(listener);
}
public void unRegisterListener(AppProgressListener listener) {
mDownloadListeners.remove(listener);
}
private OkHttpClient getDefaultOkHttp() {
return getBuilder().build();
}
private OkHttpClient.Builder getBuilder() {
AppSigningInterceptor signingInterceptor = new AppSigningInterceptor();
signingInterceptor.setProgressListener(mListener);
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(signingInterceptor);
builder.connectTimeout(mConnectionTimeout, TimeUnit.SECONDS);
builder.readTimeout(mReadTimeout, TimeUnit.SECONDS);
return builder;
}
public static class Builder {
private AppProgressListener mListener;
private String mUrl;
private String mPath;
private int mConnectionTimeout;
private int mReadTimeout;
private Object mTag;
public Builder() {
this.mConnectionTimeout = CONNECTION_TIMEOUT;
this.mReadTimeout = READ_DOWN_TIMEOUT;
}
/**
* 设置任务标记
*/
public Builder setTag(Object tag) {
this.mTag = tag;
return this;
}
/**
* 设置下载文件的URL
*/
public Builder setUrl(String url) {
this.mUrl = url;
return this;
}
/**
* 设置HTTP请求连接超时时间,默认10s
*/
public Builder setConnectionTimeout(int timeout) {
this.mConnectionTimeout = timeout;
return this;
}
/**
* 设置HTTP请求数据读取超时时间,默认20s
*/
public Builder setReadTimeout(int timeout) {
this.mReadTimeout = timeout;
return this;
}
/**
* 设置下载文件保存地址,使用绝对路径
*/
public Builder setPath(String path) {
this.mPath = path;
return this;
}
/**
* 设置下载监听
*/
public Builder setDownLoadListener(AppProgressListener listener) {
this.mListener = listener;
return this;
}
/**
* 创建一个本地任务
*/
public AppDownLoadHelper create() {
final AppDownLoadHelper helper = new AppDownLoadHelper();
helper.mConnectionTimeout = mConnectionTimeout;
helper.mReadTimeout = mReadTimeout;
helper.mPath = mPath;
helper.setTag(mTag);
helper.setmUrl(mUrl);
if (mListener != null) {
helper.mDownloadListeners.add(mListener);
}
return helper;
}
}
public void execute() {
mManager.addHelper(this);
mAdapter = new Retrofit.Builder().baseUrl(mBaseUrl).addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJavaCallAdapterFactory.create()).client(getDefaultOkHttp()).build();
mUploadService = mAdapter.create(AppDownloadService.class);
final long startTime = System.currentTimeMillis();
mUploadService.download(mApkName).subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).subscribe(new Subscriber < ResponseBody > () {
@Override public void onCompleted() {
if (isCancel) {
mListener.onError("cancel");
} else if (mListener != null) {
mListener.onCompleted();
}
}
@Override public void onError(Throwable e) {
if (mListener != null) {
mListener.onError(e.getMessage());
}
}
@Override public void onNext(ResponseBody responseBody) {
Log.i("AppDownLoadHelper", "========next" + responseBody.contentLength());
省略... 数据存储本地操作
long endTime = System.currentTimeMillis();
Log.i("AppDownLoadHelper", "========time" + (endTime - startTime));
}
});
if (mListener != null) {
mListener.onStart();
}
}
}
- 二、定义下载管理者:通过Map管理单个或多个下载器,并分发每个下载器的状态。
public class AppDownLoadManager {
public static final int START = 1;
public static final int PROGRESSING = 2;
public static final int COMPLETED = 3;
public static final int EXCEPTION = 4;
private static AppDownLoadManager mInstance;
private ConcurrentHashMap <Object, AppDownLoadHelper> mTagToHelpers;
private Handler mUIHandler;
private AppDownLoadManager() {
mTagToHelpers = new ConcurrentHashMap <Object, AppDownLoadHelper> ();
mUIHandler = new UIHandler(mTagToHelpers);
}
public synchronized static AppDownLoadManager getInstance() {
if (mInstance == null) {
mInstance = new AppDownLoadManager();
}
return mInstance;
}
public void addHelper(final AppDownLoadHelper helper) {
if (helper == null) return;
helper.mListener = new AppProgressListener() {@Override public void onStart() {
Message message = mUIHandler.obtainMessage();
message.obj = helper.mTag;
message.what = START;
message.sendToTarget();
}
@Override public void update(long bytesRead, long contentLength, boolean done) {
Message message = mUIHandler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putLong("bytesRead", bytesRead);
bundle.putLong("contentLength", contentLength);
bundle.putBoolean("done", done);
message.setData(bundle);
message.obj = helper.mTag;
message.what = PROGRESSING;
message.sendToTarget();
}
@Override public void onCompleted() {
Message message = mUIHandler.obtainMessage();
message.obj = helper.mTag;
message.what = COMPLETED;
message.sendToTarget();
}
@Override public void onError(String err) {
Message message = mUIHandler.obtainMessage();
message.obj = helper.mTag;
message.what = EXCEPTION;
Bundle bundle = new Bundle();
bundle.putString("err", err);
message.setData(bundle);
message.sendToTarget();
}
};
mTagToHelpers.put(helper.mTag, helper);
}
public AppDownLoadHelper getHelperByTag(Object tag) {
return tag == null ? null: mTagToHelpers.get(tag);
}
public void cancelHelperByTag(Object tag) {
AppDownLoadHelper helper = mTagToHelpers.get(tag);
if (helper != null) {
helper.setCancel(true);
helper.mDownloadListeners.clear();
mTagToHelpers.remove(tag);
}
}
private static class UIHandler extends Handler {
private ConcurrentHashMap < Object,
AppDownLoadHelper > mTagToHelpers;
public UIHandler(ConcurrentHashMap < Object, AppDownLoadHelper > tagToHelpers) {
mTagToHelpers = tagToHelpers;
}
@Override public void handleMessage(Message msg) {
super.handleMessage(msg);
String tagID = (String) msg.obj;
AppDownLoadHelper helper = mTagToHelpers.get(tagID);
if (helper == null) return;
switch (msg.what) {
case START:
//任务开始
for (AppProgressListener listener:
helper.mDownloadListeners) {
listener.onStart();
}
break;
case PROGRESSING:
//任务进度更新
Bundle data = msg.getData();
for (AppProgressListener listener: helper.mDownloadListeners) {
listener.update(data.getLong("bytesRead"), data.getLong("contentLength"), data.getBoolean("done"));
}
break;
case COMPLETED:
//任务完成
for (AppProgressListener listener:
helper.mDownloadListeners) {
listener.onCompleted();
}
helper.mDownloadListeners.clear();
mTagToHelpers.remove(tagID);
break;
case EXCEPTION:
//任务发生异常
Bundle e = msg.getData();
for (AppProgressListener listener: helper.mDownloadListeners) {
listener.onError(e.getString("err"));
}
helper.mDownloadListeners.clear();
helper.setCancel(true);
mTagToHelpers.remove(tagID);
break;
default:
break;
}
}
}
public void unsubscribe() {
for (Object key:
mTagToHelpers.keySet()) {
AppDownLoadHelper appDownLoadHelper = mTagToHelpers.get(key);
appDownLoadHelper.setCancel(true);
appDownLoadHelper.mDownloadListeners.clear();
mTagToHelpers.remove(key);
}
}
}
- 三、网络请求API
public interface AppDownloadService {
@Streaming
@GET("{url}")
Observable <ResponseBody> download(@Path("url")String apkName);
}
- 四、下载进度回调接口
public interface AppProgressListener {
void onStart();
void update(long bytesRead, long contentLength, boolean done);
void onCompleted();
void onError(String err);
}
- 五、Retrofit使用中的拦截器:用于对下载进度的拦截
public class AppSigningInterceptor implements Interceptor {
public AppProgressListener mProgressListener;
public void setProgressListener(AppProgressListener p) {
mProgressListener = p;
}
@Override public Response intercept(Chain chain) throws IOException {
Request oldRequest = chain.request();
// 添加新的参数
HttpUrl.Builder authorizedUrlBuilder = oldRequest.url().newBuilder().scheme(oldRequest.url().scheme()).host(oldRequest.url().host());
// 设置统一请求参数
Request.Builder newRequest = oldRequest.newBuilder().method(oldRequest.method(), oldRequest.body())
.url(authorizedUrlBuilder.build());
if (mProgressListener != null) {
Response originalResponse = chain.proceed(newRequest.build());
return originalResponse.newBuilder().body(new ProgressResponseBody(originalResponse.body(), mProgressListener)).build();
}
return chain.proceed(newRequest.build());
}
}
- 六、自定义ResponseBody:通过配置在拦截器里对下载进度监听
public class ProgressResponseBody extends ResponseBody {
private final ResponseBody responseBody;
private final AppProgressListener progressListener;
private BufferedSource bufferedSource;
public ProgressResponseBody(ResponseBody responseBody, AppProgressListener 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.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
return bytesRead;
}
};
}
}