android 单线程多任务断点排队下载(支持多界面刷新)

Posted silly_wy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android 单线程多任务断点排队下载(支持多界面刷新)相关的知识,希望对你有一定的参考价值。

最近在做一个单线程多任务的断点排队下载的功能,网上确实有很多这样的demo。但是呢我发现大部分网上的demo都是很些不完整的要么就是有缺陷的,可能是我还没找到。今天我给大家带来的一个功能完整的并且可以多界面刷新,就比如:我当前界面点了下载放后台下载了,退出了当前界面在进来网上很多这样demo都没做继续更新界面。并且还做了排队。

首先我们先制定下计划:

1、我们做的是断点下载,断点下载肯定是需要记录当前下载的进度和文件总大小的。这个当然是放在数据库里面最好,当然肯定会是异步来操作数据库,这里我们就需要用到线程锁synchronized来控制了。

2、多任务是指可以多个任务一起下载,但是我们是有排队机制的,所以当前任务只是一个任务在下载,其他的按点击顺序排队执行,这里我们就需要写一个下载管理器来管理整个下载团队,当暂停和取消都是需要把队列排队给去掉的,当然我这里是用的线程池(ThreadPoolExecutor)来管理线程的。

3、还需要做到多界面刷新,我这里是通过注册一个下载监听来做的,所有的监听都放在map集合里面,已id为key来进行保存,每次需要回调监听的时候都是通过map里面去找,所以销毁的时候需要去注销下所有的监听,然而每次去新的界面只要注册了下载监听,我又可以再map里面找到你新注册的监听从而来进行新的回调。

话不多说,请看效果:

这里写图片描述


接下来我们来分析代码:

首先我们需要创建数据库、创建表、创建文件夹、所需的操作表的代码、以及下载所需要的Bean(这种初始化操作就不在这里详细做描述)



现在我们说重点,线程池的管理

由于我们是单线程,所以线程池里面同时执行的线程只需为1就可以,(意思就是说当前线程执行的只有1条线程,当前的线程还没执行完毕就把后面添加进来的线程进行有序排序<先来后到顺序>)。并且还可以拿到排队中并还没执行的线程,这样我们就可以做很多事了。

package com.down.executors;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ThreadPoolExecutor.AbortPolicy;

/**
 *  线程池工具
 */
public class DownLoadExecutor {

    /** 请求线程池队列,同时允许1个线程操作 */
    private static ThreadPoolExecutor mPool ;

    //当线程池中的线程小于mCorePoolSize,直接创建新的线程加入线程池执行任务
    private static final int mCorePoolSize = 1;
    //最大线程数
    private static final int mMaximumPoolSize = 1;
    //线程执行完任务后,且队列中没有可以执行的任务,存活的时间,后面的参数是时间单位
    private static final long mKeepAliveTime = 5L;

    /** 执行任务,当线程池处于关闭,将会重新创建新的线程池 */
    public synchronized static void execute(Runnable run) {
        if (run == null) {
            return;
        }
        if (mPool == null || mPool.isShutdown()) {
            //参数说明
            //当线程池中的线程小于mCorePoolSize,直接创建新的线程加入线程池执行任务
            //当线程池中的线程数目等于mCorePoolSize,将会把任务放入任务队列BlockingQueue中
            //当BlockingQueue中的任务放满了,将会创建新的线程去执行,
            //但是当总线程数大于mMaximumPoolSize时,将会抛出异常,交给RejectedExecutionHandler处理
            //mKeepAliveTime是线程执行完任务后,且队列中没有可以执行的任务,存活的时间,后面的参数是时间单位
            //ThreadFactory是每次创建新的线程工厂
            mPool = new ThreadPoolExecutor(mCorePoolSize, mMaximumPoolSize, mKeepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), Executors.defaultThreadFactory(), new AbortPolicy());
        }
        mPool.execute(run);
    }

    /** 取消线程池中某个还未执行的任务 */
    public synchronized static boolean cancel(Runnable run) {
        if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
            return mPool.getQueue().remove(run);
        }else{
            return false;
        }
    }

    /** 查看线程池中是否还有某个还未执行的任务 */
    public synchronized static boolean contains(Runnable run) {
        if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
            return mPool.getQueue().contains(run);
        } else {
            return false;
        }
    }

    /** 立刻关闭线程池,并且正在执行的任务也将会被中断 */
    public static void stop() {
        if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
            mPool.shutdownNow();
        }
    }

    /** 平缓关闭单任务线程池,但是会确保所有已经加入的任务都将会被执行完毕才关闭 */
    public synchronized static void shutdown() {
        if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
            mPool.shutdownNow();
        }
    }
}

这里是取名为观察者,也就是监听回调

package com.down.executors;

import com.down.bean.DownLoadBean;

/**
 *  观察者
 */
public interface DownLoadObserver {
    /**准备下载*/
    void onPrepare(DownLoadBean bean);
    /** 开始下载 */
    void onStart(DownLoadBean bean);
    /** 下载中 */
    void onProgress(DownLoadBean bean);
    /** 暂停 */
    void onStop(DownLoadBean bean);
    /** 下载完成 */
    void onFinish(DownLoadBean bean);
    /** 下载失败 */
    void onError(DownLoadBean bean);
    /** 删除成功 */
    void onDelete(DownLoadBean bean);
}

现在关键部分到了,我们的下载管理器。

管理器全局操作:开始下载之前必须先注册下载监听,不然后不会回调,注册监听的时候就把监听放到map集合,开启下载的时候就把当前线程也到map集合保存,这样就简单啦,假如我需要暂停或者删除当前下载的任务,直接改变状态在下载的while循环里面直接return就可,如果是排队中的呢,则是先拿到队列中的任务线程再把线程中的bean对象里面的状态置为暂停或者删除,这样就算到它执行的时候也会直接返回(因为我的执行线程里面第一行代码就是判断是否被暂停或删除),继续执行排在他后面的任务线程。删除下载任务则是先从map集合里面通过id拿到需要删除的线程,然后把线程里面的状态改为删除,然后删除数据库,删除文件。

package com.down.executors;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import com.down.bean.DownLoadBean;
import com.down.uitl.DataBaseUtil;

/**
 * 下载管理器
 */
public class DownLoadManager {
    public static final int STATE_NONE = -1;
    /** 开始下载 */
    public static final int STATE_START = 0;
    /** 等待中 */
    public static final int STATE_WAITING = 1;
    /** 下载中 */
    public static final int STATE_DOWNLOADING = 2;
    /** 暂停 */
    public static final int STATE_PAUSED = 3;
    /** 下载完毕 */
    public static final int STATE_DOWNLOADED = 4;
    /** 下载失败 */
    public static final int STATE_ERROR = 5;
    /** 删除下载成功 */
    public static final int STATE_DELETE = 6;

    /** 用于记录观察者,当信息发送了改变,需要通知他们 */
    private Map<String, DownLoadObserver> mObservers = new ConcurrentHashMap<String, DownLoadObserver>();
    /** 用于记录所有下载的任务,方便在取消下载时,通过id能找到该任务进行删除 */
    private Map<String, DownLoadTask> mTaskMap = new ConcurrentHashMap<String, DownLoadTask>();
    /** 全局记录当前正在下载的bean */
    private DownLoadBean down_bean;

    private static DownLoadManager instance;

    public static DownLoadManager getInstance() {
        if (instance == null) {
            instance = new DownLoadManager();
        }
        return instance;
    }

    /** 注册观察者 */
    public void registerObserver(String id, DownLoadObserver observer) {
        if (!mObservers.containsKey(id)) {
            mObservers.put(id, observer);
        }
    }

    /** 移除观察者 */
    public void RemoveObserver() {
        mObservers.clear();
    }

    /** 删除当前正在下载的任务 */
    public void DeleteDownTask(DownLoadBean bean) {
        if (mTaskMap.containsKey(bean.id)) {
            // 拿到当前任务
            DownLoadTask task = mTaskMap.get(bean.id);
            // 暂停下载任务(等于取消了该线程)
            task.bean.downloadState = STATE_DELETE;

            // 再更改删除界面状态(这是也调一次是怕在没下载的时候删除)
            bean.downloadState = STATE_DELETE;
            notifyDownloadStateChanged(bean);

            // 最后删除数据库数据
            DataBaseUtil.DeleteDownLoadById(bean.id);
            // 删除文件
            File file = new File(bean.getPath());
            if (file.exists()) {
                file.delete();
            }
            file = null;
        }
    }

    /** 销毁的时候关闭线程池以及当前执行的线程,并清空所有数据和把当前下载状态存进数据库 */
    public void Destory() {
        DownLoadExecutor.stop();
        mObservers.clear();
        mTaskMap.clear();
        if (down_bean != null) {
            down_bean.downloadState = STATE_PAUSED;
            DataBaseUtil.UpdateDownLoadById(down_bean);
        }
    }

    /** 当下载状态发送改变的时候回调 */
    private ExecuteHandler handler = new ExecuteHandler();

    /** 拿到主线程Looper */
    @SuppressLint("HandlerLeak")
    private class ExecuteHandler extends Handler {
        private ExecuteHandler() {
            super(Looper.getMainLooper());
        }

        @Override
        public void handleMessage(Message msg) {
            DownLoadBean bean = (DownLoadBean) msg.obj;
            if (mObservers.containsKey(bean.id)) {
                DownLoadObserver observer = mObservers.get(bean.id);
                switch (bean.downloadState) {
                case STATE_START:// 开始下载
                    observer.onStart(bean);
                    break;
                case STATE_WAITING:// 准备下载
                    observer.onPrepare(bean);
                    break;
                case STATE_DOWNLOADING:// 下载中
                    observer.onProgress(bean);
                    break;
                case STATE_PAUSED:// 暂停
                    observer.onStop(bean);
                    break;
                case STATE_DOWNLOADED:// 下载完毕
                    observer.onFinish(bean);
                    break;
                case STATE_ERROR:// 下载失败
                    observer.onError(bean);
                    break;
                case STATE_DELETE:// 删除成功
                    observer.onDelete(bean);
                    break;
                }
            }
        }
    }

    /** 当下载状态发送改变的时候调用 */
    private void notifyDownloadStateChanged(DownLoadBean bean) {
        Message message = handler.obtainMessage();
        message.obj = bean;
        handler.sendMessage(message);
    }

    /** 开启下载,需要传入一个DownAppBean对象 */
    public void download(DownLoadBean loadBean) {
        // 先判断是否有这个app的下载信息
        DownLoadBean bean = DataBaseUtil.getDownLoadById(loadBean.id);
        if (bean == null) {// 如果没有,则根据loadBean创建一个新的下载信息
            bean = loadBean;
            DataBaseUtil.insertDown(bean);
        }

        // 判断状态是否为STATE_NONE、STATE_PAUSED、STATE_ERROR、STATE_DELETE。只有这4种状态才能进行下载,其他状态不予处理
        if (bean.downloadState == STATE_NONE
                || bean.downloadState == STATE_PAUSED
                || bean.downloadState == STATE_DELETE
                || bean.downloadState == STATE_ERROR) {
            // 下载之前,把状态设置为STATE_WAITING,因为此时并没有产开始下载,只是把任务放入了线程池中,当任务真正开始执行时,才会改为STATE_DOWNLOADING
            bean.downloadState = STATE_WAITING;
            DataBaseUtil.UpdateDownLoadById(bean);
            // 每次状态发生改变,都需要回调该方法通知所有观察者
            notifyDownloadStateChanged(bean);

            DownLoadTask task = new DownLoadTask(bean);// 创建一个下载任务,放入线程池
            // 线程放入map里面方便管理
            mTaskMap.put(bean.id, task);

            DownLoadExecutor.execute(task);
        } else if (bean.downloadState == STATE_START
                || bean.downloadState == STATE_DOWNLOADING
                || bean.downloadState == STATE_WAITING) {// 如果正在下载则暂停

            if (mTaskMap.containsKey(bean.id)) {
                DownLoadTask task = mTaskMap.get(bean.id);
                task.bean.downloadState = STATE_PAUSED;
                DataBaseUtil.UpdateDownLoadById(task.bean);

                // 取消还在排队中的线程
                if (DownLoadExecutor.cancel(task)) {
                    mObservers.get(bean.id).onStop(task.bean);
                }
            }
        }
    }

下载线程:这里需要注意的事,我没有在他实时下载的时候保存他的进度,只在他断掉的时候保存,因为我觉得实时下载的时候一直操作数据库不妥,性能肯定有问题。好,这时你们就会问,假如我操作不当会导致数据库里面没有当前进度,但是注意看清楚了,我下载的时候是拿的文件当前长度而不是数据库的下载进度,其实这很好理解,文件都没了,就算你数据库里面记录的有进度,也于事无补所以说拿文件的当前长度是最合理的。当然如果文件没了,肯定得先创建文件然后下载进度肯定也是0了。

我们使用的断点下载是使用的http里面的Range属性,里面参数为当前进度-某进度,这个是支持多线程下载的,这里我们只是单线程,所以就是当前进度-文件的总进度。当然我们还得看这个服务器是否支持断点下载,有的服务器不支持断点下载,你下载下来那就是坑爹,怎么判断呢,这个http里面有返回状态码的,返回206就证明是可以断点下载的。

当然下载中途报任何崩溃都是下载失败,都是需要删除数据。还有就是记得下载完之后一定要把保存线程的map集合里面remove掉这条线程

    public class DownLoadTask implements Runnable {

        private DownLoadBean bean;

        public DownLoadTask(DownLoadBean bean) {
            this.bean = bean;
        }

        @Override
        public void run() {
            // 等待中就暂停了
            if (bean.downloadState == STATE_PAUSED) {
                bean.downloadState = STATE_PAUSED;
                DataBaseUtil.UpdateDownLoadById(bean);
                return;
            } else if (bean.downloadState == STATE_DELETE) {// 等待中就删除直接回调界面,然后直接返回
                bean.downloadState = STATE_DELETE;
                notifyDownloadStateChanged(bean);
                mTaskMap.remove(bean.id);
                return;
            }

            bean.downloadState = DownLoadManager.STATE_START;// 开始下载
            DataBaseUtil.UpdateDownLoadById(bean);
            notifyDownloadStateChanged(bean);

            // 当前下载的进度
            long compeleteSize = 0;
            File file = new File(bean.getPath());// 获取下载文件
            if (!file.exists()) {
                // 如果文件不存在
                bean.currentSize = 0;
                try {
                    file.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else {
                // 如果存在就拿当前文件的长度,设置当前下载长度
                // (这样的好处就是不用每次在下载文件的时候都需要写入数据库才能记录当前下载的长度,一直操作数据库是很费资源的)
                compeleteSize = file.length();
            }
            try {
                URL url = new URL(bean.url);
                HttpURLConnection connection = (HttpURLConnection) url
                        .openConnection();
                connection.setConnectTimeout(30 * 1000);
                connection.setRequestMethod("GET");
                connection.setRequestProperty("Range", "bytes=" + compeleteSize
                        + "-" + bean.appSize);

                // 获取的状态码
                int code = connection.getResponseCode();
                // 判断是否能够断点下载
                if (code == 206) {
                    @SuppressWarnings("resource")
                    OutputStream outputStream = new FileOutputStream(file, true);
                    // 将要下载的文件写到保存在保存路径下的文件中
                    InputStream is = connection.getInputStream();
                    byte[] buffer = new byte[102400];
                    int length = -1;
                    // 进入下载中状态
                    bean.downloadState = STATE_DOWNLOADING;
                    DataBaseUtil.UpdateDownLoadById(bean);

                    while ((length = is.read(buffer)) != -1) {
                        // 暂停就回调,然后直接返回
                        if (bean.downloadState == STATE_PAUSED) {
                            bean.downloadState = STATE_PAUSED;
                            DataBaseUtil.UpdateDownLoadById(bean);
                            notifyDownloadStateChanged(bean);
                            return;
                        } else if (bean.downloadState == STATE_DELETE) {// 下载的时候删除直接回调界面,然后直接返回
                            bean.downloadState = STATE_DELETE;
                            notifyDownloadStateChanged(bean);
                            mTaskMap.remove(bean.id);
                            return;
                        }
                        // 把当前下载的bean给全局记录的bean
                        down_bean = bean;
                        outputStream.write(buffer, 0, length);
                        compeleteSize += length;
                        // 更新数据库中的下载信息
                        // 用消息将下载信息传给进度条,对进度条进行更新
                        bean.currentSize = compeleteSize;
                        notifyDownloadStateChanged(bean);
                    }
                    if (bean.appSize == bean.currentSize) {
                        bean.downloadState = STATE_DOWNLOADED;
                        DataBaseUtil.UpdateDownLoadById(bean);
                        notifyDownloadStateChanged(bean);
                    } else {
                        bean.downloadState = STATE_ERROR;
                        DataBaseUtil.UpdateDownLoadById(bean);
                        notifyDownloadStateChanged(bean);
                        bean.currentSize = 0;// 错误状态需要删除文件
                        file.delete();
                    }
                } else {
                    Log.e("123456", "不支持断点下载");
                }
            } catch (IOException e) {
                bean.downloadState = STATE_ERROR;
                DataBaseUtil.UpdateDownLoadById(bean);
                notifyDownloadStateChanged(bean);
                bean.currentSize = 0;// 错误状态需要删除文件
                file.delete();
            }
            mTaskMap.remove(bean.id);
        }
    }
}

监听回调:当然回调监听都是通过map集合里面来找,有就通过id回调,没有就不回调。这样就能保证你每次注册的监听回调都是最新的。达到只要注册了下载监听并且是当前下载的那条id。就能回调的到你最新注册的监听,刷新界面。当然你界面销毁的时候也要注销监听(也就是清除保存监听map的集合)。

    /** 当下载状态发送改变的时候回调 */
    private ExecuteHandler handler = new ExecuteHandler();

    /** 拿到主线程Looper */
    @SuppressLint("HandlerLeak")
    private class ExecuteHandler extends Handler {
        private ExecuteHandler() {
            super(Looper.getMainLooper());
        }

        @Override
        public void handleMessage(Message msg) {
            DownLoadBean bean = (DownLoadBean) msg.obj;
            if (mObservers.containsKey(bean.id)) {
                DownLoadObserver observer = mObservers.get(bean.id);
                switch (bean.downloadState) {
                case STATE_START:// 开始下载
                    observer.onStart(bean);
                    break;
                case STATE_WAITING:// 准备下载
                    observer.onPrepare(bean);
                    break;
                case STATE_DOWNLOADING:// 下载中
                    observer.onProgress(bean);
                    break;
                case STATE_PAUSED:// 暂停
                    observer.onStop(bean);
                    break;
                case STATE_DOWNLOADED:// 下载完毕
                    observer.onFinish(bean);
                    break;
                case STATE_ERROR:// 下载失败
                    observer.onError(bean);
                    break;
                case STATE_DELETE:// 删除成功
                    observer.onDelete(bean);
                    break;
                }
            }
        }
    }

    /** 当下载状态发送改变的时候调用 */
    private void notifyDownloadStateChanged(DownLoadBean bean) {
        Message message = handler.obtainMessage();
        message.obj = bean;
        handler.sendMessage(message);
    }

这里我们拿到数据的同时需要跟数据库里面的进行比较,如果数据库里面有跟现在的数据id相同的就得把数据库的数据放入
当前的集合里面并且拿到文件路径去取文件的长度来显示。
注意在onDestroy方法里面注销监听,也就是销毁的时候注销监听

public class MainActivity extends Activity {
    /** 列表控件 */
    public ListView listview;
    /** 列表数据bean */
    public ArrayList<DownLoadBean> list;

    public DownAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        list = new ArrayList<DownLoadBean>();
        Test();
        listview = (ListView) findViewById(R.id.listview);
        adapter = new DownAdapter(MainActivity.this, list);
        listview.setAdapter(adapter);
    }

    // 测试数据
    private void Test() {
        DownLoadBean bean1 = new DownLoadBean();
        bean1.id = "3210158";
        bean1.appName = "剑与魔法";
        bean1.appSize = 252821785;
        bean1.appIcon = "http://p1.qhimg.com/dr/160_160_/t0170f68197b3c8efe9.png";
        bean1.url = "http://m.shouji.360tpcdn.com/160315/168f6b5f7e38b95f8d7dcce94076acc4/com.longtugame.jymf.qihoo_22.apk";

        DownLoadBean bean2 = new DownLoadBean();
        bean2.id = "2981222";
        bean2.appName = "花椒-高颜值的直播App";
        bean2.appSize = 17699443;
        bean2.appIcon = "http://p1.qhimg.com/dr/160_160_/t01c52b0ee594a7f507.png";
        bean2.url = "http://m.shouji.360tpcdn.com/160318/a043152dd8789131a12c5beeb7e42e34/com.huajiao_4071059.apk";

        DownLoadBean bean3 = new DownLoadBean();
        bean3.id = "21972";
        bean3.appName = "唯品会";
        bean3.appSize = 33411097;
        bean3.appIcon = "http://p1.qhimg.com/dr/160_160_/t016c539aa97fdef5bf.png";
        bean3.url = "http://m.shouji.360tpcdn.com/160310/5aae1072a87bf4cef0ccec0e17999d27/com.achievo.vipshop_436.apk";

        DownLoadBean bean4 = new DownLoadBean();
        bean4.id = "1625930";
        bean4.appName = "开心消消乐";
        bean4.appSize = 67771094;
        bean4.appIcon = "http://p1.qhimg.com/dr/160_160_/t01fbaee14a2b65be0f.png";
        bean4.url = "http://m.shouji.360tpcdn.com/160310/ca3b2c5ab347fc988dde0325e6f7c658/com.happyelements.AndroidAnimal_31.apk";

        DownLoadBean bean5 = new DownLoadBean();
        bean5.id = "8043";
        bean5.appName = "阿里旅行";
        bean5.appSize = 33840292;
        bean5.appIcon = "http://p1.qhimg.com/dr/160_160_/t01c513232212e2d915.png";
        bean5.url = "http://m.shouji.360tpcdn.com/160317/0a2c6811b5fc9bada8e7e082fb5a9324/com.taobao.trip_3001049.apk";

        DownLoadBean bean6 = new DownLoadBean();
        bean6.id = "65533";
        bean6.appName = "苏宁易购";
        bean6.appSize = 27854306;
        bean6.appIcon = "http://p1.qhimg.com/dr/160_160_/t01f9b42c0addddd698.png";
        bean6.url = "http://m.shouji.360tpcdn.com/160316/deab26b43b55089736817040f921c1e7/com.suning.mobile.ebuy_120.apk";

        list.add(bean1);
        list.add(bean2);
        list.add(bean3);
        list.add(bean4);
        list.add(bean5);
        list.add(bean6);

        ArrayList<DownLoadBean> list_lin = DataBaseUtil.getDownLoad();
        for (int i = 0; i < list_lin.size(); i++) {
            for (int j = 0; j < list.size(); j++) {
                if (list_lin.get(i).id.equals(list.get(j).id)) {
                    list.remove(j);
                    File file = new File(list_lin.get(i).getPath());
                    if (file.exists()) {
                        list_lin.get(i).currentSize = file.length();
                    }
                    list.add(j, list_lin.get(i));
                    break;
                }
            }
        }
    }

    @Override
    public void onDestroy() {
    DownLoadManager.getInstance().RemoveObserver();
        super.onDestroy();
    }
}

这是adapter,显示时则需要根据状态来不一样的显示,然后注册下载监听(根据每个回调来进行不一样的显示,每个回调都会有个下载bean)、点击下载和点击删除。


    public class DownAdapter extends BaseAdapter {

        public ArrayList<DownLoadBean> list;
        public Context context;

        public DownAdapter(Context context, ArrayList<DownLoadBean> list) {
            this.context = context;
            this.list = list;
        }
        /**
         * 换算文件的大小
         */
        public String FormetFileSize(long fileSize) {// 转换文件大小
            if (fileSize <= 0) {
                return "0M";
            }
            DecimalFormat df = new DecimalFormat("#.00");
            String fileSizeString = "";
            if (fileSize < 1024) {
                fileSizeString = df.format((double) fileSize) + "B";
            } else if (fileSize < 1048576) {
                fileSizeString = df.format((double) fileSize / 1024) + "K";
            } else if (fileSize < 1073741824) {
                fileSizeString = df.format((double) fileSize / 1048576) + "M";
            } else {
                fileSizeString = df.format((double) fileSize / 1073741824) + "G";
            }
            return fileSizeString;
        }

        @Override
        public int getCount() {
            return list.size();
        }

        @Override
        public Object getItem(int position) {
            return list.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @SuppressLint("InflateParams")
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            final ViewHolder viewHolder;
            final DownLoadBean bean = list.get(position);
            if (convertView == null) {
                viewHolder = new ViewHolder();
                convertView = LayoutInflater.from(context).inflate(
                        R.layout.item_down, null);
                viewHolder.txt_name = (TextView) convertView
                        .findViewById(R.id.txt_name);
                viewHolder.txt_state = (TextView) convertView
                        .findViewById(R.id.txt_state);
                viewHolder.btn_delete = (Button) convertView
                        .findViewById(R.id.btn_delete);
                viewHolder.txt_file_size = (TextView) convertView
                        .findViewById(R.id.txt_file_size);
                viewHolder.seekbar = (SeekBar) convertView
                        .findViewById(R.id.seekbar);

                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }
            viewHolder.txt_name.setText(bean.appName);
            viewHolder.txt_file_size.setText(FormetFileSize(bean.currentSize)
                    + " / " + FormetFileSize(bean.appSize));

            if (bean.downloadState == DownLoadManager.STATE_START) {
                viewHolder.txt_state.setText("开始下载");
            } else if (bean.downloadState == DownLoadManager.STATE_WAITING) {
                viewHolder.txt_state.setText("排队等待中");
            } else if (bean.downloadState == DownLoadManager.STATE_DOWNLOADING) {
                viewHolder.txt_state.setText("下载中");
            } else if (bean.downloadState == DownLoadManager.STATE_PAUSED) {
                viewHolder.txt_state.setText("暂停中");
            } else if (bean.downloadState == DownLoadManager.STATE_DOWNLOADED) {
                viewHolder.txt_state.setText("下载完成");
                viewHolder.btn_delete.setVisibility(View.GONE);
            } else if (bean.downloadState == DownLoadManager.STATE_ERROR) {
                viewHolder.txt_state.setText("下载失败,请重新下载");
            } else {
                viewHolder.txt_state.setText("点击下载");
            }

            viewHolder.seekbar.setProgress((int) ((float) bean.currentSize
                    / (float) bean.appSize * 100f));
            viewHolder.seekbar.setMax(100);

            DownLoadManager.getInstance().registerObserver(bean.id,
                    new DownLoadObserver() {
                        @Override
                        public void onStop(DownLoadBean bean) {
                            viewHolder.txt_state.setText("暂停中");
                        }

                        @Override
                        public void onStart(DownLoadBean bean) {
                            viewHolder.txt_state.setText("开始下载");
                        }

                        @Override
                        public void onProgress(DownLoadBean bean) {
                            viewHolder.txt_file_size
                                    .setText(FormetFileSize(bean.currentSize)
                                            + " / "
                                            + FormetFileSize(bean.appSize));
                            viewHolder.txt_state.setText("下载中");
                            viewHolder.seekbar
                                    .setProgress((int) ((float) bean.currentSize
                                            / (float) bean.appSize * 100f));
                        }

                        @Override
                        public void onPrepare(DownLoadBean bean) {
                            viewHolder.txt_state.setText("排队等待中");
                        }

                        @Override
                        public void onFinish(DownLoadBean bean) {
                            viewHolder.txt_state.setText("下载完成");
                            viewHolder.btn_delete.setVisibility(View.GONE);
                        }

                        @Override
                        public void onError(DownLoadBean bean) {
                            viewHolder.txt_state.setText("下载失败,请重新下载");
                        }

                        @Override
                        public void onDelete(DownLoadBean bean) {
                            // 删除成功之后的接口
                            viewHolder.txt_state.setText("点击下载");
                            viewHolder.txt_file_size.setText(FormetFileSize(0)
                                    + " / " + FormetFileSize(bean.appSize));
                            viewHolder.seekbar.setProgress(0);
                        }
                    });

            convertView.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    // 开启下载
                    DownLoadManager.getInstance().download(bean);
                }
            });

            viewHolder.btn_delete.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    // 删除当前任务
                    DownLoadManager.getInstance().DeleteDownTask(bean);
                }
            });
            return convertView;
        }

        private class ViewHolder {
            private TextView txt_name;
            private Button btn_delete;
            private TextView txt_state;
            private TextView txt_file_size;
            private SeekBar seekbar;
        }
    }

demo里面和博客里面貌似忘记关闭流了,关闭流请在判断网络状态码里面自行加上,分别在while循环一开头的判断里面和下载完之后关闭流。请看代码:


// 暂停就回调,然后直接返回
if (bean.downloadState == STATE_PAUSED) {
    bean.downloadState = STATE_PAUSED;
    DataBaseUtil.UpdateDownLoadById(bean);
    notifyDownloadStateChanged(bean);
    //关闭流
    outputStream.close();
    is.close();
    return;
} else if (bean.downloadState == STATE_DELETE) {// 下载的时候删除直接回调界面,然后直接返回
    bean.downloadState = STATE_DELETE;
    notifyDownloadStateChanged(bean);
    mTaskMap.remove(bean.id);
    //关闭流
    outputStream.close();
    is.close();
    return;
    }
//下载完之后回调成功之后需要关闭流                  
if (bean.appSize == bean.currentSize) {
    bean.downloadState = STATE_DOWNLOADED;
    DataBaseUtil.UpdateDownLoadById(bean);
    notifyDownloadStateChanged(bean);
} else {
    bean.downloadState = STATE_ERROR;
    DataBaseUtil.UpdateDownLoadById(bean);
    notifyDownloadStateChanged(bean);
    bean.currentSize = 0;// 错误状态需要删除文件
    file.delete();
    }
//关闭流
outputStream.close();
is.close();

本篇博客就到这里,如果有对单线程多任务断点排队下载有更好的建议、bug、有疑问的欢迎留言讨论。

希望大家多多关注我的博客,多多支持我。

尊重原创转载请注明:(http://blog.csdn.net/u013895206) !

下面是地址传送门: http://download.csdn.net/detail/u013895206/9475629

以上是关于android 单线程多任务断点排队下载(支持多界面刷新)的主要内容,如果未能解决你的问题,请参考以下文章

Android多线程断点续传下载原理及实现

Andoid 更好的Android多线程下载框架

Java多线程下载器FileDownloader(支持断点续传代理等功能)

Java单线程文件下载,支持断点续传功能

Python实现下载界面(带进度条,断点续传,多线程多任务下载等)

Android(十五)多线程下载包括断点续传