Android多线程下载断点续传

2023-11-09

先上图看卡结果:
GITHUB:Android多线程下载断点续传
下载杵这儿
这里写图片描述
如图所示点击下载就开始下载,点击停止就会停止再次点击下载就会接着下载了。
设计思路是这样的:
首先通过广播将下载信息传递给DownService,DownService根据文件URL获取文件大小,再通过DownTask将下载任务分配,并且通过广播当点击停止下载时将下载进度保存在数据库中,当点击开始下载时再从数据库中获取到保存的进度,继续下载。
代码结构:
这里写图片描述
核心类是 DownLoadService,java 和DownTask.java将这两个类贴出来:

package com.example.downloaddemo.services;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.http.HttpStatus;
import com.example.downloaddemo.enties.FileInfo;
import android.app.Service;
import android.content.Intent;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;

public class DownLoadService extends Service {
    // 设置存储路劲
    public static final String DOWN_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";
    public static final int MSG_INIT = 0;
    // private DownLoadTask mDownLoadTask=null;
    private Map<Integer, DownLoadTask> mTasks = new LinkedHashMap<Integer, DownLoadTask>();
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 获取Activity传来的数据
        if (ACTION_START.equals(intent.getAction())) {
            FileInfo fileinfo = (FileInfo) intent
                    .getSerializableExtra("fileinfo");
            InitThread minitThread=new InitThread(fileinfo);
            DownLoadTask.sExecutorService.execute(minitThread);
            Log.i("test", "start:" + fileinfo.toString());
        } else if (ACTION_STOP.equals(intent.getAction())) {
FileInfo fileinfo = (FileInfo) intent
    .getSerializableExtra("fileinfo");
            // 从集合中获取下载任务
    DownLoadTask task = mTasks.get(fileinfo.getId());
            if (task != null) {
                // 停止下载任务
                task.isPause = true;
            }
            Log.i("test", "stop:" + fileinfo.toString());
        }
        return super.onStartCommand(intent, flags, startId);
    }
    @Override
    public IBinder onBind(Intent arg0) {
        // TODO Auto-generated method stub
        return null;
    }
    Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
            case MSG_INIT:
            FileInfo info = (FileInfo) msg.obj;
            Log.i("test", "init:" + info.toString());
        // 开启下载任务,默认为三个线程下载
        DownLoadTask task = new DownLoadTask(DownLoadService.this,info, 3);
        task.downLoad();
        // 把下载任务添加到集合中
        mTasks.put(info.getId(), task);
        break;
        default:
        break;
            }
        }
    };
    /**
     * 
     * 初始化子线程
     */
    class InitThread extends Thread {
        private FileInfo mfileInfo = null;
        public InitThread(FileInfo mfileInfo) {
            super();
            this.mfileInfo = mfileInfo;
        }
        @Override
        public void run() {
            HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            try {
                // 连接网络文件
                URL url = new URL(mfileInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(3000);
                conn.setRequestMethod("GET");
                int length = -1;
                // 获取文件长度
    if (conn.getResponseCode() == HttpStatus.SC_OK) {
                    length = conn.getContentLength();
                }
                if (length <= 0) {
                    return;
                }
                File dir = new File(DOWN_PATH);
                if (!dir.exists()) {
                    dir.mkdir();
                }
                // 在本地文件并设置长度
    File file = new File(dir,mfileInfo.getFileName());
                // 特殊的输出流能够 任任意位置写入
                raf = new RandomAccessFile(file,"rwd");
                // 设置本地文件的长度
                raf.setLength(length);
                mfileInfo.setLength(length);
                mHandler.obtainMessage(MSG_INIT, mfileInfo).sendToTarget();
            } catch (Exception e) {
                // TODO: handle exception
            } finally {
                try {
                    // 关闭流操作和网络连接操作
                    raf.close();
                    conn.disconnect();
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            super.run();
        }

    }

}

下面是DownLoadTask.java的代码:

package com.example.downloaddemo.services;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.http.HttpStatus;
import com.example.downloaddemo.db.ThreadDAO;
import com.example.downloaddemo.db.ThreadDAOImpl;
import com.example.downloaddemo.enties.FileInfo;
import com.example.downloaddemo.enties.ThreadInfo;
import android.content.Context;
import android.content.Intent;
/**
 * 
 * 下载任务
 * 
 */
public class DownLoadTask {
    private Context mContext = null;
    private FileInfo mFileInfo = null;
    private ThreadDAO mDao = null;
    private int mfinished = 0;
    public boolean isPause = false;
    private int threadCount = 1;// 线程数量
    private List<DownLoad> mThreadList = null;// 线程集合方便管理分段下载线程
//使用带缓存型池子,先查看池中有没有以前建立的线程,如果有,
//就reuse.如果没有,就建一个新的线程加入池中
//缓存型池子通常用于执行一些生存期很短的异步型任务,
//能reuse的线程,必须是timeout IDLE内的池中线程,
//缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。
    public static ExecutorService sExecutorService=Executors.newCachedThreadPool();
    public DownLoadTask(Context mContext, FileInfo mFileInfo, int threadCount) {
        super();
        this.mContext = mContext;
        this.mFileInfo = mFileInfo;
        mDao = new ThreadDAOImpl(mContext);
    }
    public void downLoad() {
        // 读取数据库的线程信息
List<ThreadInfo> mThreadInfos =    mDao.getThreads(mFileInfo.getUrl());
        if (mThreadInfos.size() == 0) {
            // 获取每个线程下载的长度
            int length = mFileInfo.getLength() / threadCount;
            // 创建线程下载信息
            for (int i = 0; i < threadCount; i++) {
            ThreadInfo threadInfo = new ThreadInfo(i, mFileInfo.getUrl(), i* length, (i + 1) * length, 0);
                if (i == threadCount - 1) {
                    threadInfo.setEnd(mFileInfo.getLength());
                }
                // 添加到线程信息集合中
                mThreadInfos.add(threadInfo);
                // 向数据库中插入线程信息
                mDao.insertThread(threadInfo);
            }
        }
        mThreadList = new ArrayList<DownLoadTask.DownLoad>();
        // 启动多个线程来下载
        for (ThreadInfo info : mThreadInfos) {
            DownLoad download = new DownLoad(info);
    DownLoadTask.sExecutorService.execute(download);
            mThreadList.add(download);
        }
    }
    /**
     * 判断下载线程是否都下载完毕
     */
    private synchronized void checkAllThreadsFinished() {
        boolean allFinished = true;
        for (DownLoad download : mThreadList) {
            if (!download.isfinished) {
            allFinished = false;
            break;
            }
        }
        if (allFinished) {
            // 下载完毕删除线程信息
            mDao.deleteThread(mFileInfo.getUrl());
            // 发送广播到Activity
            Intent intent = new Intent(DownLoadService.ACTION_FINISH);
            intent.putExtra("fileInfo", mFileInfo);
            mContext.sendBroadcast(intent);
        }
    }
    class DownLoad extends Thread {
        private ThreadInfo mThreadInfo = null;
        public boolean isfinished = false;// 表示线程是否下载完毕
        public DownLoad(ThreadInfo mThreadInfo) {
            super();
            this.mThreadInfo = mThreadInfo;
        }
        @Override
        public void run() {
            // 打开连接
            HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            InputStream ins = null;
            try {
                URL url = new URL(mThreadInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(3000);
                conn.setRequestMethod("GET");
                int start = mThreadInfo.getStart() + mThreadInfo.getFinished();
                // 设置下载位置
                conn.setRequestProperty("Range",
                        "bytes=" + "-" + mThreadInfo.getEnd());
                // 设置文件写入位置
                File file = new File(DownLoadService.DOWN_PATH,
                        mFileInfo.getFileName());

                raf = new RandomAccessFile(file, "rwd");
                //移动到指定位置
                raf.seek(start);
                Intent intent = new Intent(DownLoadService.ACTION_UPDATE);
                mfinished += mThreadInfo.getFinished();
                // 开始下载
                if (conn.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT) {
                    // 读取数据
                    ins = conn.getInputStream();
                    byte[] buffer = new byte[1024 * 4];
                    int length = -1;
            long time = System.currentTimeMillis();
            while ((length = ins.read(buffer)) != -1) {
                        // 写入文件
                        raf.write(buffer, 0, length);
                        // 把下载进度发送广播更新UI
                        // 累加整文件完成进度
                        mfinished += length;
                // 累加每个线程完成的进度mThreadInfo.setFinished(mThreadInfo.getFinished()+ length);
        if (System.currentTimeMillis() - time > 1000) {
    time = System.currentTimeMillis();
    intent.putExtra("finished", mfinished * 100
    / mFileInfo.getLength());
    intent.putExtra("id", mFileInfo.getId());
        mContext.sendBroadcast(intent);
    }
        // 下载暂停保存进度
    if (isPause) {
                            mDao.updateThread(mThreadInfo.getUrl(),
                                    mThreadInfo.getId(),
                                    mThreadInfo.getFinished());
                return;
            }
}
                    isfinished = true;
                    // 检查下载任务是否完成
                    checkAllThreadsFinished();
                }

            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } finally {
                try {
                    conn.disconnect();
                    ins.close();
                    raf.close();
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            super.run();
        }
    }

}

没写之前我在想多线程下载之后如何下载下来的刷数据拼接在一起,后来查看JavaAPI之后使用了用Java的RandomAccessFile操作就可以写入到指定的位,DownLoadService获取长度后,就在存储的位置创建该文件。

HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            try {
                // 连接网络文件
                URL url = new URL(mfileInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(3000);
                conn.setRequestMethod("GET");
                int length = -1;
                // 获取文件长度
                if (conn.getResponseCode() == HttpStatus.SC_OK) {
                    length = conn.getContentLength();
                }
                if (length <= 0) {
                    return;
                }
                File dir = new File(DOWN_PATH);
                if (!dir.exists()) {
                    dir.mkdir();
                }
                // 在本地文件并设置长度
                File file = new File(dir, mfileInfo.getFileName());
                // 特殊的输出流能够 任任意位置写入
                raf = new RandomAccessFile(file, "rwd");
                // 设置本地文件的长度
                raf.setLength(length);
                mfileInfo.setLength(length);
                mHandler.obtainMessage(MSG_INIT, mfileInfo).sendToTarget();

在写入文件时:不同的线程获取对应的数据写在对应的文件位置就可以了不存在拼接问题。举个栗子:
这里写图片描述
T1下载0–5;T2下载6—-10;T3下载11—-15,就可以啦下载完写到对应得位置即可。
在写入的时候设置么每写入 byte[1024 * 4],通知UI更新进度条,并进行总的进度累加,下载完后在判断多个线程下载一个文件是不是都下载完成了,如果下载完成了就通知弹出下载完成提示。
核心代码如下:

// 打开连接
            HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            InputStream ins = null;
            try {
                URL url = new URL(mThreadInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(3000);
                conn.setRequestMethod("GET");
                int start = mThreadInfo.getStart() + mThreadInfo.getFinished();
                // 设置下载位置
                conn.setRequestProperty("Range",
                        "bytes=" + "-" + mThreadInfo.getEnd());
                // 设置文件写入位置
                File file = new File(DownLoadService.DOWN_PATH,
                        mFileInfo.getFileName());
                raf = new RandomAccessFile(file, "rwd");
                //移动到指定位置
                raf.seek(start);
                Intent intent = new Intent(DownLoadService.ACTION_UPDATE);
                mfinished += mThreadInfo.getFinished();
                // 开始下载
                if (conn.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT) {
                    // 读取数据
                    ins = conn.getInputStream();
                    byte[] buffer = new byte[1024 * 4];
                    int length = -1;
                    long time = System.currentTimeMillis();
                    while ((length = ins.read(buffer)) != -1) {
                        // 写入文件
                        raf.write(buffer, 0, length);
                        // 把下载进度发送广播更新UI
                        // 累加整文件完成进度
                        mfinished += length;
                        // 累加每个线程完成的进度
                        mThreadInfo.setFinished(mThreadInfo.getFinished()
                                + length);
                        if (System.currentTimeMillis() - time > 1000) {
                            time = System.currentTimeMillis();
                            intent.putExtra("finished", mfinished * 100
                                    / mFileInfo.getLength());
                            intent.putExtra("id", mFileInfo.getId());
                            mContext.sendBroadcast(intent);
                        }
                        // 下载暂停保存进度
                        if (isPause) {
                            mDao.updateThread(mThreadInfo.getUrl(),
                                    mThreadInfo.getId(),
                                    mThreadInfo.getFinished());
                            return;
                        }
                    }
                    isfinished = true;
                    // 检查下载任务是否完成
                    checkAllThreadsFinished();

线程通过使用线程池来管理
ExecutorService sExecutorService=Executors.newCachedThreadPool();
//使用带缓存型池子,先查看池中有没有以前建立的线程,如果有,就reuse.如果没有,就建一个新的线程加入池中
//缓存型池子通常用于执行一些生存期很短的异步型任务,能reuse的线程,必须是timeout IDLE内的池中线程,缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。
GITHUB:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android多线程下载断点续传 的相关文章

随机推荐

  • 【实践3】Python pandas读取Excel指定单元格 / 在指定单元格插入数据,不改变Excel格式

    简单介绍 有时会遇到只需将爬取的数据填入指定的单元格 而不需要更改Excel格式的情况 或是将一个Excel指定单元格内容复制后插入另一个Excel的单元格 完整代码 import pandas as pd from openpyxl im
  • Cmake常用命令(二)

    本文主要介绍File关键字 它是文件系统相关的操作的入口 读文件 命令 格式 解释 示例 READ file READ
  • 初探支付对账

    大家好 我是老三 好久不见 最近比较忙碌 状态也不是太好 很久没有输出 最近在做对账系统的调研和设计 给大家分享一些对账系统的知识 什么是对账 有个男人叫小帅 娶了个老婆 叫小美 早上 小美给小帅二十块钱买早餐 小帅买了包子 油条 豆浆回来
  • Golang jwt跨域鉴权

    Golang jwt跨域鉴权 JWT全称JSON Web Token是一种跨域认证解决方案 属于一个开放的标准 它规定了一种Token实现方式 目前多用于前后端分离项目和OAuth2 0 安装jwt go get github com dg
  • java中集合框架汇总

    Java 集合框架主要包括两种类型的容器 一种是集合 Collection 存储一个元素集合 另一种是图 Map 存储键 值对映射 Collection 接口又有 3 种子类型 List Set 和 Queue 再下面是一些抽象类 最后是具
  • 【Shell编程】字符截取命令cut、printf命令

    目录 cut命令 功能 语法 参数 实例 测试文本 提取所有行的姓名 提取所有行的姓名和评分 分割文本 提取 etc passwd中用户的用户名 cut命令的局限 printf命令 功能 语法 参数 输出类型 输出格式 实例 以字符串格式输
  • message.h

    文章目录 message h 概述 objc super objc msgSend objc msgSendSuper objc msgSend stret objc msgSendSuper stret objc msgSend fpre
  • java--基础--14--File

    java 基础 14 File 1 介绍 用于操作文件和目录 文件夹 1 1 构造方法 File String pathname 根据一个路径得到File对象 File String parent String child 根据一个目录pa
  • 【Lua】不进位保留小数点X位数

    游戏需求常常因为数值太大 需要简化显示 例XX XX亿 XX XX万 lua在对两个整数进行除法操作时不会向C 那样将结果转换成整数 而是自动转换成浮点数 所以当我们保留小数使用string format 2f str 的时候 会自动完成四
  • 去掉小数点后面的0(javascript)

  • HTML导航菜单

    frameset html 文件
  • Docker运行gin项目(go mod)

    准备 先在本地把golang的docker镜像拉取下来 docker pull golang Dockerfile文件内容 在gin项目根目录下创建Dockerfile配置文件 指定基础镜像 FROM golang 维护人信息 MAINTA
  • 【TCN回归预测】基于TCN时间卷积神经网络实现数据多输入回归预测附matlab代码

    作者简介 热爱科研的Matlab仿真开发者 修心和技术同步精进 matlab项目合作可私信 个人主页 Matlab科研工作室 个人信条 格物致知 更多Matlab仿真内容点击 智能优化算法 神经网络预测 雷达通信 无线传感器 信号处理 图像
  • strapi的使用(三)-- 上传图片

    1 建表添加媒体字段 2 前端请求格式 用axios举例 需要注意几点 第一 传文件时 参数前要加前缀files 比如我表里面的媒体文件字段名为img 前端需要传的参数就为files img 第二 传除文件外的其他参数时 需要其他参数包裹在
  • 运用遍历判断无向图(网)是否连通

    基本思路 使用广度优先遍历方法 任选一个结点开始遍历 遍历结束后每个结点都访问到了即为连通 主代码 判断是否连通的isConnected 方法在最末尾 import dataStructure linearList LinkQueue pu
  • Win11安装适用于 Linux 的 Windows 子系统 (WSL)

    一 什么是适用于 Linux 的 Windows 子系统 官方解释 适用于 Linux 的 Windows 子系统可让开发人员按原样运行 GNU Linux 环境 包括大多数命令行工具 实用工具和应用程序 且不会产生传统虚拟机或双启动设置开
  • 疫情当下,我们如何正确的看待nft数字藏品

    我是一个做传统家居行业的老板 从2019年底开始 国内疫情的反复蔓延 导致家居生意越来越难 线上线下都很难活下去 公司从一开始的裁员 到最后的关门 中间经历了很多挫折 还记得公司在关门的那一刻 手底下员工对我讲期待我东山再起的那一刻 当时数
  • 蓝桥杯2021年第十二届真题第一场-时间显示

    题目 题目链接 题解 就是考查取模运算 我是FW 居然用了牛刀 我是FW啊 而且我居然以为一秒等于一百毫秒 因为时 分 秒 毫秒之间的换算关系是不随着年月日的不同而变化的 所以直接整除就可以了 可以理解为时分之间为60进制进位关系 分秒之间
  • 未启用windows无线服务器,win10未启用对服务器的远程

    win10未启用对服务器的远程 内容精选 换一换 Windows Server 2012 R2操作系统弹性云服务器 本地使用远程桌面连接功能连接云服务器并启用redirected drive功能时 云服务器出现蓝屏 远程桌面连接启用了red
  • Android多线程下载断点续传

    先上图看卡结果 GITHUB Android多线程下载断点续传 下载杵这儿 如图所示点击下载就开始下载 点击停止就会停止再次点击下载就会接着下载了 设计思路是这样的 首先通过广播将下载信息传递给DownService DownService