现在的位置: 首页 > 综合 > 正文

多线程断点续传后台下载

2013年10月24日 ⁄ 综合 ⁄ 共 5006字 ⁄ 字号 评论关闭

http://www.apkbus.com/android-60374-1-1.html

本菜鸟最近在做一个小项目,项目中用到了多线程断点续传的功能,因为是菜鸟嘛,所以在网络上找了很多教程,不过大多教程只给出了源码,注释跟说明实在太少,也许大牛们不需要太多的解释,可是就苦了小菜们了。本着技术的分享,文化的传承,今天本小菜就给大家详细的解释一下多线程断点续传。(吹大了。。。)由于是小菜,难免有很多地方理解不够深刻或者有出现错误的地方,就请各位大大们继续补充,完善。

废话不多说,先看看实现的效果图:
    1.jpg

2012-7-22 22:01 上传

下载附件(38.59 KB)

2.jpg

2012-7-22 22:01 上传

下载附件(32.93 KB)

3.jpg

2012-7-22 22:01 上传

下载附件(40.08 KB)

看到效果图以后,相信大家对本项目有了一个大概的了解。由于是在本机上测试的,所以小菜我在本地用tomcat架设了一个服务器,如何在本地架设服务器这个很简单,大家谷歌吧,这里我把服务端的图给贴出来。
服务端.jpg

2012-7-22 22:03 上传

下载附件(107.57 KB)

服务端就4个音频文件,与网络音频那个界面的1.mp3等对应。只所以取这个名字是因为方便,如果大家应用到自己的项目中,可以改成相应的名字,不过有可能要处理乱码的问题。
  既然是多线程断点续传,那么肯定要用到2个技术。
1.      多线程技术
2.      断点续传技术
多线程技术的实现有很多种方法,小菜今天采用的是直接new Thread的方法。这个相信大家已经很熟悉了。
断点续传技术是小菜我一开始很头疼的问题,因为小菜根本就不知道该如何实现断点续传。小菜在看过《Http协议&断点续传实例》的3小时视频以后终于对断点续传有了大概的了解,如果有时间大家可以去看看这个视频,虽然视频中的代码最后还有很多不完善,但是对断地续传技术的讲解还是很透彻的。如果大家没有时间,那么继续看完本文,我相信大家也会明白多线程断地续传是如何实现的。说起来总是没那么形象,那么看图吧,小菜我根据图来为大家讲解。
断点续传.jpg

2012-7-22 22:01 上传

下载附件(189.4 KB)

在上图我们可以看到,手机客户端开启了3个线程去下载网络服务端的一个名叫1.mp3文件的数据。在单线程下载中,由1个线程从数据的开头一直下载的结尾。那么在多线程中—-本文为3个线程,是如何分配每个线程下载的长度呢?上图中1.MP3文件总长度为3435603字节,我们使用3个线程下载,3435603/3=1145201byte也就是说,每个线程只要下载长度为1145201byte就可以把1.mp3文件下载完整。有童鞋就要问了,你这例子举的太巧了,1.mp3文件正好能被3整除,如果不能整除该怎么办啊?这个问题问的很好,那么我们举一个不能整除的例子。
  例如一个文件总大小为4,721,096byte.4721096/3=1573698.666666…这就是一个不整除的例子,那么遇到这种情况怎么办呢?其实只需要让01号线程下载长度为1573698byte,而最后的2号线程从1号线程的结束直接下载到文件的结尾(4721096)就行了。具体怎么实现,待会具体看代码就知道了。
  好了,现在知道每个线程该下载多长了,但是我怎么才能让线程听我的话,去网络上下载这个长度,又怎么能指定线程的下载位置呢?
  这时候我们要用到http头中的一个range字段。那么什么是range呢?Range,是在 HTTP/1.1(http://www.w3.org/Protocols/rfc2616/rfc2616.html)里新增的一个 header field,也是现在众多号称多线程下载工具(如
FlashGet、迅雷等)实现多线程下载的核心所在。简单的说只要利用了range字段来和服务器交互就能实现文件的多线程下载了。
Range : 用于客户端到服务器端的请求,可通过该字段指定下载文件的某一段大小,及其单位。典型的格式
Range: bytes=0-499    下载第0-499字节范围的内容
Range: bytes=500-999  下载第500-999字节范围的内容
Range: bytes=-500     下载最后500字节的内容
Range: bytes=500-     下载从第500字节开始到文件结束部分的内容
Range: bytes=0-0,-1   下载第一以及最后一个字节的内容
本示例中就使用了Range:bytes x-y这种格式。
  现在知道了,通过range就可以让我们在网络上下载数据时利用多线程下载数据的指定位置和指定长度,那么我们在本地存储器中存储数据时是否也可以采用多线程存储呢?答案是肯定的,而且我们的存储还跟从网络上获取数据的位置长度一模一样,怎么说呢?也就是0号线程从网络上下载多少数据我就保存在存储器中,1号,2号线程也是雷同。那网络上可以采用range实现,在本地存储器存储我们采用什么方式呢?这时候就要用到java.io包中的RandomAccessFile这个类,并通过这个类的seek方法指定从哪个位置开始写入。
  现在我们指定如何用多线程在网络上获取数据,在存储器上存储数据了。那么说说断点续传吧,断点续传就是说,本次下载由于各种原因中断,下载再进行下载时还能接着上次中断的地方进行下载,而不必重新进行下载。那么我们如何知道上次中断时的位置呢?这就需要我们在上次中断时把下载的位置保存起来,在android中提供了很多方法保存数据,这里小菜采用的是使用SQLite数据库保存。
  好了,多线程断点续传的概念基本上介绍完了,那么就要开始本次项目的讲解了,喂喂。。。大家醒醒,表打瞌睡噢。
咱们先看看项目的整体框架。
框架.jpg

2012-7-22 22:04 上传

下载附件(290.6 KB)

框架1.jpg

2012-7-22 22:04 上传

下载附件(125.84 KB)

大体框架大家也看到了,我也做了概括性的注释。我们现在来看看数据库的设计吧。数据库的设计,我觉得自己设计的非常差,存在很多冗余,但是没办法,谁叫我是小菜呢?希望有大牛以后能改进一下。我还是先贴图,然后进行讲解,再贴代码。
database-1.jpg

2012-7-22 22:01 上传

下载附件(125.05 KB)

那,这就是咱们的数据库了,数据库名字叫down.db,其中创建了2个表:
1、  localdown_info:
music_name:音乐名称
completeSize:已经下载文件长度
fileSize:要下载文件总大小
state:下载的状态:0为已经下载,1为下载未完成
2、  download_info:
thread_id:线程id
start_pos:线程从哪个位置开始下载
end_pos:线程下载到哪个位置
complete_size:线程完成的长度
现在先介绍一下这2个表的作用吧。
Localdown_info表:是用来在本地下载的前台显示用的,这个表有一个对应的类叫FileState。FileState中有localdown_info中的每一个字段,然后提供了set,get方法。在LocalDownActivity中有一个List<FilteState>属性,每次启动LocalDownActivity的时候,就会从数据库读取数据,保存到这个List里面,然后这个List的内容会显示到ListView上面。
Download_info表:这个表是用来存放每一个item下载时所开启的线程,如图所示,我下载了一个1.mp3的文件,在localdown_info中我们看见state为1表示这个1.mp3下载未完成(有可能是暂停,也有可能是正在下载中),从Download_info表中我们可以看到下载1.mp3这个文件开启的3个线程,已经这3个线程的信息。这个表也是实现断点续传的关键,如果出现下载中断,再下一次下载时会从这个表中读取信息,然后再继续下载。
现在我们对数据库的架构有了大致的了解。接下来我将对项目中的数据结构进行分析,本来想1个类1个类的进行分析,后来仔细看了看源码发现没必要,在代码的关键位置我都写了比较详细的注解,相信大家一下就能明白,如果有不明白的可以单独再问我,写长了有骗稿费的嫌疑。呵呵~~
  在DownloaderService这个类中有一个类变量
   4.jpg

2012-7-22 22:06 上传

下载附件(9.83 KB)

  在Downloader这个类中有一个成员变量
5.jpg

2012-7-22 22:06 上传

下载附件(3.05 KB)

在Downloader这个类中还有3个静态常量
6.jpg

2012-7-22 22:06 上传

下载附件(9.97 KB)

它们之间有什么着什么样的联系呢?请看下图:
数据结构.jpg

2012-7-22 22:01 上传

下载附件(165.4 KB)

在DownloaderService中的Map<String,Downloader> downloader是一下载器的集合,存放着所有的下载任务。而每一个Downloader下载器都有一个List<DownloaderInfo>infos的成员变量,可以看出这个List中存放的都是DownloaderInfo,这个DownloaderInfo是什么呢?就是用于存放每条线程的下载信息的类,之所以每个List<DownloaderInfo>有3个DownloaderInfo是因为我们只开了3条线程下载。并且每个downloader下载器都存在着3种不同的状态,分别是初始化,正在下载,暂停下载。看明白上图很重要,否则看代码时会被绕晕的噢。
至于Range的设置是比较简单的,我这里只讲解一下思路,具体的看我提供的源码吧。首先我们建立一个URL对象URL url=new URL(downPath);把下载地址传进去。
        然后通过url对象建立一个HttpURLConnection连接。HttpURLConnection conn= (HttpURLConnection)url.openConnection();得到HttpURLConnection对象以后我们就可以使用它的setRequestProperty方法就可以设置range了,conn.setRequestProperty(“Range”,”bytes=”+startPos+”-”+endPos);这样就设置好Range啦,是不是很简单呢?
          接下来我们就讲讲如何去更新下载进度条了,实现方法很多,小菜我这里就采用很傻很天真的一种方式。要想讲清楚本项目中是如何更新界面的,还需借助下图的帮忙:
更新界面.jpg
2012-7-22 22:01 上传

下载附件(166.12 KB)

从图中我们可以很清楚都看见UI的更新流程,首先在Downloader的内部类MyThread类中一边下载下载数据,一边把已经下载好的数据大小通过Handler发送给DonwloadService类,在DownloaderService类中对数据进行处理以后再利用广播创送到LocalDownActivity类中,LocalDownActivity的内部广播接收器接受到广播后,理解更新list列表,然后把list列表传入adapter中,在利用notifyDataSetChanged()方法去调用adapter中的getView()方法,在getView()里进行界面的更新。这就是整个项目更新界面的流程。
  这里提到了LocalDownActivity中有个List<FileState>列表,这个列表存放都就是我们要显示的界面,所以在接收到更新后,我们必须去更新这个list列表,list列表里每一项又是一个FileState,那么FileState是什么呢?一个FileState就对应与localdown_info表中的一条记录。

  好了,本项目介绍的差不多了,关键点我基本上也都列出来了,其他的详细步骤请看我提供的源代码。小菜第1次发帖,有很多不足的地方,希望大家多多包涵。可能项目里有很多2B的做法也请大家原谅,毕竟我是小菜嘛。如果有什么不懂的欢迎加我QQ:121880399,注明:安卓巴士。也期望有大牛能够加我,给我指点,不管怎样,你的回复就是对我最大的鼓励,请大家放心大胆的开骂吧。哈哈哈。。。

抱歉!评论已关闭.