您的位置:首页 > 移动开发 > Android开发

Android 多线程断点续传下载器 - 商业级代码倾血奉献

2013-08-21 16:12 399 查看
楼主三年磨剑(当然不是磨着一把剑),倾血奉献Android多线程下载Demo。有的人就问了“怎么写来写去还是Demo?”,因为老哥我实在太忙了,

每天写一点,写到现在也才写了个下载器,也就差下载管理类就是个完整的模块了。对于新手学习这已经足够了,不对,是完全足够了。

这不仅仅只是一个简单的Demo,这绝对是你前所未见的商业级别的范例,集支持多线程下载,断点续传,只使用wifi网络下载,显示下载速度,人性化提示

及超强的容错机制多功能于一体,绝对的实用,绝对的专业。



当然我写这个是为了下载apk的,大家稍微改一改就可以写成更通用的下载器。唯一有点不足的地方就是在Android上使用RandomAccessFile在创建大文件的时候

速度有些慢,导致前几秒的进度都为0。不知道有没有人可以帮我解决这个问题。

下面给出关键代码。

package com.h3c.DownloadEngine;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;

import android.content.Context;
import android.os.Environment;
import android.util.Log;

import com.h3c.DownloadEngine.common.DownloaderErrorException;
import com.h3c.DownloadEngine.common.EngineConstants;
import com.h3c.DownloadEngine.common.EngineUtil;
import com.h3c.DownloadEngine.common.EngineVariable;
import com.h3c.DownloadEngine.db.EngineDBOperator;
import com.h3c.DownloadEngine.entity.DownloadBean;

public class Downloader {
    private final static String TAG = "Downloader";
    private final static byte[] lock_getFileSize = new byte[1];
    private final static byte[] lock_refresh_progress = new byte[1];

    private int mThreadCount = 4;// 默认子线程数为4个
    private int bufferSize = 1024 * 16; // 16K 一个块

    private DownloadBean mBean;// 注意,这里是dwonloader的bean不是其subBean
    private Context mContext;
    private DownloadEngineCallback mCallback;
    private EngineDBOperator mDBOper;
    private int mDoneThreadCount = 0;// 完成的线程数
    private int mState = EngineConstants.DOWNLOAD_STATE_INIT;// 下载器状态
    private ArrayList<DownloadBean> mBeans = new ArrayList<DownloadBean>(
            mThreadCount);

    public Downloader(DownloadBean bean, Context context,
            DownloadEngineCallback callback) throws DownloaderErrorException {
        this.mBean = bean;
        this.mContext = context;
        this.mCallback = callback;
        this.mDBOper = EngineDBOperator.getInstance(context);

        if (this.mDBOper != null) {
            if (this.mDBOper.isHasDownloadTaskByUrl(bean.url)) {// 如果此任务已经存放进数据库
                getDownloaderInfoFromDB(bean);
            } else {// 插入信息至数据库
                addDownloaderInfoToDB(bean);
            }
        } else {
            callBackError("Downloader错误,可能是EngineDBOperator为Null.");
            throw new DownloaderErrorException(
                    "Downloader错误,可能是EngineDBOperator为Null.");
        }
    }

    public DownloadBean getDownloaderInfo() {
        return mBean;
    }

    public int getDownloaderState() {
        return mState;
    }

    /**
     * 请求初始化
     * 
     * @param state
     */
    protected void setDownloaderState(int state) {
        mState = state;
        if (state == EngineConstants.DOWNLOAD_STATE_INIT) {
            mBean.currentPosition = 0;
        }
    }

    /**
     * 加入下载信息进入数据库,此方法用于刚刚初始化Downloader,且数据库中没有该任务的时候
     * 
     * @param bean
     * @throws DownloaderErrorException
     */
    private void addDownloaderInfoToDB(DownloadBean bean)
            throws DownloaderErrorException {
        if (mState != EngineConstants.DOWNLOAD_STATE_INIT
                && mState != EngineConstants.DOWNLOAD_STATE_STOP
                && mState != EngineConstants.DOWNLOAD_STATE_ERROR) {
            callBackError("这个任务已经加入到数据库中了");
            throw new DownloaderErrorException("这个任务已经加入到数据库中了");
        }

        if (mDBOper != null) {
            long fileSize = bean.fileSize;
            if (mBeans.size() > 0) {
                mBeans.clear();
            }

            try {
                if (fileSize > 0) {// 判断传入的fileSize大小,如果大于0,就不用从网络中获取,直接初始化N个子下载器
                    if (!hasSpaceInSDCard()) {
                        return;
                    }
                    long range = fileSize / mThreadCount;// 文件分段值
                    for (int i = 0; i < mThreadCount - 1; i++) {
                        DownloadBean subBean = (DownloadBean) bean.clone();
                        subBean.threadId = i;
                        subBean.startPosition = i * range;
                        subBean.endPosition = (i + 1) * range - 1;
                        mBeans.add(subBean);
                    }

                    DownloadBean subBean = (DownloadBean) bean.clone();
                    subBean.threadId = mThreadCount - 1;
                    subBean.startPosition = (mThreadCount - 1) * range;
                    subBean.endPosition = fileSize - 1;
                    mBeans.add(subBean);
                } else {// 如果等于0,就直接初始化N个0大小的子下载器
                    for (int n = 0; n < mThreadCount - 1; n++) {
                        DownloadBean subBean = (DownloadBean) bean.clone();
                        subBean.threadId = n;
                        mBeans.add(subBean);
                    }

                    DownloadBean subBean = (DownloadBean) bean.clone();
                    subBean.threadId = mThreadCount - 1;
                    mBeans.add(subBean);
                }

                mDBOper.addDownloadTask(mBeans);
                if (bean.fileSize > 0) {// 如果文件大小已经获取就进入等待状态
                    mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下载器进入等待状态
                } else {// 文件大小未获取就开启线程去获取文件大小并更新子下载器中的内容
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            boolean flag = false;
                            synchronized (lock_getFileSize) {
                                flag = getFileSizeByNetwork(mBean);
                            }
                            if (flag) {
                                mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下载器进入等待状态
                            } else {
                                Log.e(TAG, "从网络中获取文件大小失败 1");
                            }
                        }
                    }).start();

                }
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
        } else {
            callBackError("addDownloaderInfoToDB错误,可能是EngineDBOperator为Null.");
            throw new DownloaderErrorException(
                    "addDownloaderInfoToDB错误,可能是EngineDBOperator为Null.");
        }
    }

    /**
     * 从数据库中读取下载器信息
     * 
     * @param bean
     * @throws DownloaderErrorException
     */
    private void getDownloaderInfoFromDB(DownloadBean bean)
            throws DownloaderErrorException {
        if (mDBOper != null) {
            mBeans.clear();
            mBeans = mDBOper.getDownloadTaskByUrl(bean.url);

            mBean.currentPosition = 0;
            mBean.fileSize = 0;
            mThreadCount = mBeans.size();
            for (DownloadBean subBean : mBeans) {
                mBean.currentPosition += subBean.currentPosition;
                if (subBean.fileSize > mBean.fileSize) {
                    mBean.fileSize = subBean.fileSize;
                }
            }

            if (mBean.fileSize < 1) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        boolean flag = false;
                        synchronized (lock_getFileSize) {
                            flag = getFileSizeByNetwork(mBean);
                        }
                        if (flag) {
                            mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下载器进入等待状态
                        } else {
                            Log.e(TAG, "从网络中获取文件大小失败 2");
                        }
                    }
                }).start();
            } else {
                mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下载器进入等待状态
            }
        } else {
            callBackError("getDownloaderInfoFromDB Error,May be EngineDBOperator is Null.");
            throw new DownloaderErrorException(
                    "getDownloaderInfoFromDB Error,May be EngineDBOperator is Null.");
        }
    }

    /**
     * 从网络中获取文件大小,并更新listBeans
     */
    private boolean getFileSizeByNetwork(DownloadBean bean) {
        HttpURLConnection connection = null;
        long fileSize = bean.fileSize;
        try {
            if (fileSize <= 0) {// 如果没有传入文件大小就从网络中获取
                URL url = new URL(bean.url);
                connection = (HttpURLConnection) url.openConnection();
                connection.setConnectTimeout(5000);
                connection.setReadTimeout(8000);

                if (android.os.Build.VERSION.SDK_INT > 10) {// 规避2.x上因为加入setRM导致连接超时的bug
                    connection.setRequestMethod("HEAD");// head
                }

                int resopnseCode = connection.getResponseCode();
                if (resopnseCode != 200 && resopnseCode != 206) {
                    callBackError("http返回码不正确:" + resopnseCode);
                    return false;
                }
                // 获得文件大小
                fileSize = connection.getContentLength();
                mBean.fileSize = fileSize;

                if (fileSize <= 0) {
                    callBackError("无法从服务器上获得文件大小" + fileSize);
                    return false;
                }

                // if (connection.getHeaderField("Content-Range") == null) {
                // Log.e(TAG, "服务器不支持断点续传");
                // mThreadCount = 1;
                // }

                // 如果没有存储空间了
                if (!hasSpaceInSDCard()) {
                    return false;
                }

                long range = fileSize / mThreadCount;// 文件分段值
                // 更新listBean
                for (int i = 0; i < mThreadCount - 1; i++) {
                    DownloadBean subBean = mBeans.get(i);
                    subBean.fileSize = fileSize;
                    subBean.startPosition = i * range;
                    subBean.endPosition = (i + 1) * range - 1;
                }

                DownloadBean subBean = mBeans.get(mThreadCount - 1);
                subBean.fileSize = fileSize;
                subBean.startPosition = (mThreadCount - 1) * range;
                subBean.endPosition = fileSize - 1;

                // 更新数据库
                if (mDBOper != null) {
                    mDBOper.updateTaskCompleteSize(mBeans, mBean.url);
                } else {
                    callBackError("getFileSizeByNetwork错误,可能是EngineDBOperator is Null.");
                    throw new DownloaderErrorException(
                            "getFileSizeByNetwork错误,可能是EngineDBOperator is Null.");
                }
                return true;
            } else {// 文件有大小就直接退出
                return true;
            }
        } catch (Exception e) {
            callBackError("从服务器获取文件大小超时");
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
        return false;
    }

    /**
     * 开始下载,可能多次调用
     */
    public void startDownloader() {
        if (mState == EngineConstants.DOWNLOAD_STATE_DOWNLOADING) {// 如果正在下载就return
            return;
        }

        if (mBean == null) {
            callBackError("下载器没有初始化");
            return;
        }

        File file = new File(mBean.savePath);
        File parentDirectory = file.getParentFile();
        if (!parentDirectory.exists()) {
            parentDirectory.mkdirs();
        }

        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        if (mBeans.size() < 1) {// 防止由于发生错误导致清空了mBeans列表,但是又重新开始了任务,所有要再次初始化mBeans
            try {
                addDownloaderInfoToDB(mBean);
            } catch (DownloaderErrorException e) {
                e.printStackTrace();
                return;
            }
        }

        /**
         * 只有获得文件大小后才会开始下载
         */
        synchronized (lock_getFileSize) {
            if (mState == EngineConstants.DOWNLOAD_STATE_INIT) {// 获取文件大小失败,重新获取
                boolean flag = getFileSizeByNetwork(mBean);
                if (!flag) {
                    callBackError("获取文件大小失败");
                    return;
                }
            }
        }

        mState = EngineConstants.DOWNLOAD_STATE_DOWNLOADING;
        mDBOper.removePauseFileByUrl(mBean.url);// 从暂停列表中移除
        mDoneThreadCount = 0;// 初始化完成线程数

        for (DownloadBean bean : mBeans) {
            if (bean.currentPosition < (bean.endPosition - bean.startPosition)) {// 如果该线程属于没有下载完成的
                HamalThread hamalThread = new HamalThread(bean);
                hamalThread.start();
            } else {// 已经完成的线程不需要重新创建
                mDoneThreadCount++;
            }
        }
        
        if (mDoneThreadCount == mThreadCount) {// 下载完成
            downloaderDone();
        }
    }

    private class HamalThread extends Thread {
        private int threadId;
        private long startPos;
        private long endPos;
        private long compeleteSize;
        private String urlstr;

        public HamalThread(DownloadBean bean) {
            this.threadId = bean.threadId;
            this.startPos = bean.startPosition;
            this.endPos = bean.endPosition;
            this.compeleteSize = bean.currentPosition;
            this.urlstr = bean.url;
        }

        @Override
        public void run() {
            HttpURLConnection connection = null;
            RandomAccessFile randomAccessFile = null;
            InputStream is = null;
            try {
                URL url = new URL(urlstr);
                connection = (HttpURLConnection) url.openConnection();
                connection.setConnectTimeout(5000);
                connection.setReadTimeout(8000);
                connection.setRequestMethod("GET");
                if (mThreadCount > 1) {// 多线程下载
                    // 设置范围,格式为Range:bytes x-y;
                    connection.setRequestProperty("Range", "bytes="
                            + (startPos + compeleteSize) + "-" + endPos);
                }

                randomAccessFile = new RandomAccessFile(mBean.savePath, "rwd");
                randomAccessFile.seek(startPos + compeleteSize);
                // 将要下载的文件写到保存在保存路径下的文件中
                is = connection.getInputStream();
                byte[] buffer = new byte[bufferSize];
                int length = -1;
                EngineUtil eUtil = EngineUtil.getInstance();

                if (EngineVariable.SUPPORT_NETWORK_TYPE == EngineConstants.DOWNLOAD_NETWORK_ONLYWIFI) {// 如果只能是3G下载
                    if (eUtil.getNetworkType() != EngineConstants.NETWORK_STATE_WIFI) {// 且当前网络不是Wifi
                        interruptDownloader();
                        return;
                    }
                }

                while ((length = is.read(buffer)) != -1) {
                    // 网络判断
                    if (EngineVariable.SUPPORT_NETWORK_TYPE == EngineConstants.DOWNLOAD_NETWORK_ONLYWIFI) {// 如果只能是3G下载
                        if (eUtil.getNetworkType() != EngineConstants.NETWORK_STATE_WIFI) {// 且当前网络不是Wifi
                            interruptDownloader();
                            return;
                        }
                    }

                    randomAccessFile.write(buffer, 0, length);
                    compeleteSize += length;
                    synchronized (lock_refresh_progress) {
                        mBean.currentPosition += length;
                    }
                    // 更新数据库中的下载信息
                    mDBOper.updateTaskCompleteSize(threadId, compeleteSize,
                            urlstr);
                    if (mState == EngineConstants.DOWNLOAD_STATE_PAUSE
                            || mState == EngineConstants.DOWNLOAD_STATE_INTERRUPT
                            || mState == EngineConstants.DOWNLOAD_STATE_STOP
                            || mState == EngineConstants.DOWNLOAD_STATE_ERROR) {// 暂停
                        return;
                    }
                }

                // 该子线程下载完成
                mDoneThreadCount++;
            } catch (Exception e) {
                Log.e(TAG, "下载途中断掉了连接...");
                interruptDownloader();
                e.printStackTrace();
            } finally {
                try {
                    is.close();
                    randomAccessFile.close();
                    connection.disconnect();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            if (mDoneThreadCount == mThreadCount) {
                downloaderDone();
            }
        }
    }

    /**
     * 获取下载进度
     * 
     * @return
     */
    public int getProgress() {
        if (mBean.fileSize < 1) {
            return 0;
        }
        return (int) (mBean.currentPosition * 100 / mBean.fileSize);
    }

    /**
     * 暂停下载
     */
    public void pauseDownloader() {
        mState = EngineConstants.DOWNLOAD_STATE_PAUSE;
        mDBOper.addPauseFile(mBean.url, mBean.packageName, mBean.fileId);
    }

    /**
     * 中断下载(非人为的暂停)
     */
    private void interruptDownloader() {
        mState = EngineConstants.DOWNLOAD_STATE_INTERRUPT;
    }

    /**
     * 结束下载
     */
    public void stopDownloader() {
        mState = EngineConstants.DOWNLOAD_STATE_STOP;
        mBean.currentPosition = 0;
        removeDownloaderInfo(mBean.url);
    }

    /**
     * 清除下载的信息
     * 
     * @param urlstr
     */
    private void removeDownloaderInfo(String urlstr) {
        mDBOper.deleteDownloadTaskByUrl(urlstr);
        mDBOper.removePauseFileByUrl(urlstr);
        mBeans.clear();
    }

    /**
     * 下载完成
     */
    private void downloaderDone() {
        mState = EngineConstants.DOWNLOAD_STATE_DONE;
        mBean.doneTime = System.currentTimeMillis();
        mCallback.callbackWhenDownloadTaskListener(mState, mBean,
                mBean.fileName + "下载完成");

        removeDownloaderInfo(mBean.url);
        mDBOper.addCompleteTask(mBean);// 将完成信息保存至数据库
    }

    /**
     * 出现错误时候回调
     * 
     * @param info
     */
    private void callBackError(String info) {
        mState = EngineConstants.DOWNLOAD_STATE_ERROR;
        mCallback.callbackWhenDownloadTaskListener(mState, mBean, info);
        removeDownloaderInfo(mBean.url);
    }

    /**
     * 判断SD卡上是否留有足够的空间
     */
    private boolean hasSpaceInSDCard() {
        if (mBean.fileSize > EngineUtil.getInstance().getFreeSpaceAtDirectory(
                Environment.getExternalStorageDirectory().getAbsolutePath())) {
            callBackError("存储卡空间不够");
            return false;
        }
        return true;
    }
}



欢迎吐槽,欢迎提供建议,欢迎提供改进方法。

整个架构详见源码,绝对值得你下载收藏!这一切都是免费的,对,你不要怀疑,商业级别的源码都是免费的。

如果你觉得好,请帮忙下载源码下载

源码:http://download.csdn.net/detail/h3c4lenovo/5987789
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: