前言
在開(kāi)發(fā)項(xiàng)目中不免會(huì)遇到下載文件的需求,如果正好使用的是Retrofit網(wǎng)絡(luò)框架又不想使用其它網(wǎng)絡(luò)框架或者手寫(xiě)的HttpUrlConnection,那只好在Retrofit的基礎(chǔ)上封裝下載。
功能描述
- 支持下載大文件
- 支持多個(gè)頁(yè)面監(jiān)聽(tīng)
- 支持進(jìn)度回調(diào)監(jiān)聽(tīng)
使用說(shuō)明
- 例一
通過(guò)AppDownLoadHelper設(shè)置下載地址、存儲(chǔ)位置即可下載文件。
還可以配置AppProgressListener或者設(shè)置Tag來(lái)滿足更多的需求。
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", "========開(kāi)始");
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();
}
}
- 例二
根據(jù)Tag獲取指定的AppDownLoadHelper,AppDownLoadHelper是下載的總控制器只要獲取到它就可以對(duì)正在下載的文件進(jìn)行取消、進(jìn)度監(jiān)聽(tīng)操作。
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);
}
}
}
源碼解讀
- 一、定義下載器:負(fù)責(zé)下載功能
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);
}
}
}
/**
* 默認(rèn)分配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;
}
/**
* 設(shè)置任務(wù)標(biāo)記
*/
public Builder setTag(Object tag) {
this.mTag = tag;
return this;
}
/**
* 設(shè)置下載文件的URL
*/
public Builder setUrl(String url) {
this.mUrl = url;
return this;
}
/**
* 設(shè)置HTTP請(qǐng)求連接超時(shí)時(shí)間,默認(rèn)10s
*/
public Builder setConnectionTimeout(int timeout) {
this.mConnectionTimeout = timeout;
return this;
}
/**
* 設(shè)置HTTP請(qǐng)求數(shù)據(jù)讀取超時(shí)時(shí)間,默認(rèn)20s
*/
public Builder setReadTimeout(int timeout) {
this.mReadTimeout = timeout;
return this;
}
/**
* 設(shè)置下載文件保存地址,使用絕對(duì)路徑
*/
public Builder setPath(String path) {
this.mPath = path;
return this;
}
/**
* 設(shè)置下載監(jiān)聽(tīng)
*/
public Builder setDownLoadListener(AppProgressListener listener) {
this.mListener = listener;
return this;
}
/**
* 創(chuàng)建一個(gè)本地任務(wù)
*/
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());
省略... 數(shù)據(jù)存儲(chǔ)本地操作
long endTime = System.currentTimeMillis();
Log.i("AppDownLoadHelper", "========time" + (endTime - startTime));
}
});
if (mListener != null) {
mListener.onStart();
}
}
}
- 二、定義下載管理者:通過(guò)Map管理單個(gè)或多個(gè)下載器,并分發(fā)每個(gè)下載器的狀態(tài)。
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:
//任務(wù)開(kāi)始
for (AppProgressListener listener:
helper.mDownloadListeners) {
listener.onStart();
}
break;
case PROGRESSING:
//任務(wù)進(jìn)度更新
Bundle data = msg.getData();
for (AppProgressListener listener: helper.mDownloadListeners) {
listener.update(data.getLong("bytesRead"), data.getLong("contentLength"), data.getBoolean("done"));
}
break;
case COMPLETED:
//任務(wù)完成
for (AppProgressListener listener:
helper.mDownloadListeners) {
listener.onCompleted();
}
helper.mDownloadListeners.clear();
mTagToHelpers.remove(tagID);
break;
case EXCEPTION:
//任務(wù)發(fā)生異常
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);
}
}
}
- 三、網(wǎng)絡(luò)請(qǐng)求API
public interface AppDownloadService {
@Streaming
@GET("{url}")
Observable <ResponseBody> download(@Path("url")String apkName);
}
- 四、下載進(jìn)度回調(diào)接口
public interface AppProgressListener {
void onStart();
void update(long bytesRead, long contentLength, boolean done);
void onCompleted();
void onError(String err);
}
- 五、Retrofit使用中的攔截器:用于對(duì)下載進(jìn)度的攔截
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();
// 添加新的參數(shù)
HttpUrl.Builder authorizedUrlBuilder = oldRequest.url().newBuilder().scheme(oldRequest.url().scheme()).host(oldRequest.url().host());
// 設(shè)置統(tǒng)一請(qǐng)求參數(shù)
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:通過(guò)配置在攔截器里對(duì)下載進(jìn)度監(jiān)聽(tīng)
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;
}
};
}
}