Android 基于OkHttp的下载,支持https,断点下载,优化下载速度

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();


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章