Android实现网络下载二(多任务下载--支持断点续传)

Android实现网络下载二(多任务下载–支持断点续传)

上文中说了单任务的断点续传,这篇文章就说说多任务下载,不啰嗦了,直接进入正题。

附上demo源码,GitHub代码后续上传,这里的链接还是csdn的。

点这里下载源码,快,戳我戳我…

q:486789970
email:[email protected]

下图是一个多任务下载的动态图:

效果图如下(单任务下载在上篇文章)https://blog.csdn.net/qq_35840038/article/details/90239354

在上篇博客基础上,新增了一个ListView用来显示多条下载内容,这个很简单,这里就不多说啦
在这里插入图片描述
.上面的效果图就是多任务下载(使用线程池管理),支持断点续传、实时进度更新、下载暂停、下载继续,下载完成自动安装等功能;同时包括网络下载请求和本地文件的存储。

实现原理

  • 首先通过Service里面的代码获得下载文件的长度,然后设置本地文件的长度
  • 根据文件长度和线程数计算每条线程下载的数据长度和下载位置(这里用了三条线程,当然也可改成让用户输入线程数)
  • 文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M

例如10M大小,使用3个线程来下载

具体的流程在上篇文章中说过了,多任务的只是修改了一点东西而已。直接看修改的代码:

Service

package com.cc.downloaddemo.services;

import android.app.Service;
import android.content.Intent;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.annotation.Nullable;
import com.cc.downloaddemo.info.FileInfo;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 下载service
 */
public class DownloadService extends Service {

    //通过URL地址下载apk文件并保存在本地存储路径
    public static final String DOWNLOAD_PATH =
            Environment.getExternalStorageDirectory().getAbsolutePath() +
                    "/downloads/";

    //开始下载
    public static final String ACTION_START = "ACTION_START";

    //暂停下载
    public static final String ACTION_STOP = "ACTION_STOP";

    //结束下载
    public static final String ACTION_FINISH = "ACTION_FINISH";

    //更新下载进度
    public static final String ACTION_UPDATE = "ACTION_UPDATE";

    //定义handler的flag
    public static final int MSG_INIT = 0;

    //异步下载任务集合(设置为map集合,查找方便一些)
    private Map<Integer, DownloadTask> taskMap = new LinkedHashMap<>();

    /**
     * 执行下载、暂停、继续下载
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //获取从Activity传过去的参数,判断intent的值。
        if(ACTION_START.equals(intent.getAction())){
            FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
            //启动初始化子线程,联网下载文件内容。
            InitThread initThread = new InitThread(fileInfo);
            //使用线程池来管理
            DownloadTask.executorService.execute(initThread);
        } else if (ACTION_STOP.equals(intent.getAction())) {
            //暂停下载时,更改下载任务类的flag便会自动暂停。
            FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
            //从集合中取出下载任务
            DownloadTask task = taskMap.get(fileInfo.getId());
            //非空处理
            if(task != null){
                //停止下载任务
                task.ispause = true;
            }
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * 网络内容下载完成后
     * 通过handler启动异步任务类开始正式下载文件。
     */
    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //是否为初始化消息
            if(msg.what == MSG_INIT){
                //获取发回来的信息
                FileInfo fileInfo = (FileInfo) msg.obj;
                //启动一个下载任务,将文件内容传递过去。
                //一个任务让三个线程去下载
                DownloadTask downloadTask = new DownloadTask(DownloadService.this, fileInfo, 3);
                //启动下载方法
                downloadTask.download();
                //把下载任务添加到集合中
                taskMap.put(fileInfo.getId(), downloadTask);
            }
        }
    };

    /**
     * 定义子线程用来下载
     */
    class InitThread extends Thread{

        //定义一个文件用来接收下载信息
        private FileInfo mFileInfo = null;

        //启动线程时传入的对象
        public InitThread(FileInfo mFileInfo) {
            this.mFileInfo = mFileInfo;
        }

        @Override
        public void run() {
            //定义conn
            HttpURLConnection conn = null;
            //定义文件内容访问类
            RandomAccessFile raf = null;
            try {
                //连接网络文件
                URL url = new URL(mFileInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setReadTimeout(3000);
                conn.setRequestMethod("GET");

                //自定义一个长度,注意尽量用long(处理下载消息进度百分比时好计算)
                long length = -1;
                //下载完成
                if(conn.getResponseCode() == HttpURLConnection.HTTP_OK ){
                    //获取文件总长度赋值给length
                    length = conn.getContentLength();
                }
                //长度不能小于0
                if(length <= 0){
                    return;
                }
                //判断该路径存在与否
                File dir = new File(DOWNLOAD_PATH);
                //文件不存在时创建
                if(!dir.exists()){
                    dir.mkdir();
                }
                //本地创建一个文件
                File file = new File(dir, mFileInfo.getFilename());
                //输出流
                raf = new RandomAccessFile(file, "rwd");
                //设置本地的文件长度(等于下载文件的总长度。杜绝浪费资源)
                raf.setLength(length);
                //赋值给定义的对象长度
                mFileInfo.setLength(length);
                //启动flag,将文件发送给handler处理
                handler.obtainMessage(MSG_INIT, mFileInfo).sendToTarget();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                try {
                    //关闭资源
                    raf.close();
                    conn.disconnect();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }

}

注:使用了线程池统一管理

DownloadTask:

package com.cc.downloaddemo.services;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import com.cc.downloaddemo.db.dao.ThreadDao;
import com.cc.downloaddemo.db.impl.ThreadDaoImpl;
import com.cc.downloaddemo.info.FileInfo;
import com.cc.downloaddemo.info.ThreadInfo;
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 java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 任务下载类
 * @author
 */
public class DownloadTask {

    //上下文
    private Context context;

    //定义数据库线程对象文件
    private FileInfo fileInfo;

    //实例化接口
    public static ThreadDao threadDao = null;

    //定义finished(当前已下载的长度)
    private int finished = 0;

    //默认直接下载
    public boolean ispause = false;

    //默认线程数量
    private int threadCount = 1;

    //定义线程集合
    private List<DownloadThread> threadList;

    //定义线程池
    public static ExecutorService executorService = Executors.newCachedThreadPool();

    /**
     * 构造方法
     * @param context
     * @param fileInfo
     */
    public DownloadTask(Context context, FileInfo fileInfo, int threadCount) {
        this.context = context;
        //拿到handler传过来的文件
        this.fileInfo = fileInfo;
        //拿到线程数量
        this.threadCount = threadCount;

        //实例化接口实现类
        threadDao = new ThreadDaoImpl(context);
    }

    /**
     * 下载方法
     */
    public void download(){
        //下载任务一开始,先查询数据库,看是否有文件在等待下载。
        //读取数据库的所有线程信息
        List<ThreadInfo> threads = threadDao.getThreads(fileInfo.getUrl());
        //当list的size为0时,说明没有等待下载的线程,为1时则有。
        if(threads.size() == 0){
            //获得每一个文件
            int length = (int) (fileInfo.getLength() / threadCount);
            for (int i = 0; i < threadCount; i++){
                //创建线程信息
                ThreadInfo threadInfo = new ThreadInfo(i, fileInfo.getUrl()
                        , length * i, (i + 1) * length - 1, 0);
                //当i是最后一个线程时,设置一个索引
                if(i == threadCount - 1){
                    threadInfo.setEnd((int) fileInfo.getLength());
                }
                //添加到线程信息集合中
                threads.add(threadInfo);
                threadDao.insertThread(threadInfo);
            }
        }
        threadList = new ArrayList<>();
        //启动多个线程进行下载
        for (ThreadInfo info : threads){
            DownloadThread downloadThread = new DownloadThread(info);
//            downloadThread.start();
            DownloadTask.executorService.execute(downloadThread);
            //添加线程到集合中
            threadList.add(downloadThread);
        }
    }

    /**
     * 判断是否所有线程都执行完毕
     * 保证同一时间段只有一个线程访问此方法
     */
    private synchronized void checkAddThreadFinished(){
        //假设全部完成啦
        boolean allFinished = true;

        //遍历集合
        for (DownloadThread thread : threadList){
            if(!thread.isFinished){
                allFinished = false;
                break;
            }
        }
        if(allFinished){
            //下载完成,删除数据库所保存的线程信息
            threadDao.deleteThread(fileInfo.getUrl());
            //发送广播通知,消灾任务结束
            Intent intent = new Intent(DownloadService.ACTION_FINISH);
            intent.putExtra("fileInfo", fileInfo);
            context.sendBroadcast(intent);
        }
    }

    /**
     * 下载线程
     */
    class DownloadThread extends Thread{

        //继续定义一个临时线程对象
        private ThreadInfo threadInfo;

        //标记线程是否结束
        public boolean isFinished = false;

        //构造
        public DownloadThread(ThreadInfo threadInfo) {
            this.threadInfo = threadInfo;
        }

        @Override
        public void run() {
            //定义conn、输入流和文件内容访问类
            HttpURLConnection conn = null;
            InputStream inputStream = null;
            RandomAccessFile raf = null;

            try{
                //开始联网下载
                URL url = new URL(threadInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setReadTimeout(3000);
                conn.setRequestMethod("GET");

                //设置下载位置(通过当前开始值和现在值判断下载位置)
                int start = threadInfo.getStart() + threadInfo.getFinished();
                conn.setRequestProperty("Range", "bytes=" + start + "-" + threadInfo.getEnd());
                //设置文件写入位置
                File file = new File(DownloadService.DOWNLOAD_PATH, fileInfo.getFilename());
                raf = new RandomAccessFile(file, "rwd");
                raf.seek(start);
                //定义一个广播,用来更新下载进度
                Intent intent = new Intent(DownloadService.ACTION_UPDATE);

                //
                finished += threadInfo.getFinished();
                //开始下载
                if(conn.getResponseCode() == HttpURLConnection.HTTP_PARTIAL){
                    //读取数据
                    inputStream = conn.getInputStream();
                    byte[] buffer = new byte[1024 * 4];
                    int len = -1;
                    long time = System.currentTimeMillis();
                    while ((len = inputStream.read(buffer)) != -1){
                        //写入文件
                        raf.write(buffer, 0, len);
                        //累加整个文件的下载完成进度
                        finished += len;
                        //累加每个线程完成的进度
                        threadInfo.setFinished(threadInfo.getFinished() + len);
                        //间隔500毫秒更新一下进度
                        if(System.currentTimeMillis() - time > 1500){
                            time = System.currentTimeMillis();
                            intent.putExtra("finished", (int)(finished / (float)fileInfo.getLength() * 100));
                            intent.putExtra("id", fileInfo.getId());
                            context.sendBroadcast(intent);
                        }
                        //在下载暂停时,保存下载进度
                        if(ispause){
                            threadDao.updateThread(threadInfo.getUrl(), threadInfo.getId(), threadInfo.getFinished());
                            return;
                        }
                    }
                    intent.putExtra("finished", 100);
                    context.sendBroadcast(intent);
                    //标识线程执行完毕
                    isFinished = true;
                    //执行完之后检查
                    checkAddThreadFinished();
                    openFile(file);
                }
            }catch (Exception e){
                e.printStackTrace();;
            }finally {
                conn.disconnect();
                try {
                    inputStream.close();
                    raf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 自动安装
     * @param file
     */
    private void openFile(File file) {
        // TODO Auto-generated method stub
        Log.e("OpenFile", file.getName());
        Intent intent = new Intent();
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(android.content.Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.fromFile(file),
                "application/vnd.android.package-archive");
        context.startActivity(intent);
    }

}

附上demo源码,GitHub代码后续上传,这里的链接还是csdn的。

点这里下载源码,快,戳我戳我…

q:486789970
email:[email protected]

如果有什么问题,欢迎大家指导。并相互联系,希望能够通过文章互相学习。

												---财财亲笔

猜你喜欢

转载自blog.csdn.net/qq_35840038/article/details/90241757