国际惯例, 从一个使用demo入手
OkGo.get(Urls.URL_DOWNLOAD)//
.tag(this)//
.headers("header1", "headerValue1")//
.params("param1", "paramValue1")//
.execute(new FileCallback("OkGo.apk") {
@Override
public void onBefore(BaseRequest request) {
btnFileDownload.setText("正在下载中");
}
@Override
public void onSuccess(File file, Call call, Response response) {
handleResponse(file, call, response);
btnFileDownload.setText("下载完成");
}
@Override
public void downloadProgress(long currentSize, long totalSize, float progress, long networkSpeed) {
}
@Override
public void onError(Call call, @Nullable Response response, @Nullable Exception e) {
super.onError(call, response, e);
handleError(call, response);
btnFileDownload.setText("下载出错");
}
});
1. 创建BaseRequest对象
baseRequest 对象是OkGo封装的一个Request基类,这里的GetRequest 是他的子类
public static GetRequest get(String url) {
return new GetRequest(url);
}
对应的还有其他一系列的Request对象
generateRequest是baseRequest的抽象方法,用来构造真正的okhttp的Request对象
public class GetRequest extends BaseRequest {
public GetRequest(String url) {
super(url);
}
@Override
public RequestBody generateRequestBody() {
return null;
}
@Override
public Request generateRequest(RequestBody requestBody) {
Request.Builder requestBuilder = HttpUtils.appendHeaders(headers);
url = HttpUtils.createUrlFromParams(baseUrl, params.urlParamsMap);
return requestBuilder.get().url(url).tag(tag).build();
}
}
public BaseRequest(String url) {
this.url = url;
baseUrl = url;
httpUrl = HttpUrl.parse(url);
OkGo go = OkGo.getInstance();
//默认添加 Accept-Language
String acceptLanguage = HttpHeaders.getAcceptLanguage();
if (!TextUtils.isEmpty(acceptLanguage)) headers(HttpHeaders.HEAD_KEY_ACCEPT_LANGUAGE, acceptLanguage);
//默认添加 User-Agent
String userAgent = HttpHeaders.getUserAgent();
if (!TextUtils.isEmpty(userAgent)) headers(HttpHeaders.HEAD_KEY_USER_AGENT, userAgent);
//添加公共请求参数
if (go.getCommonParams() != null) params.put(go.getCommonParams());
if (go.getCommonHeaders() != null) headers.put(go.getCommonHeaders());
//添加缓存模式
if (go.getCacheMode() != null) cacheMode = go.getCacheMode();
cacheTime = go.getCacheTime();
//超时重试次数
retryCount = go.getRetryCount();
}
2. execute执行请求
其实是调用的CacheCall.execute()
/** 非阻塞方法,异步请求,但是回调在子线程中执行 */
@SuppressWarnings("unchecked")
public void execute(AbsCallback callback) {
mCallback = callback;
mConverter = callback;
new CacheCall(this).execute(callback);
}
我们再来看看CacheCall
//防止重复execute
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
}
mCallback = callback;
if (mCallback == null) mCallback = new AbsCallbackWrapper<>();
//构建请求, 其实就是将baseRequest转换为okHttp的request, 调用的generateRequest, 这下明白了吧?
RequestBody requestBody = baseRequest.generateRequestBody();
final Request request = baseRequest.generateRequest(baseRequest.wrapRequestBody(requestBody));
//构建真正的okHttp的 callback
rawCall = baseRequest.generateCall(request);
接下来看下generateCall根据传入的request, 最终调用okhttp 生成Call调用对象
/** 根据当前的请求参数,生成对应的 Call 任务 */
public okhttp3.Call generateCall(Request request) {
mRequest = request;
if (readTimeOut <= 0 && writeTimeOut <= 0 && connectTimeout <= 0 && sslParams == null && userCookies.size() == 0) {
return OkGo.getInstance().getOkHttpClient().newCall(request);
} else {
OkHttpClient.Builder newClientBuilder = OkGo.getInstance().getOkHttpClient().newBuilder();
if (readTimeOut > 0) newClientBuilder.readTimeout(readTimeOut, TimeUnit.MILLISECONDS);
if (writeTimeOut > 0) newClientBuilder.writeTimeout(writeTimeOut, TimeUnit.MILLISECONDS);
if (connectTimeout > 0) newClientBuilder.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS);
if (hostnameVerifier != null) newClientBuilder.hostnameVerifier(hostnameVerifier);
if (sslParams != null) newClientBuilder.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager);
if (userCookies.size() > 0) OkGo.getInstance().getCookieJar().addCookies(userCookies);
if (interceptors.size() > 0) {
for (Interceptor interceptor : interceptors) {
newClientBuilder.addInterceptor(interceptor);
}
}
return newClientBuilder.build().newCall(request);
}
}
最后设置请求回调
rawCall.enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
}
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
int responseCode = response.code();
try {
Response parseResponse = parseResponse(response);
T data = parseResponse.body();
//网络请求成功,保存缓存数据
handleCache(response.headers(), data);
//网络请求成功回调
sendSuccessResultCallback(false, data, call, response);
} catch (Exception e) {
//一般为服务器响应成功,但是数据解析错误
sendFailResultCallback(false, call, response, e);
}
}
});
请求成功则调用sendSuccessResultCallback
很简单, 就是最开始调用传入的new FileCallback()
/** 成功回调,发送到主线程 */
private void sendSuccessResultCallback(final boolean isFromCache, final T t, final okhttp3.Call call, final okhttp3.Response response) {
final CacheMode cacheMode = baseRequest.getCacheMode();
OkGo.getInstance().getDelivery().post(new Runnable() {
@Override
public void run() {
if (isFromCache) {
mCallback.onCacheSuccess(t, call); //缓存成功回调 (UI线程)
if (cacheMode == CacheMode.DEFAULT || cacheMode == CacheMode.REQUEST_FAILED_READ_CACHE || cacheMode == CacheMode.IF_NONE_CACHE_REQUEST) {
mCallback.onAfter(t, null); //请求结束回调 (UI线程)
}
} else {
mCallback.onSuccess(t, call, response); //请求成功回调 (UI线程)
mCallback.onAfter(t, null); //请求结束回调 (UI线程)
}
}
});
}
再来看下FileCallback
他会创建FileConvert, 用来将返回的Response 转换为File
public FileCallback(String destFileDir, String destFileName) {
convert = new FileConvert(destFileDir, destFileName);
convert.setCallback(this);
}
@Override
public File convertSuccess(Response response) throws Exception {
File file = convert.convertSuccess(response);
response.close();
return file;
}
其实, 这个文件下载请求, 最核心的封装就是在convertSuccess里面,也就是把这一大坨帮你封装好了。
@Override
public File convertSuccess(Response value) throws Exception {
if (TextUtils.isEmpty(destFileDir)) destFileDir = Environment.getExternalStorageDirectory() + DM_TARGET_FOLDER;
if (TextUtils.isEmpty(destFileName)) destFileName = HttpUtils.getNetFileName(value, value.request().url().toString());
File dir = new File(destFileDir);
if (!dir.exists()) dir.mkdirs();
File file = new File(dir, destFileName);
if (file.exists()) file.delete();
long lastRefreshUiTime = 0; //最后一次刷新的时间
long lastWriteBytes = 0; //最后一次写入字节数据
InputStream is = null;
byte[] buf = new byte[2048];
FileOutputStream fos = null;
try {
is = value.body().byteStream();
final long total = value.body().contentLength();
long sum = 0;
int len;
fos = new FileOutputStream(file);
while ((len = is.read(buf)) != -1) {
sum += len;
fos.write(buf, 0, len);
//下载进度回调
if (callback != null) {
final long finalSum = sum;
long curTime = System.currentTimeMillis();
//每200毫秒刷新一次数据
if (curTime - lastRefreshUiTime >= 200 || finalSum == total) {
//计算下载速度
long diffTime = (curTime - lastRefreshUiTime) / 1000;
if (diffTime == 0) diffTime += 1;
long diffBytes = finalSum - lastWriteBytes;
final long networkSpeed = diffBytes / diffTime;
OkGo.getInstance().getDelivery().post(new Runnable() {
@Override
public void run() {
callback.downloadProgress(finalSum, total, finalSum * 1.0f / total, networkSpeed); //进度回调的方法
}
});
lastRefreshUiTime = System.currentTimeMillis();
lastWriteBytes = finalSum;
}
}
}
fos.flush();
return file;
} finally {
try {
if (is != null) is.close();
} catch (IOException e) {
OkLogger.e(e);
}
try {
if (fos != null) fos.close();
} catch (IOException e) {
OkLogger.e(e);
}
}
}
okserver的分析
okserver主要是比okgo多了断点下载和下载状态的管理
获取DownloadService单例
downloadManager = DownloadService.getDownloadManager();
/** start 方式开启服务,保存全局的下载管理对象 */
public static DownloadManager getDownloadManager() {
Context context = OkGo.getContext();
if (!DownloadService.isServiceRunning(context)) context.startService(new Intent(context, DownloadService.class));
if (DownloadService.DOWNLOAD_MANAGER == null) DownloadService.DOWNLOAD_MANAGER = DownloadManager.getInstance();
return DOWNLOAD_MANAGER;
}
设置同时下载线程池数量
downloadManager.getThreadPool().setCorePoolSize(progress);
创建request
GetRequest request = OkGo.get(apkModel.getUrl())//
.headers("headerKey1", "headerValue1")//
.headers("headerKey2", "headerValue2")//
.params("paramKey1", "paramValue1")//
.params("paramKey2", "paramValue2");
downloadManager.addTask(apkModel.getUrl(), request, null);
addTask其实就是添加一个ayncTask的子类到downloadManager的队列中
/**
* 添加一个下载任务
*
* @param request 下载的网络请求
* @param listener 下载监听
* @param isRestart 是否重新开始下载
*/
private void addTask(String fileName, String taskTag, BaseRequest request, DownloadListener listener, boolean isRestart) {
DownloadInfo downloadInfo = getDownloadInfo(taskTag);
if (downloadInfo == null) {
downloadInfo = new DownloadInfo();
downloadInfo.setUrl(request.getBaseUrl());
downloadInfo.setTaskKey(taskTag);
downloadInfo.setFileName(fileName);
downloadInfo.setRequest(request);
downloadInfo.setState(DownloadManager.NONE);
downloadInfo.setTargetFolder(mTargetFolder);
DownloadDBManager.INSTANCE.replace(downloadInfo);
mDownloadInfoList.add(downloadInfo);
}
//无状态,暂停,错误才允许开始下载
if (downloadInfo.getState() == DownloadManager.NONE || downloadInfo.getState() == DownloadManager.PAUSE || downloadInfo.getState() == DownloadManager.ERROR) {
//构造即开始执行
DownloadTask downloadTask = new DownloadTask(downloadInfo, isRestart, listener);
downloadInfo.setTask(downloadTask);
}
}
下载方法是核心
断点下载主要是读取mDownloadInfo.getDownloadLength(),然后在http的header加入RANGE,以此来实现断点下载,任务的暂停,停止,等状态都在doInBackground里面处理,其实就是跟AsyncTask的状态处理一样。
/** 一旦该方法执行,意味着开始下载了 */
@Override
protected DownloadInfo doInBackground(Void... params) {
if (isCancelled()) return mDownloadInfo;
OkLogger.e("doInBackground:" + mDownloadInfo.getFileName());
mPreviousTime = System.currentTimeMillis();
mDownloadInfo.setNetworkSpeed(0);
mDownloadInfo.setState(DownloadManager.DOWNLOADING);
postMessage(null, null);
long startPos = mDownloadInfo.getDownloadLength();
Response response;
try {
//设置request的 header range
response = mDownloadInfo.getRequest().headers("RANGE", "bytes=" + startPos + "-").execute();
} catch (IOException e) {
e.printStackTrace();
mDownloadInfo.setNetworkSpeed(0);
mDownloadInfo.setState(DownloadManager.ERROR);
postMessage("网络异常", e);
return mDownloadInfo;
}
//构建下载文件路径,如果有设置,就用设置的,否者就自己创建
String url = mDownloadInfo.getUrl();
String fileName = mDownloadInfo.getFileName();
if (TextUtils.isEmpty(fileName)) {
fileName = HttpUtils.getNetFileName(response, url);
mDownloadInfo.setFileName(fileName);
}
if (TextUtils.isEmpty(mDownloadInfo.getTargetPath())) {
File file = new File(mDownloadInfo.getTargetFolder(), fileName);
mDownloadInfo.setTargetPath(file.getAbsolutePath());
}
//检查文件有效性,文件大小大于总文件大小
if (startPos > mDownloadInfo.getTotalLength()) {
mDownloadInfo.setNetworkSpeed(0);
mDownloadInfo.setState(DownloadManager.ERROR);
postMessage("断点文件异常,需要删除后重新下载", null);
return mDownloadInfo;
}
if (startPos == mDownloadInfo.getTotalLength() && startPos > 0) {
mDownloadInfo.setProgress(1.0f);
mDownloadInfo.setNetworkSpeed(0);
mDownloadInfo.setState(DownloadManager.FINISH);
postMessage(null, null);
return mDownloadInfo;
}
//设置断点写文件
File file = new File(mDownloadInfo.getTargetPath());
ProgressRandomAccessFile randomAccessFile;
try {
randomAccessFile = new ProgressRandomAccessFile(file, "rw", startPos);
randomAccessFile.seek(startPos);
} catch (Exception e) {
e.printStackTrace();
mDownloadInfo.setNetworkSpeed(0);
mDownloadInfo.setState(DownloadManager.ERROR);
postMessage("没有找到已存在的断点文件", e);
return mDownloadInfo;
}
//获取流对象,准备进行读写文件
long totalLength = response.body().contentLength();
if (mDownloadInfo.getTotalLength() == 0) {
mDownloadInfo.setTotalLength(totalLength);
}
InputStream is = response.body().byteStream();
//读写文件流
try {
download(is, randomAccessFile);
} catch (IOException e) {
e.printStackTrace();
mDownloadInfo.setNetworkSpeed(0);
mDownloadInfo.setState(DownloadManager.ERROR);
postMessage("文件读写异常", e);
return mDownloadInfo;
}
//循环结束走到这里,a.下载完成 b.暂停 c.判断是否下载出错
if (isCancelled()) {
OkLogger.e("state: 暂停 " + mDownloadInfo.getState());
mDownloadInfo.setNetworkSpeed(0);
if (isPause) mDownloadInfo.setState(DownloadManager.PAUSE); //暂停
else mDownloadInfo.setState(DownloadManager.NONE); //停止
postMessage(null, null);
} else if (file.length() == mDownloadInfo.getTotalLength() && mDownloadInfo.getState() == DownloadManager.DOWNLOADING) {
mDownloadInfo.setNetworkSpeed(0);
mDownloadInfo.setState(DownloadManager.FINISH); //下载完成
postMessage(null, null);
} else if (file.length() != mDownloadInfo.getDownloadLength()) {
mDownloadInfo.setNetworkSpeed(0);
mDownloadInfo.setState(DownloadManager.ERROR); //由于不明原因,文件保存有误
postMessage("未知原因", null);
}
return mDownloadInfo;
}
在看ProgressRandomAccessFile写文件
保存已下载的大小到DownloadLength属性
//已下载大小
long downloadLength = lastDownloadLength + count;
curDownloadLength += count;
lastDownloadLength = downloadLength;
//这里会将以下载的大小保存到DownloadLength属性中
mDownloadInfo.setDownloadLength(downloadLength);
//计算下载速度
long totalTime = (System.currentTimeMillis() - mPreviousTime) / 1000;
if (totalTime == 0) {
totalTime += 1;
}
long networkSpeed = curDownloadLength / totalTime;
mDownloadInfo.setNetworkSpeed(networkSpeed);
//下载进度
float progress = downloadLength * 1.0f / mDownloadInfo.getTotalLength();
mDownloadInfo.setProgress(progress);
long curTime = System.currentTimeMillis();
//每200毫秒刷新一次数据
if (curTime - lastRefreshUiTime >= 200 || progress == 1.0f) {
postMessage(null, null);
lastRefreshUiTime = System.currentTimeMillis();
}
addTask 最后一个参数为isRestart, 是否重新下载, 如果是true,则会删除临时文件
/**
* 添加一个下载任务
*
* @param request 下载的网络请求
* @param listener 下载监听
* @param isRestart 是否重新开始下载
*/
private void addTask(String fileName, String taskTag, BaseRequest request, DownloadListener listener, boolean isRestart) {
//如果是重新下载,需要删除临时文件
if (isRestartTask) {
HttpUtils.deleteFile(mDownloadInfo.getTargetPath());
mDownloadInfo.setProgress(0);
mDownloadInfo.setDownloadLength(0);
mDownloadInfo.setTotalLength(0);
isRestartTask = false;
}