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

原创 2016年06月01日 23:25:00

文件多线程下载,可暂停、继续下载,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>
        <!--  接受广播-->
        <receiver
            android: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;
	}
	@Override
	public void onCreate(SQLiteDatabase db)
	{
		db.execSQL(SQL_CREATE);
		db.execSQL(SQL_CREATE_FILE);
	}
	@Override
	public 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;//文件url
	private String fileName;//文件name
	private int length;//文件长度
	private int finished;//完成的百分比
	private String fileTotal;//文件总大小
	private String fileFinish;//完成了多少kb
	private 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  方法
	@Override
	public String toString()
	{
		return "FileInfo [id = "+id+",url=" + url + ", fileName=" + fileName
				+ ", length=" + length + ", finished=" + finished + "]";
	}
	
}

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


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

	@Override
	public 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);
	@Override
	public 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之——多线程断点续传下载示例

在上一篇博文中,我们讲解了如何实现Android的多线程下载功能,通过将整个文件分成多个数据块,开启多个线程,让每个线程分别下载一个相应的数据块来实现多线程下载的功能。多线程下载中,可以将下载这个耗时...
  • l1028386804
  • l1028386804
  • 2015年07月15日 21:02
  • 2903

Android-Service系列之断点续传下载

PS:本文代码来自慕课网同名课程Android-Service系列之断点续传下载,这里只是为了Mark下来方便自己以后使用。 首先是布局文件,因为只是demo,所以使用图形化工具生成。 ...
  • u013983998
  • u013983998
  • 2015年06月03日 23:33
  • 1173

多线程下载文件和断点续传的简单实现

断点续传的要点: 每写入一次文件就将这次写入文件的位置保存起来(保存到文件中),下次读取文件的的位置开始下载public class MuchThreadDown { p...
  • wo_ha
  • wo_ha
  • 2016年07月24日 21:05
  • 2813

Aandroid 多线程断点续传同时下载多个大文件

Aandroid 多线程断点续传同时下载多个大文件Aandroid 多线程断点续传同时下载多个大文件最近学习在Android环境中一些网络请求方面的知识,其中有一部分是关于网络下载方面的知识。在这里解...
  • luoshishou
  • luoshishou
  • 2016年05月21日 23:06
  • 4383

多线程下载和断点续传和进度条

public class MainActivity extends Activity { protected final int DOWNLOAD_ERROR = 1; private fin...
  • lilengfan1991
  • lilengfan1991
  • 2015年07月02日 11:12
  • 252

使用xUtils框架实现多线程下载和断点续传

在前面的博客中分别介绍了在Java中实现多线程下载和断点续传以及在android中实现多线程下载和断点续传,这篇博客将介绍使用第三方框架xUtils实现多线程下载和断点续传 首先看一下实现的效果,点...
  • u010105970
  • u010105970
  • 2016年04月23日 17:26
  • 2780

多线程断点下载(开始下载,暂停下载,百分比进度条)

package cn.itcast.download;      import java.io.File;   import java.io.FileInputStream;   import...
  • scott2017
  • scott2017
  • 2016年02月27日 18:42
  • 342

关于“多线程断点续传下载”功能的一个简单实现和讲解

转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992     上班第一天,在技术群里面和大家闲扯,无意中谈到了关于框架的使用,一个同学说为了用xUtils的断线...
  • wanglha
  • wanglha
  • 2015年03月13日 12:45
  • 934

android 多文件多线程断点续传下载

今天跟大家一起分享下android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2次总结中已经为大家分享过有关技术的一些基...
  • taa1007
  • taa1007
  • 2017年11月22日 19:06
  • 82

Android_Service多线程断点下载

Android中实现单线程下载文件是比较容易的,可是要使得自己的应用支持多线程断点下载就要考虑到很多细节了,今天我们一起来探讨一下多线程断点下载时怎么实现的。 首先先画一张图说明一下Androi...
  • GePriniusce
  • GePriniusce
  • 2016年07月21日 17:25
  • 993
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android 使用service实现多线程断点续传下载
举报原因:
原因补充:

(最多只允许输入30个字)