文件下载断点续传的意思是在文件下载中途可以多次暂停下载,然后又可以从刚才暂停的位置继续读取数据完成下载任务,并且最终下载下来的文件是与服务器上的资源是完整一致的,不会受损。由此可以确定,实现此功能需要掌握如下功能点:
1、开启网络链接,下载数据,并且在下载过程中可以随时中断。
2、开启网络链接后可以从任意位置开始读取服务器资源数据。
3、可以创建本地文件,并且可以从文件的任意位置开始写入数据。
下面本章将逐一实现以上三个功能点。
一、设计下载任务进度类DownloadBean
每个下载任务都有其相应的属性,静态的不变化的属性有:下载资源名称、下载链接、资源字节数,在下载进度中一直在变化的属性有:已经下载的字节数、下载状态、标记是否允许下载的布尔变量。其中,下载状态有:正在下载数据、下载暂停中、数据下载完成、数据下载失败四种状态。由此,可设计下载任务进度类DownloadBean如下:
01 | package com.devdiv.ch12.downloadbreak; |
02 | |
03 | /** |
04 | * @author Sodino E-mail:sodinoopen@hotmail.com |
05 | * @version Time:2011-6-8 下午11:33:10 |
06 | */ |
07 | public class DownloadBean { |
08 | /** 正在下载数据。Button应显示“暂停”。 */ |
09 | public static final int STATE_LOADING = 0 ; |
10 | /** 数据全部下载完成。Button应显示“安装”。 */ |
11 | public static final int STATE_COMPLETED = 1 ; |
12 | /** 数据下载过程中被暂停。Button应显示“继续”。 */ |
13 | public static final int STATE_INTERRUPTED = 2 ; |
14 | /** 下载安装包失败。Button应显示“失败”。 */ |
15 | public static final int STATE_DOWNLOAD_FAIL = 3 ; |
16 | |
17 | |
18 | /** 下载资源名称 */ |
19 | public String name; |
20 | /** 下载链接 */ |
21 | public String url; |
22 | /** 资源字节数 */ |
23 | public long size; |
24 | /** 已经下载的字节数 */ |
25 | public long loadedSize; |
26 | /** 下载状态 */ |
27 | public int state; |
28 | /** 标记是否允许下载的boolean变量 */ |
29 | public boolean enable; |
30 | } |
二、创建下载线程并可随时中断线程
由于开启网络访问链接及下载资源是一个相当耗时的操作,为了防止主线程无响应异常(ANR),应该将这种耗时操作单独开启线程执行。
在下载过程中,是否继续读取数据是由DownloadBean的boolean变量enable来决定的。由于用户的干预需要中断下载时,可将enable设置为false,让线程从读取数据循环中跳出,即可实现中断下载。
伪代码如下:
01 | class DownloadThread extends Thread { |
02 | public void run() { |
03 | // 访问资源链接 |
04 | openConnection(); |
05 | // 循环读取资源数据 |
06 | while (downloadBean.enable){ |
07 | inputStream.read(); |
08 | outputStream.write(); |
09 | } |
10 | // 跳出循环,通知更新界面 |
11 | sendMessage(REFRESH_UI); |
12 | } |
13 | } |
这样一来,小节开头所要求的第一个功能点:开启网络链接,下载数据,并且在下载过程中可以随时中断,就此实现。
三、设置网络链接读取资源的有效范围
假设网络资源已经下载了前面的500字节,用户恢复下载后,应用程序应该发送一个什么样的请求报文才能够保证请求的文件是从第501字节开始读取,而不会从头开始下载呢?
根据HTTP1.1规范,可以使用Range头域。Range头域可以请求实体的一个或者多个子范围。Range的值为0表示第一个字节,也就是Range计算字节数是从0开始的。
以下为Range在各种不同需求下的设置值:
表示头500个字节:Range: bytes=0-499
表示第二个500字节:Range: bytes=500-999
表示最后500个字节:Range: bytes=-500
表示500字节以后的范围:Range: bytes=500-
第一个和最后一个字节:Range: bytes=0-0,-1
同时指定几个范围:Range: bytes=500-600,601-999
因此,如果应用程序想直接从第501个字节读取,所发起的HTTP Get请求报文中,应该有一组<Key,Value>,Key值是"Range",Value则是"bytes=500-"。所获取到的RespondCode值为206;获取到的头信息中有一组<Key,Value>为"Content-Range: bytes 500-123455/123456"。其中,由于字节的计数是从0开始的,所以返回的"500"表示从第501个字节的数据开始,一直读取到第文件结尾的第123455个字节,"/"后面的"123456"表示该资源占用123456字节。
开启链接并设置Range头域的代码如下:
1 | HttpURLConnection httpConn = openUrl(context, downloadBean.url); |
2 | // 设置读取数据范围,从上一次未下载的字节开始读取直到资源的最后一个字节 |
3 | httpConn.setRequestProperty( "Range" , "bytes=" + downloadBean.loadedSize + "-" + (downloadBean.size - 1 )); |
4 | // 返回值为206,即:HttpURLConnection.HTTP_PARTIAL |
5 | int respondCode = connect(httpConn); |
由此,小节开头所要求的第二个功能点:开启网络链接后可以从任意位置开始读取服务器资源数据,到此实现。
四、使用RandomAccessFile随机读写文件
RandomAccessFile支持对随机存取文件的读取和写入。随机存取文件的行为类似存储在文件系统中的一个大型字节数组。存在指向该数组的光标或索引,称为文件指针。
输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机存取文件以读取/写入模式创建,则输出操作也可用。输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。
写入该数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer() 方法读取,并通过 seek() 方法设置。
生成一个RandomAccessFile需要指定文件索引及读写模式。读写模式包括以下四种:
“r”——以只读方式打开。调用结果对象的任何write方法都将导致抛出 IOException。
“rw”——打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
“rws”——打开以便读取和写入,与"rw"相似,但要求对文件的内容或元数据的每个更新都同步写入到基础存储设备。
“rwd”——打开以便读取和写入,与"rw"相似,但要求对文件内容的每个更新都同步写入到基础存储设备。
1 | RandomAccessFile randomFile = new RandomAccessFile(file, "rw" ); |
2 | randomFile.setLength(downloadBean.size); |
当从HttpURLConnection的数据输入流读取到数据后,数据应该从之前已经读完后的字节位置开始写入,此时需要移动文件指针,然后再读写数据。当数据写入后,应该将DownloadBean中的属性loadedSize加上刚刚写入的数据量.代码如下:
1 | randomFile.seek(downloadBean.loadedSize); |
2 | randomFile.write(data, 0 , readLength); |
3 | downloadBean.loadedSize += readLength; |
由此,小节开头提前的功能点:可以创建本地文件,并且可以从文件的任意位置开始写入数据,由此实现。
最后需要注意的是,需要在manifest.xml文件中进行相应权限的设置,本例中需要这只的权限如下:
1 | < uses-permission android:name = "android.permission.INTERNET" /> |
2 | < uses-permission android:name = "android.permission.READ_PHONE_STATE" /> |
3 | < uses-permission android:name = "android.permission.WRITE_EXTERNAL_STORAGE" /> |
4 | < uses-permission android:name = "android.permission.ACCESS_NETWORK_STATE" /> |
http://www.devdiv.com/Android-_-thread-128979-1-1.html
今天学习了Android开发中比较难的一个环节,就是断点续传下载,很多人看到这个标题就感觉头大,的确,如果没有良好的逻辑思维,这块的确很难搞明白。下面我就将自己学到的知识和一些见解写下供那些在这个环节还烦恼的人参考。这里我以下载mp3文件为例。
断点续传下载,顾名思义,那就是我们在一次下载未结束时,退出下载,第二次下载时会接着第一次下载的进度继续下载。那么怎么记录第一次下载的数据呢,这里肯定就要用到数据库了。下面就是我创建数据库的一个SQLiteOpenHelper类。用来首次运行时创建数据库。
DBHelper类:
1 package cn.yj3g.DBHelper;
2
3 import android.content.Context;
4 import android.database.sqlite.SQLiteDatabase;
5 import android.database.sqlite.SQLiteOpenHelper;
6
7 /**
8 * 建立一个数据库帮助类
9 */
10 public class DBHelper extends SQLiteOpenHelper {
11 //download.db-->数据库名
12 public DBHelper(Context context) {
13 super(context, "download.db", null, 1);
14 }
15
16 /**
17 * 在download.db数据库下创建一个download_info表存储下载信息
18 */
19 @Override
20 public void onCreate(SQLiteDatabase db) {
21 db.execSQL("create table download_info(_id integer PRIMARY KEY AUTOINCREMENT, thread_id integer, "
22 + "start_pos integer, end_pos integer, compelete_size integer,url char)");
23 }
24 @Override
25 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
26
27 }
28
29 }
下面来看主界面的布局,在这里,我只设计了一个ListView来显示下载的音乐的名称,和一个开始下载按钮和一个暂停按钮。
布局文件如下:
main.xml:
1 <?xml version="1.0" encoding="utf-8"?>
2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:orientation="vertical"
4 android:layout_width="fill_parent"
5 android:layout_height="fill_parent"
6 android:id="@+id/llRoot">
7 <ListView android:id="@android:id/list"
8 android:layout_width="fill_parent"
9 android:layout_height="fill_parent">
10 </ListView>
11 </LinearLayout>
list_item.xml:
1 <?xml version="1.0" encoding="utf-8"?>
2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:orientation="vertical"
4 android:layout_width="fill_parent"
5 android:layout_height="wrap_content">
6 <LinearLayout
7 android:orientation="horizontal"
8 android:layout_width="fill_parent"
9 android:layout_height="wrap_content"
10 android:layout_marginBottom="5dip">
11 <TextView
12 android:layout_width="fill_parent"
13 android:layout_height="wrap_content"
14 android:layout_weight="1"
15 android:id="@+id/tv_resouce_name"/>
16 <Button
17 android:layout_width="fill_parent"
18 android:layout_height="wrap_content"
19 android:layout_weight="1"
20 android:text="下载"
21 android:id="@+id/btn_start"
22 android:onClick="startDownload"/>
23 <Button
24 android:layout_width="fill_parent"
25 android:layout_height="wrap_content"
26 android:layout_weight="1"
27 android:text="暂停"
28 android:id="@+id/btn_pause"
29 android:onClick="pauseDownload"/>
30 </LinearLayout>
31 </LinearLayout>
主界面运行效果如下:
下面我们来看具体实现下载的方法。首先,我们要定义一个记录在下载时各个时期的数据的类,这里我创建了一个DownloadInfo类来记录。代码如下:
DownloadInfo:
1 package cn.yj3g.entity;
2 /**
3 *创建一个下载信息的实体类
4 */
5 public class DownloadInfo {
6 private int threadId;//下载器id
7 private int startPos;//开始点
8 private int endPos;//结束点
9 private int compeleteSize;//完成度
10 private String url;//下载器网络标识
11 public DownloadInfo(int threadId, int startPos, int endPos,
12 int compeleteSize,String url) {
13 this.threadId = threadId;
14 this.startPos = startPos;
15 this.endPos = endPos;
16 this.compeleteSize = compeleteSize;
17 this.url=url;
18 }
19 public DownloadInfo() {
20 }
21 public String getUrl() {
22 return url;
23 }
24 public void setUrl(String url) {
25 this.url = url;
26 }
27 public int getThreadId() {
28 return threadId;
29 }
30 public void setThreadId(int threadId) {
31 this.threadId = threadId;
32 }
33 public int getStartPos() {
34 return startPos;
35 }
36 public void setStartPos(int startPos) {
37 this.startPos = startPos;
38 }
39 public int getEndPos() {
40 return endPos;
41 }
42 public void setEndPos(int endPos) {
43 this.endPos = endPos;
44 }
45 public int getCompeleteSize() {
46 return compeleteSize;
47 }
48 public void setCompeleteSize(int compeleteSize) {
49 this.compeleteSize = compeleteSize;
50 }
51
52 @Override
53 public String toString() {
54 return "DownloadInfo [threadId=" + threadId
55 + ", startPos=" + startPos + ", endPos=" + endPos
56 + ", compeleteSize=" + compeleteSize +"]";
57 }
58 }
在下载时,我们有进度条来显示进度,怎么确定进度条的进度,大小和起始位置呢?这里我定义了一个LoadInfo类来记录下载器详细信息。代码如下:
LoadInfo:
1 package cn.yj3g.entity;
2 /**
3 *自定义的一个记载下载器详细信息的类
4 */
5 public class LoadInfo {
6 public int fileSize;//文件大小
7 private int complete;//完成度
8 private String urlstring;//下载器标识
9 public LoadInfo(int fileSize, int complete, String urlstring) {
10 this.fileSize = fileSize;
11 this.complete = complete;
12 this.urlstring = urlstring;
13 }
14 public LoadInfo() {
15 }
16 public int getFileSize() {
17 return fileSize;
18 }
19 public void setFileSize(int fileSize) {
20 this.fileSize = fileSize;
21 }
22 public int getComplete() {
23 return complete;
24 }
25 public void setComplete(int complete) {
26 this.complete = complete;
27 }
28 public String getUrlstring() {
29 return urlstring;
30 }
31 public void setUrlstring(String urlstring) {
32 this.urlstring = urlstring;
33 }
34 @Override
35 public String toString() {
36 return "LoadInfo [fileSize=" + fileSize + ", complete=" + complete
37 + ", urlstring=" + urlstring + "]";
38 }
39 }
下面是最最重要的一步,那就是定义一个下载器来进行下载了,这里我就不多说,具体解释在代码中都有注解,我就直接将代码附下,供大家研究参考
Downloader:
1 package cn.yj3g.service;
2
3 import java.io.File;
4 import java.io.InputStream;
5 import java.io.RandomAccessFile;
6 import java.net.HttpURLConnection;
7 import java.net.URL;
8 import java.util.ArrayList;
9 import java.util.List;
10 import android.content.Context;
11 import android.os.Handler;
12 import android.os.Message;
13 import android.util.Log;
14 import cn.yj3g.Dao.Dao;
15 import cn.yj3g.entity.DownloadInfo;
16 import cn.yj3g.entity.LoadInfo;
17
18 public class Downloader {
19 private String urlstr;// 下载的地址
20 private String localfile;// 保存路径
21 private int threadcount;// 线程数
22 private Handler mHandler;// 消息处理器
23 private Dao dao;// 工具类
24 private int fileSize;// 所要下载的文件的大小
25 private List<DownloadInfo> infos;// 存放下载信息类的集合
26 private static final int INIT = 1;//定义三种下载的状态:初始化状态,正在下载状态,暂停状态
27 private static final int DOWNLOADING = 2;
28 private static final int PAUSE = 3;
29 private int state = INIT;
30
31 public Downloader(String urlstr, String localfile, int threadcount,
32 Context context, Handler mHandler) {
33 this.urlstr = urlstr;
34 this.localfile = localfile;
35 this.threadcount = threadcount;
36 this.mHandler = mHandler;
37 dao = new Dao(context);
38 }
39 /**
40 *判断是否正在下载
41 */
42 public boolean isdownloading() {
43 return state == DOWNLOADING;
44 }
45 /**
46 * 得到downloader里的信息
47 * 首先进行判断是否是第一次下载,如果是第一次就要进行初始化,并将下载器的信息保存到数据库中
48 * 如果不是第一次下载,那就要从数据库中读出之前下载的信息(起始位置,结束为止,文件大小等),并将下载信息返回给下载器
49 */
50 public LoadInfo getDownloaderInfors() {
51 if (isFirst(urlstr)) {
52 Log.v("TAG", "isFirst");
53 init();
54 int range = fileSize / threadcount;
55 infos = new ArrayList<DownloadInfo>();
56 for (int i = 0; i < threadcount - 1; i++) {
57 DownloadInfo info = new DownloadInfo(i, i * range, (i + 1)* range - 1, 0, urlstr);
58 infos.add(info);
59 }
60 DownloadInfo info = new DownloadInfo(threadcount - 1,(threadcount - 1) * range, fileSize - 1, 0, urlstr);
61 infos.add(info);
62 //保存infos中的数据到数据库
63 dao.saveInfos(infos);
64 //创建一个LoadInfo对象记载下载器的具体信息
65 LoadInfo loadInfo = new LoadInfo(fileSize, 0, urlstr);
66 return loadInfo;
67 } else {
68 //得到数据库中已有的urlstr的下载器的具体信息
69 infos = dao.getInfos(urlstr);
70 Log.v("TAG", "not isFirst size=" + infos.size());
71 int size = 0;
72 int compeleteSize = 0;
73 for (DownloadInfo info : infos) {
74 compeleteSize += info.getCompeleteSize();
75 size += info.getEndPos() - info.getStartPos() + 1;
76 }
77 return new LoadInfo(size, compeleteSize, urlstr);
78 }
79 }
80
81 /**
82 * 初始化
83 */
84 private void init() {
85 try {
86 URL url = new URL(urlstr);
87 HttpURLConnection connection = (HttpURLConnection) url.openConnection();
88 connection.setConnectTimeout(5000);
89 connection.setRequestMethod("GET");
90 fileSize = connection.getContentLength();
91
92 File file = new File(localfile);
93 if (!file.exists()) {
94 file.createNewFile();
95 }
96 // 本地访问文件
97 RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");
98 accessFile.setLength(fileSize);
99 accessFile.close();
100 connection.disconnect();
101 } catch (Exception e) {
102 e.printStackTrace();
103 }
104 }
105
106 /**
107 * 判断是否是第一次 下载
108 */
109 private boolean isFirst(String urlstr) {
110 return dao.isHasInfors(urlstr);
111 }
112
113 /**
114 * 利用线程开始下载数据
115 */
116 public void download() {
117 if (infos != null) {
118 if (state == DOWNLOADING)
119 return;
120 state = DOWNLOADING;
121 for (DownloadInfo info : infos) {
122 new MyThread(info.getThreadId(), info.getStartPos(),
123 info.getEndPos(), info.getCompeleteSize(),
124 info.getUrl()).start();
125 }
126 }
127 }
128
129 public class MyThread extends Thread {
130 private int threadId;
131 private int startPos;
132 private int endPos;
133 private int compeleteSize;
134 private String urlstr;
135
136 public MyThread(int threadId, int startPos, int endPos,
137 int compeleteSize, String urlstr) {
138 this.threadId = threadId;
139 this.startPos = startPos;
140 this.endPos = endPos;
141 this.compeleteSize = compeleteSize;
142 this.urlstr = urlstr;
143 }
144 @Override
145 public void run() {
146 HttpURLConnection connection = null;
147 RandomAccessFile randomAccessFile = null;
148 InputStream is = null;
149 try {
150 URL url = new URL(urlstr);
151 connection = (HttpURLConnection) url.openConnection();
152 connection.setConnectTimeout(5000);
153 connection.setRequestMethod("GET");
154 // 设置范围,格式为Range:bytes x-y;
155 connection.setRequestProperty("Range", "bytes="+(startPos + compeleteSize) + "-" + endPos);
156
157 randomAccessFile = new RandomAccessFile(localfile, "rwd");
158 randomAccessFile.seek(startPos + compeleteSize);
159 // 将要下载的文件写到保存在保存路径下的文件中
160 is = connection.getInputStream();
161 byte[] buffer = new byte[4096];
162 int length = -1;
163 while ((length = is.read(buffer)) != -1) {
164 randomAccessFile.write(buffer, 0, length);
165 compeleteSize += length;
166 // 更新数据库中的下载信息
167 dao.updataInfos(threadId, compeleteSize, urlstr);
168 // 用消息将下载信息传给进度条,对进度条进行更新
169 Message message = Message.obtain();
170 message.what = 1;
171 message.obj = urlstr;
172 message.arg1 = length;
173 mHandler.sendMessage(message);
174 if (state == PAUSE) {
175 return;
176 }
177 }
178 } catch (Exception e) {
179 e.printStackTrace();
180 } finally {
181 try {
182 is.close();
183 randomAccessFile.close();
184 connection.disconnect();
185 dao.closeDb();
186 } catch (Exception e) {
187 e.printStackTrace();
188 }
189 }
190
191 }
192 }
193 //删除数据库中urlstr对应的下载器信息
194 public void delete(String urlstr) {
195 dao.delete(urlstr);
196 }
197 //设置暂停
198 public void pause() {
199 state = PAUSE;
200 }
201 //重置下载状态
202 public void reset() {
203 state = INIT;
204 }
205 }
在这边下载器类的定义中,我们用到了许多关于进行数据库操作的方法,这里我定义了一个数据库工具类,来提供这些方法,代码如下:
Dao:
1 package cn.yj3g.Dao;
2
3 import java.util.ArrayList;
4 import java.util.List;
5 import android.content.Context;
6 import android.database.Cursor;
7 import android.database.sqlite.SQLiteDatabase;
8 import cn.yj3g.DBHelper.DBHelper;
9 import cn.yj3g.entity.DownloadInfo;
10
11 /**
12 *
13 * 一个业务类
14 */
15 public class Dao {
16 private DBHelper dbHelper;
17
18 public Dao(Context context) {
19 dbHelper = new DBHelper(context);
20 }
21
22 /**
23 * 查看数据库中是否有数据
24 */
25 public boolean isHasInfors(String urlstr) {
26 SQLiteDatabase database = dbHelper.getReadableDatabase();
27 String sql = "select count(*) from download_info where url=?";
28 Cursor cursor = database.rawQuery(sql, new String[] { urlstr });
29 cursor.moveToFirst();
30 int count = cursor.getInt(0);
31 cursor.close();
32 return count == 0;
33 }
34
35 /**
36 * 保存 下载的具体信息
37 */
38 public void saveInfos(List<DownloadInfo> infos) {
39 SQLiteDatabase database = dbHelper.getWritableDatabase();
40 for (DownloadInfo info : infos) {
41 String sql = "insert into download_info(thread_id,start_pos, end_pos,compelete_size,url) values (?,?,?,?,?)";
42 Object[] bindArgs = { info.getThreadId(), info.getStartPos(),
43 info.getEndPos(), info.getCompeleteSize(), info.getUrl() };
44 database.execSQL(sql, bindArgs);
45 }
46 }
47
48 /**
49 * 得到下载具体信息
50 */
51 public List<DownloadInfo> getInfos(String urlstr) {
52 List<DownloadInfo> list = new ArrayList<DownloadInfo>();
53 SQLiteDatabase database = dbHelper.getReadableDatabase();
54 String sql = "select thread_id, start_pos, end_pos,compelete_size,url from download_info where url=?";
55 Cursor cursor = database.rawQuery(sql, new String[] { urlstr });
56 while (cursor.moveToNext()) {
57 DownloadInfo info = new DownloadInfo(cursor.getInt(0),
58 cursor.getInt(1), cursor.getInt(2), cursor.getInt(3),
59 cursor.getString(4));
60 list.add(info);
61 }
62 cursor.close();
63 return list;
64 }
65
66 /**
67 * 更新数据库中的下载信息
68 */
69 public void updataInfos(int threadId, int compeleteSize, String urlstr) {
70 SQLiteDatabase database = dbHelper.getReadableDatabase();
71 String sql = "update download_info set compelete_size=? where thread_id=? and url=?";
72 Object[] bindArgs = { compeleteSize, threadId, urlstr };
73 database.execSQL(sql, bindArgs);
74 }
75 /**
76 * 关闭数据库
77 */
78 public void closeDb() {
79 dbHelper.close();
80 }
81
82 /**
83 * 下载完成后删除数据库中的数据
84 */
85 public void delete(String url) {
86 SQLiteDatabase database = dbHelper.getReadableDatabase();
87 database.delete("download_info", "url=?", new String[] { url });
88 database.close();
89 }
90 }
下来就是要进行下载和暂停按钮的响应事件了。具体代码和解释如下。
MainActivity:
1 package cn.yj3g.AndroidDownload;
2
3 import java.util.ArrayList;
4 import java.util.HashMap;
5 import java.util.List;
6 import java.util.Map;
7 import android.app.ListActivity;
8 import android.os.Bundle;
9 import android.os.Handler;
10 import android.os.Message;
11 import android.view.View;
12 import android.widget.LinearLayout;
13 import android.widget.LinearLayout.LayoutParams;
14 import android.widget.ProgressBar;
15 import android.widget.SimpleAdapter;
16 import android.widget.TextView;
17 import android.widget.Toast;
18 import cn.yj3g.entity.LoadInfo;
19 import cn.yj3g.service.Downloader;
20
21 public class MainActivity extends ListActivity {
22 // 固定下载的资源路径,这里可以设置网络上的地址
23 private static final String URL = "http://192.168.1.105:8080/struts2_net/";
24 // 固定存放下载的音乐的路径:SD卡目录下
25 private static final String SD_PATH = "/mnt/sdcard/";
26 // 存放各个下载器
27 private Map<String, Downloader> downloaders = new HashMap<String, Downloader>();
28 // 存放与下载器对应的进度条
29 private Map<String, ProgressBar> ProgressBars = new HashMap<String, ProgressBar>();
30 /**
31 * 利用消息处理机制适时更新进度条
32 */
33 private Handler mHandler = new Handler() {
34 public void handleMessage(Message msg) {
35 if (msg.what == 1) {
36 String url = (String) msg.obj;
37 int length = msg.arg1;
38 ProgressBar bar = ProgressBars.get(url);
39 if (bar != null) {
40 // 设置进度条按读取的length长度更新
41 bar.incrementProgressBy(length);
42 if (bar.getProgress() == bar.getMax()) {
43 Toast.makeText(MainActivity.this, "下载完成!", 0).show();
44 // 下载完成后清除进度条并将map中的数据清空
45 LinearLayout layout = (LinearLayout) bar.getParent();
46 layout.removeView(bar);
47 ProgressBars.remove(url);
48 downloaders.get(url).delete(url);
49 downloaders.get(url).reset();
50 downloaders.remove(url);
51
52 }
53 }
54 }
55 }
56 };
57 @Override
58 public void onCreate(Bundle savedInstanceState) {
59 super.onCreate(savedInstanceState);
60 setContentView(R.layout.main);
61 showListView();
62 }
63 // 显示listView,这里可以随便添加音乐
64 private void showListView() {
65 List<Map<String, String>> data = new ArrayList<Map<String, String>>();
66 Map<String, String> map = new HashMap<String, String>();
67 map.put("name", "mm.mp3");
68 data.add(map);
69 map = new HashMap<String, String>();
70 map.put("name", "pp.mp3");
71 data.add(map);
72 map = new HashMap<String, String>();
73 map.put("name", "tt.mp3");
74 data.add(map);
75 map = new HashMap<String, String>();
76 map.put("name", "You.mp3");
77 data.add(map);
78 SimpleAdapter adapter = new SimpleAdapter(this, data, R.layout.list_item, new String[] { "name" },
79 new int[] { R.id.tv_resouce_name });
80 setListAdapter(adapter);
81 }
82 /**
83 * 响应开始下载按钮的点击事件
84 */
85 public void startDownload(View v) {
86 // 得到textView的内容
87 LinearLayout layout = (LinearLayout) v.getParent();
88 String musicName = ((TextView) layout.findViewById(R.id.tv_resouce_name)).getText().toString();
89 String urlstr = URL + musicName;
90 String localfile = SD_PATH + musicName;
91 //设置下载线程数为4,这里是我为了方便随便固定的
92 int threadcount = 4;
93 // 初始化一个downloader下载器
94 Downloader downloader = downloaders.get(urlstr);
95 if (downloader == null) {
96 downloader = new Downloader(urlstr, localfile, threadcount, this, mHandler);
97 downloaders.put(urlstr, downloader);
98 }
99 if (downloader.isdownloading())
100 return;
101 // 得到下载信息类的个数组成集合
102 LoadInfo loadInfo = downloader.getDownloaderInfors();
103 // 显示进度条
104 showProgress(loadInfo, urlstr, v);
105 // 调用方法开始下载
106 downloader.download();
107 }
108
109 /**
110 * 显示进度条
111 */
112 private void showProgress(LoadInfo loadInfo, String url, View v) {
113 ProgressBar bar = ProgressBars.get(url);
114 if (bar == null) {
115 bar = new ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal);
116 bar.setMax(loadInfo.getFileSize());
117 bar.setProgress(loadInfo.getComplete());
118 ProgressBars.put(url, bar);
119 LinearLayout.LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, 5);
120 ((LinearLayout) ((LinearLayout) v.getParent()).getParent()).addView(bar, params);
121 }
122 }
123 /**
124 * 响应暂停下载按钮的点击事件
125 */
126 public void pauseDownload(View v) {
127 LinearLayout layout = (LinearLayout) v.getParent();
128 String musicName = ((TextView) layout.findViewById(R.id.tv_resouce_name)).getText().toString();
129 String urlstr = URL + musicName;
130 downloaders.get(urlstr).pause();
131 }
132 }
最后我们要在清单文件中添加权限,一个是访问网络的权限,一个是往SD卡写数据的权限。代码如下:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
这样我们就实现了文件的断点续传下载功能。具体效果图如下:
http://www.cnblogs.com/zxl-jay/archive/2011/10/09/2204195.html