实现DownloadService(下载服务)
这个DownloadService.java
就是一个Android
服务,主要职责有两个:
- 请求网络(将
NetKit
中实例化得到的Call
执行)。 - 将请求到的数据流写入到内存中。
そして、还有两个隐性的待解决的问题:
- 下载线程控制问题。
- 将下载进度广播通知(好吧,不是Android的广播,字面意思,你懂的,词穷)的问题。
带着这 两个问题,开始思考,怎么解决呢?经过思考后,我最终是这样处理的:
- 下载进度通知:在
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与外部进行关联
*/
public class DownloadServiceBinder extends Binder {
/**
* 通过这方法暴露给外部调用下载方法
*
* @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 { //不进行断点续传,删除文件
FileUtil.deleteFile(downloadTask.getFile());
}
new Thread(new Runnable() {
@Override
public void run() {
download(downloadTask);
}
}).start();
}
/**
* 开始下载
*采用非异步执行`Call`方法,尽量减少使用匿名内部类
* @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; //判断是否支持断点续传
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);
}
}
/**
* 发送信息
*
* @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();
}
}
}
(这只是第一版,有些还是需要修改的)好啦,下载服务也搞定,线程也算是稍微合理的解决了,那就要解决进度问题了。
监听下载进度
因为采用OkHttp3
,所以要解决这一问题,都知道用拦截器实现,网上挺多的,亮代码'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; //回调进度相应间隔,单位s
public static final String TAG = "ProgressResponseBody";
private DownloadTask mDownloadTask = null;
private boolean mIsAlreadyComplete = false; //是否已经发送下载完成事件,防止重复发送下载完成的问题。
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) { //防止重复发送下载完成事件
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
的实例化 - 控制暴露接口给外部调用(实现进度回调等)
- 控制添加删除暂停任务等
- 查询历史下载任务
- ……