Android实现简易轻量下载器:单线程任务队列

最近的项目是一个与音乐相关的App,其中有一个功能:收藏喜欢的歌曲,在wifi的环境下自动下载。

考虑到音乐歌曲都是3、4Mb的小文件,断点下载的功能便不需要了。因此只需要实现一个特别轻量、简单的下载管理类,进行管理即可。

最初的思路便是任务队列,单线程顺序执行,一个文件接着一个文件进行下载。

之前看过AsyncTask的部分源码,其设计与我的想法类似,于是便借鉴着AsyncTask的源码,实现了一个特别简单、轻量的下载管理类。

源码如下:

public class MyDownloadManager { 

    private static final String TAG = "MyDownloadManager";
    private File downloadDir; // 文件保存路径
    private static MyDownloadManager instance; // 单例

    // 单线程任务队列
    public static Executor executor;
    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "MyDownloadManager #" + mCount.getAndIncrement());
        }
    };
    private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<>(128);
    public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(1, 1, 1,
            TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);


    private MyDownloadManager() {
        // 初始化下载路径
        downloadDir = new File(AndroidCacheUtils.getCacheDirFile(MiaApplication.getInstance()), "download");
        if (!downloadDir.exists()) {
            downloadDir.mkdirs();
        }
        executor = new SerialExecutor();
    }

    /** * 顺序执行下载任务 */
    private static class SerialExecutor implements Executor { 
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

    /** * 获取单例对象 * * @return */
    public static MyDownloadManager getInstance() {
        if (instance == null) {
            instance = new MyDownloadManager();
        }
        return instance;
    }

    /** * 添加下载任务 * * @param path */
    public void addDownloadTask(final String path) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                download(path);
            }
        });
    }

    /** * 下载文件 * * @param path */
    private void download(String path) {
        String fileName = AndroidMD5.MD5(path);
        File savePath = new File(downloadDir, fileName); // 下载文件路径
        File finallyPath = new File(downloadDir, fileName + ".mp3"); // 下载完成后加入.mp3后缀
        if (finallyPath.exists()) { // 文件存在则已下载
            Log.i(TAG, "file is existed");
            return;
        }
        if (AndroidNetWorkUtils.isWifiDataEnable(MiaApplication.getInstance())) { // 如果是Wifi则开始下载
            if (savePath.exists() && savePath.delete()) { // 如果之前存在文件,证明没有下载完成,删掉重新创建
                savePath = new File(downloadDir, fileName);
            }
            Log.i(TAG, "download start");
            try {
                byte[] bs = new byte[1024];
                int len;
                URL url = new URL(path);
                InputStream is = url.openStream();
                OutputStream os = new FileOutputStream(savePath);
                while ((len = is.read(bs)) != -1) {
                    os.write(bs, 0, len);
                }
                os.close();
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (savePath.renameTo(finallyPath)) { // 下载完成后重命名为.mp3文件
                Log.i(TAG, "download end");
                EventBus.getDefault().post(new DownloadDoneEvent(path));
            }
        } else { // 不是wifi则不下载
            Log.i(TAG, "not wifi net, stop download");
        }

    }

    /** * 添加删除任务 * * @param path */
    public void addDeleteTask(final String path) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                delete(path);
            }
        });
    }

    /** * 删除本地文件 * * @param path */
    private void delete(String path) {
        String fileName = AndroidMD5.MD5(path);
        File savePath = new File(downloadDir, fileName + ".mp3");
        Log.i(TAG, savePath.getPath());
        if (savePath.exists()) {
            if (savePath.delete()) {
                Log.i(TAG, "file is deleted");
            }
        }
    }

    /** * 返回下载路径 * * @return */
    public File getDownloadDir() {
        return downloadDir;
    }
}

我们看到public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(1, 1, 1,TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);,这句代码便是创建一个线程池。其方法源码及参数说明:

/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default rejected execution handler.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

这里我前三个参数传的都是1,既是最多只有1个线程。sPoolWorkQueue参数则是一个容量为128的任务队列,既最多能存放128个任务。

下面我们看到SerialExecutor的代码,它有一个Runnable队列mTasks ,不断的接受Runnable对象,并通过poll操作,每次取出顶部的Runnable进行执行。结合创建的单一线程池,便实现了我需要的简易、轻量的下载器。

    原文作者:lastwarmth
    原文地址: https://blog.csdn.net/lastwarmth/article/details/50721910
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞