Android 使用service实现多线程断点续传下载

2024-05-25 04:48

本文主要是介绍Android 使用service实现多线程断点续传下载,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文件多线程下载,可暂停、继续下载,app退出再进入可接着下载。

写在前面的话:最近做一个视频下载的需求,所以去研究了service的下载,这儿有人会问,用service有什么好处,我可以用Thread吗?我想说的是service有自己的生命周期,它不依赖于activity的生命周期,如果有需要甚至你的app退出了你的service还可以继续运行,其次一个项目中下载的入口可能有好几个,用service的话可以让多个入口统一到service处来方便管理。如果你使用Thread的话它在一个activity中被创建,那么它的生命周期将很大程度上依赖于这个activity。


遇到的问题:假设我们有3个不同的页面是下载的入口,这3个页面上有可能有相同的视频,如何避免同一个视频被重复下载?大家有没有注意到同一个app(相对大点的,效果才能明显)在360手机助手,51手机助手,百度手机助手,腾讯手机助手等一些列的软件应用上的下载速度是不一样的,有的快有的慢,那么我们如何提高我们视频的下载速度?在使用多个线程下载同一个视频的时候,如何分配每个线程的下载任务,不重复不丢失?线程的创建和销毁都需要花费时间,那么如何提高线程的使用效率?默认开启多少个线程?最多开启多少个线程?假如有三个视频正在下载,用户按2个back键退出app,此时我们如何保存这3个视频的下载进度,如何保存每个视频的几个线程的下载结果,使得下次用户进入下载列表点击下载继续之前的下载结果接着下载?最后一个问题就是如何及时的更新UI,让用户看到下载进度?


下面贴出部分关键代码:

首先再AndroidManifest.xml中加入下载所需要的权限(网络权限和往sd写的权限),其次配置service(用于下载),receive(发送广播,更新ui),AndroidManifest.xml文件部分代码如下:

 <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>。。。省略activity的声明
 <!--  文件下载 --><service android:name="com.download.services.DownloadService"></service><!--  接受广播--><receiverandroid:name="com.download.receive.DownLoadBroadcastReceive"android:exported="true" ><intent-filter><action android:name="com.download.receive.ACTION_UPDATE" /><action android:name="com.download.receive.ACTION_FINISHED" /><action android:name="com.download.receive.ACTION_FINISHED_ALL" /></intent-filter></receiver>
如何避免一个文件被多次下载,可以定义一个全局的list,所有的下载请求都经过service,然后在service中判断即可。那如何提交下载的速度呢?我们可以使用多个线程来下载同一个文件,这儿就用到了Java的RandomAccessFile类,它可以对文件随机写入,我们可以把一个文件分成几块,被不同的线程来处理。我们可以使用线程池来统一管理所有的线程,开的线程的个数一般是cpu核数加1。

使用数据库来保存文件的下载结果

下面是关于数据库的操作,创建2个表,一个线程表,一个文件表,再线程表中记录每个线程的id,文件的id,文件的url,开始下载的位置,下载到哪个位置,下载完成了多少。在文件表中我们记录每个文件的url,name,完成百分比,完成多少字节,文件大小,文件状态等。

数据库sqlite操作工具类如下:

public class DBHelper extends SQLiteOpenHelper
{private static final String DB_NAME = "download.db";private static final int VERSION = 1;private static final String SQL_CREATE = "create table thread_info(_id integer primary key autoincrement," +"thread_id integer, url varchar(512), start integer, end integer, finished integer)";private static final String SQL_DROP = "drop table if exists thread_info";private static final String SQL_CREATE_FILE = "create table file_info(_id integer primary key autoincrement," +"id char(8), url varchar(512), name varchar(512),state char(2), finished integer,length integer)";private static final String SQL_DROP_FILE = "drop table if exists file_info";private static DBHelper sDbHelper = null;private DBHelper(Context context){super(context, DB_NAME, null, VERSION);}public static DBHelper getInstance(Context context){if (null == sDbHelper){sDbHelper = new DBHelper(context);}return sDbHelper;}@Overridepublic void onCreate(SQLiteDatabase db){db.execSQL(SQL_CREATE);db.execSQL(SQL_CREATE_FILE);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){db.execSQL(SQL_DROP);db.execSQL(SQL_CREATE);db.execSQL(SQL_DROP_FILE);db.execSQL(SQL_CREATE_FILE);}
}

threadInfo实体类:

public class ThreadInfo
{private int id;private String url;private int start;private int end;private int finished;public ThreadInfo(){}public ThreadInfo(int id, String url, int start, int end, int finished){this.id = id;this.url = url;this.start = start;this.end = end;this.finished = finished;}//  省略set get方法。。。public String toString(){return "ThreadInfo [id=" + id + ", url=" + url + ", start=" + start+ ", end=" + end + ", finished=" + finished + "]";}}

fileinfo实体类:(防止文章太长,省略一些简单的注释)

public class FileInfo implements Serializable{private static final long serialVersionUID = -732438102358927131L;private String id;private String url;//文件urlprivate String fileName;//文件nameprivate int length;//文件长度private int finished;//完成的百分比private String fileTotal;//文件总大小private String fileFinish;//完成了多少kbprivate String state;//0 正在下载 1 暂停  2 完成private boolean isHaved = false;//是否下载完成private boolean isDownLoading ;//是否正下�?public FileInfo(){}public FileInfo(String id,String url, String fileName){this(id, url, fileName, 0, 0);}public FileInfo(String id,String url, String fileName, int length,int finished){this.id = id;this.url = url;this.fileName = fileName;this.length = length;this.finished = finished;isDownLoading = true;}//  省略 set  get  方法@Overridepublic String toString(){return "FileInfo [id = "+id+",url=" + url + ", fileName=" + fileName+ ", length=" + length + ", finished=" + finished + "]";}}

再写2个文件操作和线程操作的dao类,里面加入增删改的方法。。。


做好这些准备,就来看看service如下下载了,service类的部分代码

	@Overridepublic int onStartCommand(Intent intent, int flags, int startId){// 获得Activity传过来的参数if (ACTION_START.equals(intent.getAction())){//开始下载//用户网络判断/*if("WIFI".equals(NetUtils.getNetworkState().toString())){}else if("MOBILE".equals(NetUtils.getNetworkState().toString())){}else{}*///多个下载的入口,检查文件是否正在下载FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");for(FileInfo f : DownloadTask.fList){if(f.getUrl().equals(fileInfo.getUrl())){Toast.makeText(PhoneApplication.getContext(), "正在下载", Toast.LENGTH_SHORT).show();return super.onStartCommand(intent, flags, startId);}}Toast.makeText(PhoneApplication.getContext(), "开始下载", Toast.LENGTH_SHORT).show();fileInfo.setDownLoading(false);//修改文件的状态Log.i(TAG , "Start:" + fileInfo.toString());new InitThread(fileInfo).start();}else if (ACTION_STOP.equals(intent.getAction())){//暂停下载FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");Log.i(TAG , "Stop:" + fileInfo.toString());fileInfo.setDownLoading(true);//修改文件的状态DownloadTask task = mTasks.get(fileInfo.getUrl());//文件下载的线程类if (task != null){task.removeFile(fileInfo);//防止service网络请求失败,下载列表中存在该数据task.isPause = true;}}return super.onStartCommand(intent, flags, startId);}
再类DownloadTask,使用构造方法来初始化文件和线程

public DownloadTask(Context mContext, FileInfo mFileInfo, int count){this.mContext = mContext;this.mFileInfo = mFileInfo;this.mThreadCount = count;//当个文件下载的线程的个数mDao = new ThreadDAOImpl(mContext);fileDao = new FileDAOImpl(mContext);List<FileInfo> resList = fileDao.getFiles(mFileInfo.getUrl());if(resList.size()==0){//如果文件是刚开始下载,则写入文件信息到数据库mFileInfo.setState("0");fileDao.insertFile(mFileInfo);}else if(resList.get(0).getFinished() == 100){isHaved = true;}}

计算每个线程下载的文件的大小

// 读取数据库的线程信息List<ThreadInfo> threads = mDao.getThreads(mFileInfo.getUrl());ThreadInfo threadInfo = null;if (0 == threads.size()){// 计算每个线程下载长度int len = mFileInfo.getLength() / mThreadCount;for (int i = 0; i < mThreadCount; i++){// 初始化线程信息对象threadInfo = new ThreadInfo(i, mFileInfo.getUrl(),len * i, (i + 1) * len - 1, 0);if (mThreadCount - 1 == i)  // 处理最后一个线程下载长度不能整除的问题{threadInfo.setEnd(mFileInfo.getLength());}// 添加到线程集合中threads.add(threadInfo);mDao.insertThread(threadInfo);}}

接下来就是文件下载,组合,发送广播的代码了, 代码有点多啊,这个就不全写了,会传到csdn

。。。

DownLoadBroadcastReceive接受广播更新ui

public class DownLoadBroadcastReceive extends BroadcastReceiver{private FileDAO fileDao = null;//适配器的对象要是同一个FileListAdapter myAdapter = FileListAdapter.getInstance(PhoneApplication.getContext(), null);@Overridepublic void onReceive(Context context, Intent intent) {fileDao = new FileDAOImpl(context);if (DownloadService.ACTION_UPDATE.equals(intent.getAction())){int finised = intent.getIntExtra("finished", 0);double fileFinish = intent.getDoubleExtra("fileFinish", 0.0);String url = intent.getStringExtra("url");String id = intent.getStringExtra("id");int fileTotal = intent.getIntExtra("fileTotal", 0);myAdapter.updateProgress(finised,(int)fileFinish,fileTotal,url,id);Log.i("mReceiver","-finised = " + finised);}else if (DownloadService.ACTION_STOP.equals(intent.getAction())){// 下载停止int finised = intent.getIntExtra("finished", 0);double fileFinish = intent.getDoubleExtra("fileFinish", 0.0);String url = intent.getStringExtra("url");String id = intent.getStringExtra("id");int fileTotal = intent.getIntExtra("fileTotal", 0);myAdapter.updateProgress(finised,(int)fileFinish,fileTotal,url,id);Log.i("mReceiver","-finised = " + finised);}else if (DownloadService.ACTION_FINISHED.equals(intent.getAction())){// 下载结束FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");fileDao.updateFile(fileInfo.getUrl(),  100,"2");myAdapter.updateProgress( 100,fileInfo.getLength(),fileInfo.getLength(),fileInfo.getUrl(),fileInfo.getId());Toast.makeText(context, fileInfo.getFileName() + "下载完毕", Toast.LENGTH_SHORT).show();}}
}


完整代码已传到csdn,欢迎大家交流

点击下载

这篇关于Android 使用service实现多线程断点续传下载的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1000519

相关文章

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

Python中注释使用方法举例详解

《Python中注释使用方法举例详解》在Python编程语言中注释是必不可少的一部分,它有助于提高代码的可读性和维护性,:本文主要介绍Python中注释使用方法的相关资料,需要的朋友可以参考下... 目录一、前言二、什么是注释?示例:三、单行注释语法:以 China编程# 开头,后面的内容为注释内容示例:示例:四

java实现docker镜像上传到harbor仓库的方式

《java实现docker镜像上传到harbor仓库的方式》:本文主要介绍java实现docker镜像上传到harbor仓库的方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 前 言2. 编写工具类2.1 引入依赖包2.2 使用当前服务器的docker环境推送镜像2.2

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Java easyExcel实现导入多sheet的Excel

《JavaeasyExcel实现导入多sheet的Excel》这篇文章主要为大家详细介绍了如何使用JavaeasyExcel实现导入多sheet的Excel,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录1.官网2.Excel样式3.代码1.官网easyExcel官网2.Excel样式3.代码

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁

python实现对数据公钥加密与私钥解密

《python实现对数据公钥加密与私钥解密》这篇文章主要为大家详细介绍了如何使用python实现对数据公钥加密与私钥解密,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录公钥私钥的生成使用公钥加密使用私钥解密公钥私钥的生成这一部分,使用python生成公钥与私钥,然后保存在两个文