前言

在经过一段时间使用OKHttp之后,偶尔需要应用别人的jar,但是别人的jar中已经包含了OKHttp之后,又是各种麻烦修改,考虑种种之后想办法自己在HttpUrlConnection方面写一个断点下载,同时如果自己不需要断点下载,简单的设置以下就可以了。

知识点

(1)断点传递给服务器端,请求需要的从断点开始的数据

// 统一资源
URL httpUrl = new URL(url);
// 连接类的父类,抽象类
URLConnection urlConnection = httpUrl.openConnection();
// http的连接类
HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
if (url.toUpperCase().startsWith("HTTPS")) {
      httpURLConnection = (HttpURLConnection) urlConnection;
      HttpsURLConnection httpsURLConnection = (HttpsURLConnection) httpURLConnection;
      httpsURLConnection.setHostnameVerifier(new HttpsHostnameVerifier());
      httpsURLConnection.setSSLSocketFactory(HttpsSSLSocketFactory.factory());
      httpURLConnection = httpsURLConnection;
}
httpURLConnection.setDoInput(true);
// 设定请求的方法,默认是GET
httpURLConnection.setRequestMethod("GET");
// 设置字符编码
httpURLConnection.setRequestProperty("Charset", "UTF-8");
long downloadedLength = calculateDownloadedLength(url);
 //设置下载位置
httpURLConnection.setRequestProperty("RANGE", "bytes=" + downloadedLength + "-");
// 打开到此 URL 引用的资源的通信链接(如果尚未建立这样的连接)。
httpURLConnection.connect();

(2)在写入数据的时候,调节对应的断点进行结合:

RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
randomAccessFile.seek(downloadedLength);

方案一:

RandomAccessFile randomAccessFile = new RandomAccessFile(file, “rw”);
randomAccessFile.seek(downloadedLength);

方案二:

FileChannel channelOut = randomAccessFile.getChannel()
MappedByteBuffer mappedBuffer = channelOut.map(FileChannel.MapMode.READ_WRITE,response.body().contentLength());

此处注意的是,在写入数据的时候,有人喜欢用数据通道加缓存来写入数据,这样写的结果其实都是可以的,唯一的区别是用缓存加数据通道(方案二),第一次写入就相当于占用了内存空间,但是实际没有那么大的内存。

(3)在HttpUrlConnection设置对应的下载起点之后,是可以正常下载,但是如果遇到已经下载完的文件,再次点击文件下载,在getInputSteam()方法会IOException.这时候需要判断response返回的code,如果是416那么就返回已经存在的文件就行了。

int code = httpURLConnection.getResponseCode();
if (code == 416) {
        sendCompletedMsg(createFile(url));
    return;
}

使用

HucDownloader.Builder builder = new HucDownloader.Builder()
.url(file_url)
.name("Git.zip")
.folder("Downloader")
.isBreakpoint(true)
.listener(new OnDownloadListener() {
      @Override
      public void onDownloading(long total, long progress, int percent) {
                Log.e("RRL", "onDownloading " + percent);
       }

      @Override
      public void onDownloadCompleted(File file) {
                Log.e("RRL", "onDownloadCompleted " + file.getAbsolutePath());
      }

      @Override
      public void onDownloadFailed(Exception e) {
                Log.e("RRL", "onDownloadFailed " + e.toString());
      }

     });
downloader = new HucDownloader(builder);
downloader.start();

源码

(1)HttpsHostnameVerifier

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;

/**
 * Created by Relin
 * on 2018-11-01.
 */
public class HttpsHostnameVerifier implements HostnameVerifier {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
}

(2)HttpsX509TrustManager

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.X509TrustManager;

/**
 * Created by Relin
 * on 2018-11-01.
 */
public class HttpsX509TrustManager implements X509TrustManager{
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
}

(3)HttpsSSLSocketFactory

import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

/**
 * Created by Relin
 * on 2018-11-01.
 */
public class HttpsSSLSocketFactory {

    public static SSLSocketFactory factory() {
        SSLContext sslContext = null;
        try {
            sslContext = SSLContext.getInstance("SSL");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        TrustManager[] tm = {new HttpsX509TrustManager()};
        try {
            sslContext.init(null, tm, new java.security.SecureRandom());
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        return sslContext.getSocketFactory();
    }

}

(4)HucDownload.java

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;

import com.android.image.http.HttpsHostnameVerifier;
import com.android.image.http.HttpsSSLSocketFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;

import javax.net.ssl.HttpsURLConnection;

/**
 * Created by Relin
 * on 2018/9/19.<br/>
 * 下载助手可以帮助你简单的调用函数进行下载,同时如果你需要自定义下载网络的方式,<br/>
 * 只需要继承该类然后重写download方法,在获取到文件流之后调用doHttpResponse()<br/>
 * 处理对应逻辑就行了。<br/>
* download assistant can help you simply calling a function for download, at the same time,< br / >
*  if you need a custom download network way, < br / >
* only needs to inherit the class and then rewrite the download method,< br / >
*  after the access to file stream call doHttpResponse () < br / >
* handle corresponding logic. < br / >
 */
public class HucDownloader {
    /**
     * 缓存文件夹
     */
    public static final String CACHE_FOLDER = "Downloader";

    public static final int WHAT_DOWNLOADING = 0x001;
    public static final int WHAT_DOWNLOAD_COMPLETED = 0x002;
    public static final int WHAT_DOWNLOAD_FAILED = 0x003;

    /**
     * 总的大小
     */
    private long totalSize = 0;

    /**
     * 是否取消
     */
    private boolean isCancel;

    /**
     * 是否暂停
     */
    private boolean isPause;

    /**
     * 是否在下载中
     */
    private boolean isDownloading;

    /**
     * 资源地址
     */
    public final String url;
    /**
     * 文件名称
     */
    public final String name;
    /**
     * 缓存文件夹
     */
    public final String folder;
    /**
     * 实发支持断点下载
     */
    public final boolean isBreakpoint;
    /**
     * 下载监听
     */
    public OnDownloadListener onDownloadListener;


    public HucDownloader(Builder builder) {
        this.url = builder.url;
        this.name = builder.name;
        this.folder = builder.folder;
        this.isBreakpoint = builder.isBreakpoint;
        this.onDownloadListener = builder.onDownloadListener;
    }

    public static class Builder {
        private String url;
        private String name;
        private String folder;
        private boolean isBreakpoint;
        private OnDownloadListener onDownloadListener;


        public Builder url(String url) {
            this.url = url;
            return this;
        }

        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Builder folder(String folder) {
            this.folder = folder;
            return this;
        }

        public Builder isBreakpoint(boolean isBreakpoint) {
            this.isBreakpoint = isBreakpoint;
            return this;
        }

        public Builder listener(OnDownloadListener onDownloadListener) {
            this.onDownloadListener = onDownloadListener;
            return this;
        }

        public HucDownloader build() {
            return new HucDownloader(this);
        }
    }

    protected boolean isBreakpoint() {
        return isBreakpoint;
    }

    protected boolean isPause() {
        return isPause;
    }

    protected boolean isCancel() {
        return isCancel;
    }

    protected boolean isDownloading() {
        return isDownloading;
    }

    public void setDownloading(boolean downloading) {
        this.isDownloading = downloading;
    }

    /**
     * 开始下载
     */
    public void start() {
        isPause = false;
        isCancel = false;
        if (!isDownloading) {
            download();
        }
    }

    /**
     * 暂停下载
     */
    public void pause() {
        isPause = true;
    }

    /**
     * 取消下载
     */
    public void cancel() {
        isCancel = true;
    }

    /**
     * 销毁下载
     */
    public void destory() {
        cancel();
        if (handler != null) {
            handler.removeCallbacksAndMessages(null);
            handler = null;
        }
    }

    /**
     * 创建文件
     *
     * @param url 资源地址
     * @return
     */
    protected File createFile(String url) {
        File cacheFolder = new File(new IOUtils().createFolder(folder == null ? CACHE_FOLDER : folder));
        if (cacheFolder.isDirectory() && !cacheFolder.exists()) {
            cacheFolder.mkdirs();
        }
        return new File(cacheFolder.getAbsolutePath() + File.separator + (name == null ? createName(url) : name));
    }

    /**
     * 创建Url文件名称
     *
     * @param url 资源地址
     * @return
     */
    private String createName(String url) {
        if (url.contains("/") && url.contains(".")) {
            return url.substring(url.lastIndexOf("/") + 1);
        }
        SimpleDateFormat format = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
        return format.format(format) + ".zip";
    }

    /**
     * 下载文件
     */
    private void download() {
        if (TextUtils.isEmpty(url)) {
            sendFailedMsg(new IOException("File download network address is empty."));
            return;
        }
        if (!url.toUpperCase().startsWith("HTTP")) {
            sendFailedMsg(new IOException("File download address error, unable to download normal."));
            return;
        }
        setDownloading(true);
        download(url);
    }

    protected void download(final String url) {
        new Thread() {
            @Override
            public void run() {
                super.run();
                try {
                    // 统一资源
                    URL httpUrl = new URL(url);
                    // 连接类的父类,抽象类
                    URLConnection urlConnection = httpUrl.openConnection();
                    // http的连接类
                    HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
                    if (url.toUpperCase().startsWith("HTTPS")) {
                        httpURLConnection = (HttpURLConnection) urlConnection;
                        HttpsURLConnection httpsURLConnection = (HttpsURLConnection) httpURLConnection;
                        httpsURLConnection.setHostnameVerifier(new HttpsHostnameVerifier());
                        httpsURLConnection.setSSLSocketFactory(HttpsSSLSocketFactory.factory());
                        httpURLConnection = httpsURLConnection;
                    }
                    httpURLConnection.setDoInput(true);
                    // 设定请求的方法,默认是GET
                    httpURLConnection.setRequestMethod("GET");
                    // 设置字符编码
                    httpURLConnection.setRequestProperty("Charset", "UTF-8");
                    long downloadedLength = calculateDownloadedLength(url);
                    //设置下载位置
                    httpURLConnection.setRequestProperty("RANGE", "bytes=" + downloadedLength + "-");
                    // 打开到此 URL 引用的资源的通信链接(如果尚未建立这样的连接)。
                    httpURLConnection.connect();
                    int code = httpURLConnection.getResponseCode();
                    if (code == 416) {
                        sendCompletedMsg(createFile(url));
                        return;
                    }
                    // 文件大小
                    int contentLength = httpURLConnection.getContentLength();
                    InputStream is = httpURLConnection.getInputStream();
                    doHttpResponse(is, contentLength, downloadedLength, createFile(url));
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                    sendFailedMsg(e);
                } catch (IOException e) {
                    e.printStackTrace();
                    sendFailedMsg(e);
                }
            }
        }.start();
    }

    /**
     * 计算已经下载过的文件大小
     *
     * @param url
     * @return
     */
    private long calculateDownloadedLength(String url) {
        File file = createFile(url);
        if (file.exists()) {
            if (isBreakpoint()) {
                return file.length();
            } else {
                file.delete();
            }
        }
        return 0;
    }

    /**
     * 处理服务器返回数据
     */
    protected void doHttpResponse(InputStream is, long contentLength, long downloadedLength, File file) {
        long downloading = 0;
        byte[] buf = new byte[2048];
        int len;
        RandomAccessFile randomAccessFile = null;
        try {
            if (downloadedLength == 0) {
                totalSize = contentLength;
            } else {
                totalSize = downloadedLength + contentLength;
            }
            if (totalSize == downloadedLength) {
                //已下载字节和文件总字节相等,说明下载已经完成了
                sendCompletedMsg(file);
                return;
            }
            if (totalSize == 0) {
                if (downloadedLength == 0) {
                    sendFailedMsg(new IOException("The file length value is 0 and cannot be downloaded properly"));
                } else {
                    if (isBreakpoint()) {
                        sendCompletedMsg(file);
                    } else {
                        file.delete();
                    }
                }
                return;
            }
            randomAccessFile = new RandomAccessFile(file, "rw");
            randomAccessFile.seek(downloadedLength);
            while ((len = is.read(buf)) != -1) {
                if (isPause() || isCancel()) {
                    break;
                }
                randomAccessFile.write(buf, 0, len);
                downloading += len;
                long downSum = downloading + downloadedLength;
                //传递更新信息
                int percentage = (int) ((downloadedLength + downloading) * 100 / totalSize);
                sendDownloadingMsg(totalSize, downSum, percentage);
            }
            randomAccessFile.close();
            sendCompletedMsg(file);
        } catch (Exception e) {
            sendFailedMsg(e);
        } finally {
            setDownloading(false);
            try {
                if (is != null)
                    is.close();
            } catch (IOException e) {
                sendFailedMsg(e);
            }
            try {
                if (randomAccessFile != null)
                    randomAccessFile.close();
            } catch (IOException e) {
                sendFailedMsg(e);
            }
        }
    }

    /**
     * 发送成功的信息
     *
     * @param file
     */
    protected void sendCompletedMsg(File file) {
        Message msg = handler.obtainMessage();
        msg.what = WHAT_DOWNLOAD_COMPLETED;
        msg.obj = file;
        handler.sendMessage(msg);
    }


    /**
     * 发送下载失败信息
     *
     * @param e 文件异常
     */
    protected void sendFailedMsg(Exception e) {
        Message msg = handler.obtainMessage();
        msg.what = WHAT_DOWNLOAD_FAILED;
        msg.obj = e;
        handler.sendMessage(msg);
    }

    /**
     * 发送下载信息
     *
     * @param total    文件总大小
     * @param progress 文件进度
     * @param percent  百分比
     */
    protected void sendDownloadingMsg(long total, long progress, int percent) {
        Message message = handler.obtainMessage();
        message.what = WHAT_DOWNLOADING;
        Bundle bundle = new Bundle();
        bundle.putLong("total", total);
        bundle.putLong("progress", progress);
        bundle.putInt("percent", percent);
        message.setData(bundle);
        handler.sendMessage(message);
    }

    /**
     * 下载Handler处理
     */
    private Handler handler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (onDownloadListener == null) {
                return;
            }
            Bundle data = msg.getData();
            Object obj = msg.obj;
            switch (msg.what) {
                case WHAT_DOWNLOADING:
                    onDownloadListener.onDownloading(data.getLong("total"), data.getLong("progress"), data.getInt("percent"));
                    break;
                case WHAT_DOWNLOAD_COMPLETED:
                    onDownloadListener.onDownloadCompleted((File) obj);
                    break;
                case WHAT_DOWNLOAD_FAILED:
                    onDownloadListener.onDownloadFailed((Exception) obj);
                    break;
            }
        }
    };

}