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

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

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

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

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

q:486789970
email:mr.cai_cai@foxmail.com

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

效果图如下(单任务下载在上篇文章)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:mr.cai_cai@foxmail.com

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

												---财财亲笔
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 24
    评论
实现多文件的多线程下载需要以下步骤: 1. 创建一个下载任务队列,存放所有需要下载的文件信息,包括文件名、文件大小、下载链接等。 2. 为每个下载任务创建一个下载线程,并将下载任务分配给下载线程。 3. 下载线程使用多线程的方式下载文件,可以根据文件大小划分多个线程,每个线程负责下载文件的一部分数据。 4. 在下载过程中,需要记录已经下载的字节数和下载进度,以便于断点。 5. 如果下载过程中出现网络异常或者用户暂停了下载,需要保存已下载的数据,以便于下次继下载。 6. 如果用户需要暂停或取消下载,需要停止所有下载线程,并删除已下载的数据。 以下是一个简单的多文件多线程下载的示例代码: 1. 创建一个下载任务类,用于存储文件信息和下载状态 ```java public class DownloadTask { public String fileName; public String url; public long fileSize; public int status; public long progress; // ...其他属性和方法 } ``` 2. 创建一个下载线程类,用于下载指定的文件 ```java public class DownloadThread extends Thread { private String url; private long start; private long end; private RandomAccessFile raf; private DownloadListener listener; public DownloadThread(String url, long start, long end, RandomAccessFile raf, DownloadListener listener) { this.url = url; this.start = start; this.end = end; this.raf = raf; this.listener = listener; } @Override public void run() { HttpURLConnection conn = null; InputStream is = null; byte[] buffer = new byte[1024]; int len; try { URL url = new URL(this.url); conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setRequestProperty("Range", "bytes=" + start + "-" + end); is = conn.getInputStream(); while ((len = is.read(buffer)) != -1) { raf.write(buffer, 0, len); listener.onProgress(len); } listener.onComplete(); } catch (Exception e) { listener.onError(e); } finally { try { if (is != null) { is.close(); } if (conn != null) { conn.disconnect(); } } catch (Exception e) { e.printStackTrace(); } } } } ``` 3. 创建一个下载管理类,用于管理所有下载任务下载线程 ```java public class DownloadManager { private static final int MAX_THREAD_COUNT = 3; // 最大下载线程数 private static final String DOWNLOAD_DIR = "/sdcard/download/"; // 下载文件保存目录 private List<DownloadTask> taskList; private List<DownloadThread> threadList; private DownloadListener listener; public DownloadManager() { taskList = new ArrayList<>(); threadList = new ArrayList<>(); } // 添加下载任务 public void addTask(String url, String fileName, long fileSize) { DownloadTask task = new DownloadTask(); task.url = url; task.fileName = fileName; task.fileSize = fileSize; task.status = DownloadTask.STATUS_PENDING; taskList.add(task); if (listener != null) { listener.onTaskAdded(task); } } // 开始下载任务 public void startTask(DownloadTask task) { if (task.status == DownloadTask.STATUS_DOWNLOADING) { return; } task.status = DownloadTask.STATUS_DOWNLOADING; File file = new File(DOWNLOAD_DIR + task.fileName); long downloadedSize = file.exists() ? file.length() : 0; task.progress = downloadedSize; long blockSize = (task.fileSize + MAX_THREAD_COUNT - 1) / MAX_THREAD_COUNT; for (int i = 0; i < MAX_THREAD_COUNT; i++) { long start = i * blockSize + downloadedSize; long end = (i == MAX_THREAD_COUNT - 1) ? task.fileSize - 1 : (i + 1) * blockSize - 1; try { RandomAccessFile raf = new RandomAccessFile(file, "rw"); raf.seek(start); DownloadThread thread = new DownloadThread(task.url, start, end, raf, new DownloadListener() { @Override public void onProgress(int len) { synchronized (task) { task.progress += len; if (listener != null) { listener.onProgress(task); } } } @Override public void onComplete() { synchronized (task) { boolean allThreadsComplete = true; for (DownloadThread thread : threadList) { if (thread.getState() != State.TERMINATED) { allThreadsComplete = false; break; } } if (allThreadsComplete) { task.status = DownloadTask.STATUS_COMPLETE; if (listener != null) { listener.onComplete(task); } } } } @Override public void onError(Exception e) { synchronized (task) { task.status = DownloadTask.STATUS_ERROR; if (listener != null) { listener.onError(task, e); } } } }); threadList.add(thread); thread.start(); } catch (Exception e) { e.printStackTrace(); } } } // 暂停下载任务 public void pauseTask(DownloadTask task) { task.status = DownloadTask.STATUS_PAUSED; for (DownloadThread thread : threadList) { thread.interrupt(); } threadList.clear(); if (listener != null) { listener.onPause(task); } } // 取消下载任务 public void cancelTask(DownloadTask task) { task.status = DownloadTask.STATUS_CANCELED; File file = new File(DOWNLOAD_DIR + task.fileName); if (file.exists()) { file.delete(); } for (DownloadThread thread : threadList) { thread.interrupt(); } threadList.clear(); if (listener != null) { listener.onCancel(task); } } // 设置下载监听器 public void setDownloadListener(DownloadListener listener) { this.listener = listener; } public interface DownloadListener { void onTaskAdded(DownloadTask task); void onProgress(DownloadTask task); void onComplete(DownloadTask task); void onPause(DownloadTask task); void onCancel(DownloadTask task); void onError(DownloadTask task, Exception e); } } ``` 4. 在Activity中调用下载管理类,实现文件下载功能 ```java public class DownloadActivity extends AppCompatActivity implements DownloadManager.DownloadListener { private DownloadManager downloadManager; private ListView listView; private DownloadListAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_download); downloadManager = new DownloadManager(); downloadManager.setDownloadListener(this); listView = (ListView) findViewById(R.id.list_view); adapter = new DownloadListAdapter(this, downloadManager.getTaskList()); listView.setAdapter(adapter); } // 添加新的下载任务 public void addTask(String url, String fileName, long fileSize) { downloadManager.addTask(url, fileName, fileSize); } // 开始下载任务 public void startTask(DownloadTask task) { downloadManager.startTask(task); } // 暂停下载任务 public void pauseTask(DownloadTask task) { downloadManager.pauseTask(task); } // 取消下载任务 public void cancelTask(DownloadTask task) { downloadManager.cancelTask(task); } @Override public void onTaskAdded(DownloadTask task) { adapter.notifyDataSetChanged(); } @Override public void onProgress(DownloadTask task) { adapter.notifyDataSetChanged(); } @Override public void onComplete(DownloadTask task) { adapter.notifyDataSetChanged(); } @Override public void onPause(DownloadTask task) { adapter.notifyDataSetChanged(); } @Override public void onCancel(DownloadTask task) { adapter.notifyDataSetChanged(); } @Override public void onError(DownloadTask task, Exception e) { adapter.notifyDataSetChanged(); } } ``` 以上代码仅作为示例,实际项目中还需要考虑一些异常情况,例如网络异常、存储空间不足等。同时应该注意线程同步和异常处理,以确保下载任务的正确性和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谁抢我的小口口

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值