Android 基于OkHttp的下载,支持https,断点下载,优化下载速度
2017-03-17 15:21
330 查看
package com.lenovo.smarthcc.http.okhttp; import android.text.TextUtils; import com.lenovo.smarthcc.http.Listenner; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.util.Date; import java.util.HashMap; import java.util.Map; import okhttp3.Call; import okhttp3.Headers; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okio.Buffer; import okio.Okio; import okio.Sink; import okio.Source; /** * <p>Author: shijiale</p> * <p>Date: 2017/3/14.<p> * <p>Describe: */ public class BRDownloadThread extends Thread { private static OkHttpClient mClient; //okhttp client private Builder mBuilder; //配置 private boolean isExit = false; //是否退出下载 private long mStartTime = 0; //当前这次下载起始时间 private long mDownloadLength; //当前这次下载的长度 private final int MAX_BUFF_SIZE = 4 * 1024; //每次发送的字节(这个数值可能没有什么作用,因为大部分http流都会做缓存) private Call mCall; //当前请求 private DaemonThead mDaemonMsgThread; //用于进度回调的线程 /*** * 退出当前下载线程 */ public synchronized void exit() { isExit = true; } private BRDownloadThread() { mClient = OkHttpProxy.getClient(); if (mDaemonMsgThread == null) { mDaemonMsgThread = new DaemonThead(); mDaemonMsgThread.isRunning = true; } } @Override public void run() { super.run(); try { downloadFile(); } catch (Exception e) { mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR, mBuilder.mSavePath, e.getMessage()); } finally { if (mDaemonMsgThread != null) { mDaemonMsgThread.isRunning = false; mDaemonMsgThread = null; } } } /*** * 自动修改下载的文件名称 * @param fileLength 文件在服务器的大小 */ private void autoChangeFileName(long fileLength) { File file = new File(mBuilder.mSavePath); if(file != null && file.exists() && file.length() == fileLength) { String path = mBuilder.mSavePath; path = path.substring(0,path.lastIndexOf(".") + 1); String expresion = mBuilder.mSavePath.substring(path.lastIndexOf(".") + 1,mBuilder.mSavePath.length()); path += "(" + new Date(System.currentTimeMillis()).toLocaleString() + ")" + expresion; mBuilder.mSavePath = path; } } private boolean chekDirectory() { String path = mBuilder.mSavePath; path = path.substring(0,path.lastIndexOf(File.separator) + 1); File file = new File(path); if(file != null && file.exists()) { return true; } if(file == null || !file.exists()) { boolean mkdirs = file.mkdirs(); if(!mkdirs) { return false; } } return true; } /*** * 下载文件 * @throws IOException */ private void downloadFile() throws IOException { if(!chekDirectory()) { mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR,mBuilder.mSavePath,"directory create failed!"); return; } //获取文件长度 mBuilder.mFileAllLength = getNetFileSize(); //如果允许重复下载,则对文件进行重命名,修改后的文件名会会在 onStop onSuccess中体现 if(mBuilder.isAutoRename) { autoChangeFileName(mBuilder.mFileAllLength); } RandomAccessFile file = new RandomAccessFile(mBuilder.mSavePath, "rw"); //如果本地已经存在该文件,并且大小和服务器相同则返回下载成功 if (file != null && file.length() == mBuilder.mFileAllLength && mBuilder.mFileAllLength != 0) { mBuilder.mSuccessListenner.onChanged(Listenner.STATUS_SUCCESS, mBuilder.mSavePath,mBuilder.mFileAllLength + ""); return; } //设置文件起始指针 if (file != null && file.length() != 0) { if (mBuilder.mStartOffset > 0 && mBuilder.mStartOffset < file.length()) { file.seek(mBuilder.mStartOffset); } else { file.seek(file.length() - 1); mBuilder.mStartOffset = file.length() - 1; } } //设置请求参数 callRequest(mBuilder.mStartOffset); if (mCall == null) { mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR, mBuilder.mSavePath, "CONNECTION ERROR!"); return; } //建立连接 Response res = mCall.execute(); if (!res.isSuccessful()) { mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR, mBuilder.mSavePath, "CONNECTION ERROR!"); return; } //更新文件长度 mBuilder.mLengthChangeListenner.onChanged(Listenner.STATUS_LENGTH_CHANGE, mBuilder.mFileAllLength + "", (mBuilder.mStartOffset + mDownloadLength) + ""); //开启进度刷新线程 mDaemonMsgThread.start(); //保存文件 boolean ret = saveData(res.body().byteStream(), new FileOutputStream(file.getFD())); if (ret) { mBuilder.mSuccessListenner.onChanged(Listenner.STATUS_SUCCESS, mBuilder.mSavePath,mBuilder.mFileAllLength + ""); } else { mBuilder.mStopListenner.onChanged(Listenner.STATUS_STOP, mBuilder.mSavePath, mBuilder.mFileAllLength + "", (mBuilder.mStartOffset + mDownloadLength) + ""); } mBuilder.mLengthChangeListenner.onChanged(Listenner.STATUS_LENGTH_CHANGE, mBuilder.mFileAllLength + "", (mBuilder.mStartOffset + mDownloadLength) + ""); } /*** * 获取文件在服务器的长度,head请求 * @return * @throws IOException */ private long getNetFileSize() throws IOException { Request request = getRequestBuilder(0) .head() .build(); Call call = mClient.newCall(request); if (call == null) { mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR, mBuilder.mSavePath, "CONNECTION ERROR,Get File Size!"); return -1; } Response response = call.execute(); if (!response.isSuccessful()) { mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR, mBuilder.mSavePath, "CONNECTION ERROR,Get File Size!"); return -1; } Headers headers = response.headers(); long fileSize = 0; try { fileSize = Long.parseLong(headers.get("Content-Length")); } catch (Exception e) { fileSize = -1; } return fileSize; } /*** * 执行请求 * @param offset 起始下载偏移量,该偏移量以当前已经下载的文件长度为准 */ private void callRequest(long offset) { Request request = getRequestBuilder(offset) .build(); mCall = mClient.newCall(request); } /*** * 初始化请求参数 * @param offset 起始下载偏移量 * @return */ private Request.Builder getRequestBuilder(long offset) { Headers.Builder builder = new Headers.Builder(); //header if (mBuilder.mHeaders != null) { for (String k : mBuilder.mHeaders.keySet()) { builder.set(k, mBuilder.mHeaders.get(k)); } } if (builder.get("Range") == null) { builder.set("Range", "bytes=" + offset + "-"); } Headers headers = builder.build(); //urlparams url后面的参数 if (mBuilder.mUrlParams != null && !mBuilder.mUrlParams.isEmpty()) { if (!mBuilder.mUrl.contains("?")) { mBuilder.mUrl += "?"; } for (String k : mBuilder.mUrlParams.keySet()) { mBuilder.mUrl += k + "=" + mBuilder.mUrlParams.get(k); mBuilder.mUrl += "&"; } } Request.Builder reBuilder = new Request.Builder() .url(mBuilder.mUrl) .headers(headers); return reBuilder; } /*** * 保存文件 * @param is 源输入流 * @param os 目标输出流 * @return 如果是下载完成返回true,如果停止导致返回false * @throws IOException */ private boolean saveData(InputStream is, OutputStream os) throws IOException { Source source = Okio.source(is); Sink sink = Okio.sink(os); Buffer buf = new Buffer(); long len = 0; while ((len = source.read(buf, MAX_BUFF_SIZE)) != -1 && !isExit) { sink.write(buf, len); mDownloadLength += len; } sink.flush(); sink.close(); source.close(); return !isExit; } /*** * 刷新当前下载进度 */ private synchronized void changeSpeed() { long endTime = System.currentTimeMillis(); mBuilder.mSpeedChangeListenner.onChanged(Listenner.STATUS_SPEED_CHANGE, mBuilder.mFileAllLength + "", (mBuilder.mStartOffset + mDownloadLength) + "",(endTime - mStartTime) + "", mDownloadLength + ""); } /** * 用于在一个新的线程中刷新下载进度,防止回调中有耗时操作阻塞下载线程影响下载速度 */ private final class DaemonThead extends Thread { //是否开启 private boolean isRunning = true; @Override public void run() { while (isRunning) { //防止下载完成以及其他状态回调时,进度还在刷新 synchronized (mBuilder) { changeSpeed(); try { Thread.sleep(mBuilder.mSpeedRefreshHZ); } catch (InterruptedException e) { e.printStackTrace(); } } } } } /*** * Listtenner 的简单封装,可以简单的得到状态回调中的数值 */ public static abstract class SimpleDownLoadListenner implements Listenner { @Override public void onChanged(int code, String... value) { switch (code) { case Listenner.STATUS_ERROR: { onDownloadError(value[0], value[1]); } break; case Listenner.STATUS_SUCCESS: { onDownloadSuccessful(value[0],getLongValue(value[1])); } break; case Listenner.STATUS_SPEED_CHANGE: { onDownloadSpeedChanged(getLongValue(value[0]), getLongValue(value[1]), getLongValue(value[2]),getLongValue(value[3])); } break; case Listenner.STATUS_STOP: { onDownloadStoped(value[0], getLongValue(value[1]), getLongValue(value[2])); } break; case Listenner.STATUS_LENGTH_CHANGE: { onDownloadLengthChanged(getLongValue(value[0]), getLongValue(value[1])); } break; } } private long getLongValue(String value) { try { return Long.parseLong(value); } catch (Exception e) { return 0; } } public void onDownloadSuccessful(String path,long allFileLength) { } public void onDownloadStoped(String path, long allLength, long allDownloadlength) { } public void onDownloadError(String path, String msg) { } public void onDownloadLengthChanged(long allLength, long allDownloadLength) { } public synchronized void onDownloadSpeedChanged(long allLength,long allDownloadLenght , long times, long nowDownloadLength) { } } /*** * BRDownloadThread以Builder模式创建,不允许new */ public static class Builder implements Listenner { private Map<String, String> mUrlParams; //url后的参数 private Map<String, String> mHeaders; //请求头 private long mFileAllLength; //文件总长度 private long mStartOffset = 0; //起始下载偏移量 private String mUrl; //请求地址 private String mSavePath; //保存路径(全路径) private String mBasePath; //保存文件夹名称 private String mSaveName; //文件名称 private Listenner mSuccessListenner; //成功回调 private Listenner mErrorListenner; //失败回调 private Listenner mLengthChangeListenner; //文件长度变化回调(只在获取到服务器文件大小时回调,以及线程终止后回调(包括完成和停止)) private Listenner mSpeedChangeListenner; //进度刷新 刷新频率为(1/mSpeedRefreshHZ) private Listenner mStopListenner; //停止回调,只在下载手动终止时回调 private long mSpeedRefreshHZ = 500; //进度刷新间隔 毫秒 private boolean isAutoRename = false; //是否支持文件重命名,如果本地已存在 false 不再重复下载 true 重命名后继续下载 public Builder setAutoRename(boolean auto) { isAutoRename = auto; return this; } /*** * 设置进度刷新间隔 * @param times 间隔时间 ms * @return */ public Builder setDaemonRefreshDelayTime(long times) { mSpeedRefreshHZ = times; return this; } /*** * 设置保存的的全路径 * @param path * @return */ public Builder setSavePath(String path) { mSavePath = path; return this; } /** * 设置保存文件夹名称 * @param basePath 文件夹名称 * @return */ public Builder setBasePath(String basePath) { mBasePath = basePath; return this; } /*** * 保存文件名 * @param name 文件名 * @return */ public Builder setSaveName(String name) { mSaveName = name; return this; } /*** * 请求地址 * @param url */ public Builder(String url) { mUrl = url; } public Builder() { } /*** * 设置状态监听回调 * @param l * @return */ public Builder setListenners(Listenner l) { mSuccessListenner = l; mErrorListenner = l; mLengthChangeListenner = l; mSpeedChangeListenner = l; mStopListenner = l; return this; } /*** * 成功监听 * @param l * @return */ public Builder setSuccessListenner(Listenner l) { mSuccessListenner = l; return this; } /*** * 失败监听 * @param l * @return */ public Builder setErrorListenner(Listenner l) { mErrorListenner = l; return this; } public Builder setStopListenner(Listenner l) { mStopListenner = l; return this; } /*** * 长度变化监听 * @param l * @return */ public Builder setLengthChangeListenner(Listenner l) { mLengthChangeListenner = l; return this; } /*** * 进度刷新监听 * @param l * @return */ public Builder setOnSpeedListenner(Listenner l) { mSpeedChangeListenner = l; return this; } /*** * 设置起始下载偏移量和文件长度 * @param offset 偏移量 首次下载为 0,该值只作为参考实际已文件长度为准 * @param fileAllLength 文件长度,可有可无 * @return */ public Builder setOffset(long offset, long fileAllLength) { mStartOffset = offset; mFileAllLength = fileAllLength; return this; } public Builder setUrl(String url) { mUrl = url; return this; } /*** * 请求头(如果header头中包含Range 则会替换默认的Range属性,建议不要包含) * @param headers * @return */ public Builder setHeaders(Map<String, String> headers) { mHeaders = headers; return this; } /*** * 请求头 * @param headers * @return */ public synchronized Builder addHeaders(Map<String, String> headers) { if (mHeaders == null) { mHeaders = headers; } else { mHeaders.putAll(headers); } return this; } /*** * 请求头 * @param k * @param v * @return */ public synchronized Builder addHeader(String k, String v) { if (mHeaders == null) { mHeaders = new HashMap<>(); } return this; } /*** * 如果url后面包含参数,可以使用该方法为url添加参数 * @param params * @return */ public synchronized Builder setUrlParams(Map<String, String> params) { mUrlParams = params; return this; } /*** * url参数 * @param params * @return */ public synchronized Builder addUrlParams(Map<String, String> params) { if (mUrlParams == null) { mUrlParams = params; } else { mUrlParams.putAll(mUrlParams); } return this; } /** * url 参数 * @param k * @param v * @return */ public synchronized Builder addUrlParams(String k, String v) { if (mUrlParams == null) { mUrlParams = new HashMap<>(); } mUrlParams.put(k, v); return this; } public BRDownloadThread build() throws RuntimeException { if (mErrorListenner == null) { mErrorListenner = this; } if (mSpeedChangeListenner == null) { mSpeedChangeListenner = this; } if (mSuccessListenner == null) { mSuccessListenner = this; } if (mStopListenner == null) { mStopListenner = this; } if (mLengthChangeListenner == null) { mLengthChangeListenner = this; } //文件保存路径错误 if (TextUtils.isEmpty(mSavePath) && (TextUtils.isEmpty(mBasePath) || TextUtils.isEmpty(mSaveName))) { throw new RuntimeException("can not build BRDownloadthread,because savepath or (saveBasePasth and saveName is null)!"); } //起始下载偏移量大于文件长度 if (mStartOffset > mFileAllLength) { throw new RuntimeException("can not continue to download form startOffset,Because file length is null!"); } if (TextUtils.isEmpty(mSavePath) && !TextUtils.isEmpty(mBasePath) && !TextUtils.isEmpty(mSaveName)) { mSavePath = mBasePath + File.separator + mSaveName; } BRDownloadThread thread = new BRDownloadThread(); thread.mBuilder = this; return thread; } @Override public void onChanged(int code, String... value) { } } }
示例:
mDownloadThread = new BRDownloadThread.Builder(downloadPath) .setSavePath(savePasth) .setOffset(0,0) .setListenners(new BRDownloadThread.SimpleDownLoadListenner() { @Override public void onChanged(int code, String... value) { switch (code) { case Listenner.STATUS_ERROR: { LogUtil.i("ERROR == > " + getString(value)); }break; case Listenner.STATUS_LENGTH_CHANGE: { LogUtil.i("LENGTH == >" + getString(value)); }break; case Listenner.STATUS_SPEED_CHANGE: { LogUtil.i("SPEED == >" + getString(value)); }break; case Listenner.STATUS_STOP: { LogUtil.i("STOP == >" + getString(value)); }break; case Listenner.STATUS_SUCCESS: { LogUtil.i("SUCCESSFUL == >" + getString(value)); }break; } } }) .build(); mDownloadThread.start();
相关文章推荐
- rxandroid+okhttp下载并支持https
- Android带通知栏操作多页面同步暂停支持多任务多线程断点下载demo
- Picasso 详解,完美兼容 OkHttp3.3,缓存优化,支持https
- android 单线程多任务断点排队下载(支持多界面刷新)
- 基于android的网络音乐播放器-下载任务的暂停/继续/删除以及断点下载(七)
- 基于iOS 10封装的下载器(支持存储读取、断点续传、后台下载、杀死APP重启后的断点续传等功能)
- 使用Android的OkHttp包实现基于HTTP协议的文件上传下载
- 基于OKHttp实现对Https的支持
- android下载封装类Download,支持断点下载
- android实现多任务多线程支持断点下载的下载软件
- android下载, 断点续传, 在关闭activity或杀进程后,可继续下载,保证android下载速度
- android下载封装类Download,支持断点下载
- 关于ionic2打包android时gradle下载不了的解决方法(附:简单优化启动速度彩蛋)
- Https系列之四:https的SSL证书在Android端基于okhttp,Retrofit的使用
- 优雅设计封装基于Okhttp3的网络框架(四):多线程下载添加数据库支持(greenDao)及 进度更新
- Android OkHttp实现HTTPS访问,支持Android 4.X系统HTTPS访问
- 基于iOS 10、realm封装的下载器(支持存储读取、断点续传、后台下载、杀死APP重启后的断点续传等功能)
- 网络请求工具类之OkHttp3封装(支持缓存、日志、拦截器、断点下载、上传等)
- android 下载文件(支持多任务,支持断点.....)
- Android 多线程断点下载 Okhttp+AsyncTask 封装下载任务