實現(xiàn)DownloadService(下載服務)
這個DownloadService.java
就是一個Android
服務,主要職責有兩個:
- 請求網(wǎng)絡(將
NetKit
中實例化得到的Call
執(zhí)行)儡率。 - 將請求到的數(shù)據(jù)流寫入到內(nèi)存中膝昆。
そして、還有兩個隱性的待解決的問題:
- 下載線程控制問題凸舵。
- 將下載進度廣播通知(好吧祖娘,不是Android的廣播,字面意思啊奄,你懂的渐苏,詞窮)的問題。
帶著這 兩個問題菇夸,開始思考琼富,怎么解決呢?經(jīng)過思考后庄新,我最終是這樣處理的:
- 下載進度通知:在
Callback
鞠眉、Broadcast
之間,我還是覺得Callback
更方便择诈,所以就它了械蹋。 - 線程控制:
Executors.newCachedThreadPool()
。至于為什么羞芍,因為我懶哗戈。。荷科。
Just write the fucking code!
DownloadService.java
/**
* 下載服務
* 只能通過獲取Binder后添加下載任務
*/
public class DownloadService extends Service {
private static final String TAG = "DownloadService";
private static final String HEADER_RANGE = "bytes=%1$s-";
private IBinder mDownloadServiceBinder = new DownloadServiceBinder();
private Handler mHandler = null;
public DownloadService() {
}
@Override
public IBinder onBind(Intent intent) {
try {
return mDownloadServiceBinder;
} catch (Exception e) {
return null;
}
}
/**
* 通過Binder與外部進行關(guān)聯(lián)
*/
public class DownloadServiceBinder extends Binder {
/**
* 通過這方法暴露給外部調(diào)用下載方法
*
* @param downloadTask 下載任務
*/
public void startDownload(final DownloadTask downloadTask,Handler handler) {
setTaskHandler(handler);
addToQueue(downloadTask);
}
}
/**
* 設置傳遞的Handler
*
* @param handler handler
*/
private void setTaskHandler(Handler handler) {
mHandler = handler;
}
/**
* 開始下載任務
*
* @param downloadTask 下載任務
*/
private void addToQueue(final DownloadTask downloadTask) {
if (downloadTask.isResumeBrokenTransfer()) {
String range = String.format(HEADER_RANGE, downloadTask.getFileLength());
downloadTask.setHeaders("Range", range);
} else { //不進行斷點續(xù)傳唯咬,刪除文件
FileUtil.deleteFile(downloadTask.getFile());
}
new Thread(new Runnable() {
@Override
public void run() {
download(downloadTask);
}
}).start();
}
/**
* 開始下載
*采用非異步執(zhí)行`Call`方法纱注,盡量減少使用匿名內(nèi)部類
* @param downloadTask 下載任務
*/
private void download(DownloadTask downloadTask) {
Call call = NetKit.getKit()
.get(downloadTask.getUrl(), Headers.of(downloadTask.getHeaders()));
try {
Response response = call.execute();
downloadTask.setCall(call);
if (response.body() != null && response.body() instanceof ProgressResponseBody) {
ProgressResponseBody responseBody = (ProgressResponseBody) response.body();
responseBody.setDownloadTask(downloadTask,mHandler);
}
if ((response.code() == 200 || response.code() == 206) && response.body() == null) { //返回的code及body錯誤
// DownloadManager.getInstance().downloadFaild(downloadTask, CODE_DOWNLOAD_UNKNOW_ERROR);
sendMsg(DownloadManager.HANDLER_DOWNLOAD_FAILED, downloadTask);
return;
}
boolean isAppend = false; //判斷是否支持斷點續(xù)傳
if (response.code() == 206) {
isAppend = !TextUtils.isEmpty(response.headers().get("Content-Range"));
}
File file = FileUtil.sink(downloadTask.getFile(), response.body().source(), isAppend);
if (downloadTask.isPause()) {
return; //暫停下載,不做處理
}
if (file.exists() && file.length() == downloadTask.getContentLength()) {
// downloadTask.setFile(file);
// DownloadManager.getInstance().downloadComplete(downloadTask);
sendMsg(DownloadManager.HANDLER_DOWNLOAD_COMPLETED, downloadTask);
} else {
// DownloadManager.getInstance().downloadFaild(downloadTask, CODE_DOWNLOAD_FILE_SIZE_EXCEPTION);
sendMsg(DownloadManager.HANDLER_DOWNLOAD_FAILED, downloadTask);
}
} catch (IOException e) {
e.printStackTrace();
if (downloadTask.isPause())
return;
// DownloadManager.getInstance().downloadFaild(downloadTask, CODE_DOWNLOAD_UNKNOW_ERROR);
sendMsg(DownloadManager.HANDLER_DOWNLOAD_FAILED, downloadTask);
}
}
/**
* 發(fā)送信息
*
* @param tag code
* @param downloadTask 下載任務
*/
private void sendMsg(int tag, DownloadTask downloadTask) {
if (mHandler == null) {
return;
}
Message message = Message.obtain();
message.what = tag;
message.obj = downloadTask;
mHandler.sendMessage(message);
}
@Override
public void onDestroy() {
super.onDestroy();
mHandler = null;
}
}
里面用到的IO
流處理的FileUtil.java
:
//采用OKIO的處理IO問題
public class FileUtil {
private FileUtil() {
}
public static File sink(File file, BufferedSource source, boolean isAppend) {
BufferedSink sink = null;
if (!isAppend && file.exists()) { //File存在胆胰,同時不是追加奈附,則刪除文件
deleteFile(file);
}
try {
if (isAppend) {
sink = Okio.buffer(Okio.appendingSink(file));
} else {
sink = Okio.buffer(Okio.sink(file));
}
Buffer buffer = sink.buffer();
int bufferSize = 1024 * 1024; //1M
while (source.read(buffer, bufferSize) != -1) {
sink.emit();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
source.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (sink != null)
sink.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return file;
}
/**
* 刪除文件或文件夾
*
* @param file 待刪除的文件
*/
public static void deleteFile(File file) {
try {
if (file == null || !file.exists()) {
return;
}
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files != null && files.length > 0) {
for (File f : files) {
if (f.exists()) {
if (f.isDirectory()) {
deleteFile(f);
} else {
f.delete();
}
}
}
}
} else {
file.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
(這只是第一版,有些還是需要修改的)好啦煮剧,下載服務也搞定,線程也算是稍微合理的解決了将鸵,那就要解決進度問題了勉盅。
監(jiān)聽下載進度
因為采用OkHttp3
,所以要解決這一問題顶掉,都知道用攔截器實現(xiàn)草娜,網(wǎng)上挺多的,亮代碼'ProgressResponseBody.java`:
/**
* Created by mid1529 on 2016/12/20.
* 帶進度的下載響應體
*/
public class ProgressResponseBody extends ResponseBody {
private static final String REG_TOTAL_CONTENT_LENGTH = "(?<=/)\\d*";
private static final String REG_ALREADY_DOWNLOAD_SIZE = "(?<=bytes )\\d*";
private static final String HEADER_RANGE = "bytes=%1$s-";
private Headers mHeaders = null;
private ResponseBody mResponseBody = null;
private BufferedSource mBufferedSource = null;
private static int RESPONSE_INTERVAL = 555; //回調(diào)進度相應間隔痒筒,單位s
public static final String TAG = "ProgressResponseBody";
private DownloadTask mDownloadTask = null;
private boolean mIsAlreadyComplete = false; //是否已經(jīng)發(fā)送下載完成事件宰闰,防止重復發(fā)送下載完成的問題。
private long mProgress = 0;
private long mLastSendTime = 0;
private Handler mHander = null;
public ProgressResponseBody(ResponseBody responseBody) {
this.mResponseBody = responseBody;
if (responseBody instanceof RealResponseBody) {
mHeaders = ResponseDecoratate.getRealResponsHeaders((RealResponseBody) responseBody);
}
}
@Override
public MediaType contentType() {
return mResponseBody.contentType();
}
@Override
public long contentLength() {
return mResponseBody.contentLength();
}
@Override
public BufferedSource source() {
if (mBufferedSource == null) {
mBufferedSource = Okio.buffer(source(mResponseBody.source()));
}
return mBufferedSource;
}
private Source source(Source source) {
return new ForwardingSource(source) {
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
mProgress += bytesRead == -1 ? 0 : bytesRead;
if (System.currentTimeMillis() - mLastSendTime > RESPONSE_INTERVAL) {
//每隔
mLastSendTime = System.currentTimeMillis();
if (mDownloadTask != null) {
mDownloadTask.setDownloadedLength(mProgress);
// DownloadManager.getInstance().postDownloadProgress(mDownloadTask);
sendMsg(mDownloadTask);
}
} else if (mProgress == mDownloadTask.getContentLength()) {
if (!mIsAlreadyComplete) { //防止重復發(fā)送下載完成事件
if (mDownloadTask.getDownloadedLength() != mDownloadTask.getContentLength()) {
mDownloadTask.setDownloadedLength(mDownloadTask.getContentLength());
// DownloadManager.getInstance().postDownloadProgress(mDownloadTask);
sendMsg(mDownloadTask);
}
mIsAlreadyComplete = true;
}
}
return bytesRead;
}
};
}
public void setDownloadTask(DownloadTask downloadTask, Handler handler) {
this.mDownloadTask = downloadTask;
mHander = handler;
if (this.mDownloadTask == null || mHeaders == null) {
return;
}
setDownloadTaskInfo();
}
/**
* 設置下載任務的進度簿透、下載的大小等信息
*/
private void setDownloadTaskInfo() {
if (mHeaders == null)
return;
String contentLength = RegexMatching.getString(REG_TOTAL_CONTENT_LENGTH, mHeaders.get("Content-Range"));
if (!TextUtils.isEmpty(contentLength)) {
mDownloadTask.setContentLength(Long.valueOf(contentLength));
} else {
mDownloadTask.setContentLength(contentLength());
}
String alreadyDownloadSize = RegexMatching.getString(REG_ALREADY_DOWNLOAD_SIZE, mHeaders.get("Content-Range"));
if (!TextUtils.isEmpty(alreadyDownloadSize)) {
mProgress = Long.valueOf(alreadyDownloadSize);
}
}
private void sendMsg(DownloadTask downloadTask) {
if (mHander == null) {
return;
}
Message message = Message.obtain();
message.what = DownloadManager.HANDLER_DOWNLOAD_PROGRESS;
message.obj = downloadTask;
mHander.sendMessage(message);
}
}
使用方法就不細說了移袍,看NetKit.java
中的代碼就知道了。
進度也解決了老充,那就剩最后一點要做的葡盗,就是需要編寫一個class
控制整個DownloadManager
,職責有幾點:
- 控制添加下載隊列
- 控制
DownloadService
的實例化 - 控制暴露接口給外部調(diào)用(實現(xiàn)進度回調(diào)等)
- 控制添加刪除暫停任務等
- 查詢歷史下載任務
- ……