您的位置:首页 > 移动开发 > Android开发

Android使用AsyncTask实现可以断点续传的DownloadManager功能

2014-12-11 09:24 351 查看
http://www.it165.net/pro/html/201211/4210.html

最近做项目卡壳了,要做个Android的应用市场,其他方面都还好说,唯独这个下载管理算是给我难住了,究其原因,一是之前没有做过类似的功能,二是这个项目催的着实的急促,以至于都没什么时间能仔细研究这方面的内容,三是我这二把刀的基本功实在是不太扎实啊。不过好在经高人指点,再加上bing以及stackoverflow的帮助,好歹算是有些成果,下面就将这小小的成果分享一下,虽然是使用的AsyncTask来完成,但是个人觉得还是service要更靠谱些,不过那个得等有空儿再研究了。

AsyncTask是何物我就不再赘述了,度娘,谷哥,必应都会告诉你的,不过建议大家看看文章最后参考资料的第二个链接,写的还是非常详细的。我认为它实际上就是个简单的迷你的Handler,反正把一些异步操作扔给它以后,就只需要等着它执行完就齐活了。

那么怎么用这玩意儿实现一个下载管理的功能?大体的思路是这样的:
1.点击下载按钮以后,除了要让AsyncTask开始执行外,还要把下载的任务放到HashMap里面保存,这样做的好处就是能够在列表页进行管理,比如暂停、继续下载、取消。
2.下载管理页的列表,使用ScrollView,而非ListView。这样做的好处就是为了能方便的更新ProgressBar进度。

那咱先来说说启动下载任务。

view sourceprint?

01.
btnDownload.setOnClickListener(
new
OnClickListener() {

02.
public
void
onClick(View v) {

03.
String url =datas.get(position).get(
"url"
);

04.
AsyncasyncTask =
null
;
// 下载renwu

05.
boolean
isHas =
false
;

06.
// 判断当前要下载的这个连接是否已经正在进行,如果正在进行就阻止在此启动一个下载任务

07.
for
(String urlString : AppConstants.listUrl) {

08.
if
(url.equalsIgnoreCase(urlString)) {

09.
isHas =
true
;

10.
break
;

11.
}

12.
}

13.

14.
// 如果这个连接的下载任务还没有开始,就创建一个新的下载任务启动下载,并这个下载任务加到下载列表中

15.
if
(isHas ==
false
) {

16.
asyncTask =
new
Async(); 
// 创建新异步

17.
asyncTask.setDataMap(datas.get(position));

18.
asyncTask.setContext(context);

19.
AppConstants.mapTask.put(url,asyncTask);

20.
// 当调用AsyncTask的方法execute时,就会去自动调用doInBackground方法

21.
asyncTask.executeOnExecutor(Executors.newCachedThreadPool(),url);

22.
}

23.
}

24.
});


这里我为什么写asyncTask.executeOnExecutor(Executors.newCachedThreadPool(),url);而不是asyncTask.execute(url);呢?先卖个关子,回头咱再说。

下面来看看Async里都干了啥。

view sourceprint?

001.
package
com.test.muldownloadtest.task;

002.

003.
import
java.io.File;

004.
import
java.io.IOException;

005.
import
java.io.InputStream;

006.
import
java.io.RandomAccessFile;

007.
import
java.net.HttpURLConnection;

008.
import
java.net.MalformedURLException;

009.
import
java.net.URL;

010.
import
java.util.HashMap;

011.

012.
import
com.test.muldownloadtest.AppConstants;

013.
import
com.test.muldownloadtest.bean.DBHelper;

014.

015.
import
android.content.Context;

016.
import
android.database.Cursor;

017.
import
android.database.sqlite.SQLiteDatabase;

018.
import
android.os.AsyncTask;

019.
import
android.os.Environment;

020.
import
android.os.Handler;

021.
import
android.os.Message;

022.
import
android.widget.ListView;

023.
import
android.widget.ProgressBar;

024.
import
android.widget.TextView;

025.

026.
// AsyncTask<Params,Progress,Result>

027.
public
class
Async
extends
AsyncTask<String,Integer,String>{

028.

029.
/* 用于查询数据库 */

030.
private
DBHelper dbHelper;

031.

032.
// 下载的文件的map,也可以是实体Bean

033.
private
HashMap<String,String>dataMap = 
null
;

034.
private
Context context;

035.

036.
private
boolean
finished =
false
;

037.
private
boolean
paused =
false
;

038.

039.
private
int
curSize =
0
;

040.

041.
private
int
length =
0
;

042.

043.
private
AsyncstartTask = 
null
;

044.
private
boolean
isFirst =
true
;

045.

046.
private
String strUrl;

047.

048.
@Override

049.
protected
String doInBackground=\
'#\'
" /span>

050.

051.
dbHelper =
new
DBHelper(context);

052.

053.
strUrl = Params[
0
];

054.
String name = dataMap.get(
"name"
);

055.
String appid = dataMap.get(
"appid"
);

056.
int
startPosition =
0
;

057.

058.
URL url =
null
;

059.
HttpURLConnection httpURLConnection =
null
;

060.
InputStream inputStream =
null
;

061.
RandomAccessFile outputStream =
null
;

062.
// 文件保存路径

063.
String path = Environment.getExternalStorageDirectory().getPath();

064.
// 文件名

065.
String fileName = strUrl.substring(strUrl.lastIndexOf(
'/'
));

066.
try
{

067.
length =getContentLength(strUrl);

068.
startPosition =getDownloadedLength(strUrl,name);

069.

070.
/** 判断是否是第一次启动任务,true则保存数据到数据库并下载,

071.
*  false则更新数据库中的数据 start

072.
*/

073.
boolean
isHas =
false
;

074.
for
(String urlString : AppConstants.listUrl) {

075.
if
(strUrl.equalsIgnoreCase(urlString)) {

076.
isHas =
true
;

077.
break
;

078.
}

079.
}

080.
if
(
false
== isHas) {

081.
saveDownloading(name,appid,strUrl,path,fileName,startPosition,length,
1
);

082.
}

083.
else
if
(
true
== isHas) {

084.
updateDownloading(curSize,name,strUrl);

085.
}

086.
/** 判断是否是第一次启动任务,true则保存数据到数据库并下载,

087.
*  false则更新数据库中的数据 end

088.
*/

089.

090.
// 设置断点续传的开始位置

091.
url =
new
URL=\
'#\'
" /span>

092.
httpURLConnection = (HttpURLConnection)url.openConnection();

093.
httpURLConnection.setAllowUserInteraction(
true
);

094.
httpURLConnection.setRequestMethod(
"GET"
);

095.
httpURLConnection.setReadTimeout(
5000
);

096.
httpURLConnection.setRequestProperty(
"User-Agent"
,
"NetFox"
);

097.
httpURLConnection.setRequestProperty(
"Range"
,
"bytes="
+ startPosition +
"-"
);

098.
inputStream = httpURLConnection.getInputStream();

099.

100.
File outFile =
new
File(path+fileName);

101.
// 使用java中的RandomAccessFile 对文件进行随机读写操作

102.
outputStream =
new
RandomAccessFile(outFile,
"rw"
);

103.
// 设置开始写文件的位置

104.
outputStream.seek(startPosition);

105.

106.
byte
[] buf =
new
byte
[
1024
*
100
];

107.
int
read =
0
;

108.
curSize =startPosition;

109.
while
(
false
== finished) {

110.
while
(
true
== paused) {

111.
// 暂停下载

112.
Thread.sleep(
500
);

113.
}

114.
read =inputStream.read(buf);

115.
if
(read==-
1
) {

116.
break
;

117.
}

118.
outputStream.write(buf,
0
,read);

119.
curSize =curSize+read;

120.
// 当调用这个方法的时候会自动去调用onProgressUpdate方法,传递下载进度

121.
publishProgress((
int
)(curSize*
100
.0f/length));

122.
if
(curSize == length) {

123.
break
;

124.
}

125.
Thread.sleep(
500
);

126.
updateDownloading(curSize,name,strUrl);

127.
}

128.
if
(
false
== finished) {

129.
finished =
true
;

130.
deleteDownloading(strUrl,name);

131.
}

132.
inputStream.close();

133.
outputStream.close();

134.
httpURLConnection.disconnect();

135.
}

136.
catch
(MalformedURLException e) {

137.
e.printStackTrace();

138.
}

139.
catch
(IOException e) {

140.
e.printStackTrace();

141.
}

142.
catch
(InterruptedException e) {

143.
e.printStackTrace();

144.
}

145.
finally
{

146.
finished =
true
;

147.
deleteDownloading(strUrl,name);

148.
if
(inputStream!=
null
) {

149.
try
{

150.
inputStream.close();

151.
if
(outputStream!=
null
) {

152.
outputStream.close();

153.
}

154.
if
(httpURLConnection!=
null
) {

155.
httpURLConnection.disconnect();

156.
}

157.
}

158.
catch
(IOException e) {

159.
e.printStackTrace();

160.
}

161.
}

162.
}

163.
// 这里的返回值将会被作为onPostExecute方法的传入参数

164.
return
strUrl;

165.
}

166.

167.
/**

168.
* 暂停下载

169.
*/

170.
public
void
pause() {

171.
paused =
true
;

172.
}

173.

174.
/**

175.
* 继续下载

176.
*/

177.
public
void
continued() {

178.
paused =
false
;

179.
}

180.

181.
/**

182.
* 停止下载

183.
*/

184.
@Override

185.
protected
void
onCancelled() {

186.
finished =
true
;

187.
deleteDownloading(dataMap.get(
"url"
),dataMap.get(
"name"
));

188.
super
.onCancelled();

189.
}

190.

191.
/**

192.
* 当一个下载任务成功下载完成的时候回来调用这个方法,

193.
* 这里的result参数就是doInBackground方法的返回值

194.
*/

195.
@Override

196.
protected
void
onPostExecute(String result) {

197.
try
{

198.
String name = dataMap.get(
"name"
);

199.
System.out.println(
"name===="
+name);

200.
// 判断当前结束的这个任务在任务列表中是否还存在,如果存在就移除

201.
if
(AppConstants.mapTask.containsKey(result)) {

202.
if
(AppConstants.mapTask.get(result) !=
null
) {

203.
finished =
true
;

204.
deleteDownloading(result,name);

205.
}

206.
}

207.
}

208.
catch
(NumberFormatException e) {

209.
e.printStackTrace();

210.
}

211.
super
.onPostExecute(result);

212.
}

213.

214.
@Override

215.
protected
void
onPreExecute() {

216.
super
.onPreExecute();

217.
}

218.

219.
/**

220.
* 更新下载进度,当publishProgress方法被调用的时候就会自动来调用这个方法

221.
*/

222.
@Override

223.
protected
void
onProgressUpdate(Integer... values) {

224.
super
.onProgressUpdate(values);

225.
}

226.

227.

228.
/**

229.
* 获取要下载内容的长度

230.
* @param urlString

231.
* @return

232.
*/

233.
private
int
getContentLength(String urlString){

234.
try
{

235.
URL url =
new
URL(urlString);

236.
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

237.
return
connection.getContentLength();

238.
}

239.
catch
(MalformedURLException e) {

240.
e.printStackTrace();

241.
}

242.
catch
(IOException e) {

243.
e.printStackTrace();

244.
}

245.
return
0
;

246.
}

247.

248.
/**

249.
* 从数据库获取已经下载的长度

250.
* @param url

251.
* @param name  www.it165.net

252.
* @return

253.
*/

254.
private
int
getDownloadedLength(String url,String name) {

255.
int
downloadedLength =
0
;

256.
SQLiteDatabase db = dbHelper.getReadableDatabase(); 

257.
String sql =
"SELECT downloadBytes FROM fileDownloading WHERE downloadUrl=? AND name=?"
; 

258.
Cursor cursor = db.rawQuery(sql,
new
String[] { url,name }); 

259.
while
(cursor.moveToNext()) {

260.
downloadedLength =cursor.getInt(
0
);

261.
}

262.
db.close(); 

263.
return
downloadedLength; 

264.
}

265.

266.
/**

267.
* 保存下载的数据

268.
* @param name

269.
* @param appid

270.
* @param url

271.
* @param downloadedLength

272.
*/

273.
private
void
saveDownloading(String name,String appid,String url,String savePath,String fileName,
int
downloadBytes,
int
totalBytes,
int
status) {

274.
SQLiteDatabase db = dbHelper.getWritableDatabase(); 

275.
try
{

276.
db.beginTransaction(); 

277.
String sql =
"INSERT INTO fileDownloading(name,appid,downloadUrl,savePath,fileName,downloadBytes,totalBytes,downloadStatus) "
+

278.
"values(?,?,?,?,?,?,?,?)"
; 

279.
db.execSQL(sql,
new
Object[]{ name,appid,url,savePath,fileName,downloadBytes,totalBytes,status}); 

280.
db.setTransactionSuccessful();

281.
boolean
isHas =
false
;

282.
// 判断当前要下载的这个连接是否已经正在进行,如果正在进行就阻止在此启动一个下载任务

283.
for
(String urlString : AppConstants.listUrl) {

284.
if
(url.equalsIgnoreCase(urlString)) {

285.
isHas =
true
;

286.
break
;

287.
}

288.
}

289.
if
(
false
== isHas) {

290.
AppConstants.listUrl.add(url);

291.
}

292.
if
(
false
== isFirst) {

293.
AppConstants.mapTask.put(url,startTask);

294.
}

295.
}

296.
finally
{

297.
db.endTransaction(); 

298.
db.close(); 

299.
}

300.
}

301.

302.
/**

303.
* 更新下载数据

304.
* @param cursize

305.
* @param name

306.
* @param url

307.
*/

308.
private
void
updateDownloading(
int
cursize,String name,String url) {

309.
SQLiteDatabase db = dbHelper.getWritableDatabase(); 

310.
try
{

311.
db.beginTransaction(); 

312.
String sql =
"UPDATE fileDownloading SET downloadBytes=? WHERE name=? AND downloadUrl=?"
; 

313.
db.execSQL(sql,
new
String[] { cursize +
""
,name,url }); 

314.
db.setTransactionSuccessful(); 

315.
}
finally
{

316.
db.endTransaction(); 

317.
db.close(); 

318.
}

319.
}

320.

321.
/**

322.
* 删除下载数据

323.
* @param url

324.
* @param name

325.
*/

326.
private
void
deleteDownloading(String url,String name) {

327.
if
(
true
== finished) {

328.
// 删除保存的URL。这个listurl主要是为了在列表中按添加下载任务的顺序进行显示

329.
for
(
int
i =
0
;i <AppConstants.listUrl.size();i++) {

330.
if
(url.equalsIgnoreCase(AppConstants.listUrl.get(i))) {

331.
AppConstants.listUrl.remove(i);

332.
}

333.
}

334.
// 删除已经完成的下载任务

335.
if
(AppConstants.mapTask.containsKey(url)) {

336.
AppConstants.mapTask.remove(url);

337.
}

338.
}

339.
SQLiteDatabase db = dbHelper.getWritableDatabase(); 

340.
String sql =
"DELETE FROM fileDownloading WHERE downloadUrl=? AND name=?"
; 

341.
db.execSQL(sql,
new
Object[] { url,name }); 

342.
db.close(); 

343.
}

344.

345.
public
void
setDataMap(HashMap<String,String>dataMap) {

346.
this
.dataMap = dataMap;

347.
}

348.

349.
public
HashMap<String,String>getDataMap() {

350.
return
dataMap;

351.
}

352.

353.
public
boolean
isPaused() {

354.
return
paused;

355.
}

356.

357.
public
int
getCurSize() {

358.
return
curSize;

359.
}

360.

361.
public
int
getLength() {

362.
return
length;

363.
}

364.

365.
public
void
setContext(Context context) {

366.
this
.context = context;

367.
}

368.

369.
public
Context getContext() {

370.
return
context;

371.
}

372.

373.
public
void
setListView(ListView listView) {

374.
this
.listView = listView;

375.
}

376.
}


好了,下载任务已经启动了,接下来就该开始管理了。先说说之前错误的思路,估计大多数的网友可能跟我一样,一想到列表首先想到的就是ListView,这多简单啊,放一个ListView,继承BaseAdapter写个自己的Adapter,然后一展现,完事了,so easy。我也是这么想的,这省事啊,用了以后才发现,确实省事,不过更新ProgressBar的时候可是给我愁死了,无论怎么着都不能正常更新ProgressBar。在这个地方钻了一周的牛角尖,昨儿个突然灵光乍现,干嘛给自己挖个坑,谁说列表就非得用ListView了,我自己写个列表不就得了。 先来看看列表页都有些什么

view sourceprint?

01.
package
com.test.muldownloadtest;

02.

03.
import
android.app.Activity;

04.
import
android.os.Bundle;

05.
import
android.view.View;

06.
import
android.view.View.OnClickListener;

07.
import
android.view.Window;

08.
import
android.widget.Button;

09.
import
android.widget.LinearLayout;

10.
import
android.widget.ScrollView;

11.

12.
public
class
DownloadManagerActivity
extends
Activity
implements
OnClickListener {

13.

14.
private
ScrollView scDownload;

15.
private
LinearLayout llDownloadLayout;

16.

17.
@Override

18.
protected
void
onCreate(Bundle savedInstanceState) {

19.
super
.onCreate(savedInstanceState);

20.

21.
this
.requestWindowFeature(Window.FEATURE_NO_TITLE);

22.

23.
this
.setContentView(R.layout.download_manager_layout);

24.

25.
initView();

26.
}

27.

28.
@Override

29.
protected
void
onResume() {

30.
super
.onResume();

31.

32.
refreshItemView();

33.
}

34.

35.
private
void
initView(){

36.

37.
Button btnGoback = (Button)
this
.findViewById(R.id.btnGoback);

38.
btnGoback.setOnClickListener(
this
);

39.

40.
scDownload = (ScrollView)
this
.findViewById(R.id.svDownload);

41.
scDownload.setSmoothScrollingEnabled(
true
);

42.

43.
llDownloadLayout = (LinearLayout)
this
.findViewById(R.id.llDownloadLyout);

44.
}

45.

46.
/**

47.
* 列表中的每一项

48.
*/

49.
private
void
refreshItemView(){

50.
for
(
int
i =
0
;i <AppConstants.listUrl.size();i++) {

51.
DownloadItemView downloadItemView =
new
DownloadItemView(
this
,AppConstants.listUrl.get(i),i);

52.
downloadItemView.setId(i);

53.
downloadItemView.setTag(
"downloadItemView_"
+i);

54.
llDownloadLayout.addView(downloadItemView);

55.
}

56.
}

57.

58.
public
void
onClick(View v) {

59.
switch
(v.getId()) {

60.
case
R.id.btnExit:

61.
this
.finish();

62.
break
;

63.
case
R.id.btnGoback:

64.
this
.finish();

65.
break
;

66.
default
:

67.
break
;

68.
}

69.
}

70.
}


很简单,一个ScrollView,在这个ScrollView中在内嵌一个LinearLayout,用这个LinearLayout来存储每一个列表项。其实列表项很简单,最基本只要三个控件就行了——ProgressBar、TextView、Button。一个是进度条,一个显示百分比,一个用来暂停/继续,偷个懒,这个布局文件就不列出来了,咱就看看这个Button都干嘛了。

view sourceprint?

01.
public
void
onClick(View v) {

02.
switch
(v.getId()) {

03.
case
R.id.btnPauseOrResume:

04.
String btnTag = (String) btnPauseOrResume.getTag();

05.
if
(btnTag.equals(
"pause"
)) {

06.
resumeDownload();

07.
}

08.
else
if
(btnTag.equals(
"resume"
)) {

09.
pauseDownload();

10.
}

11.
break
;

12.
default
:

13.
break
;

14.
}

15.
}

16.

17.
private
void
pauseDownload(){

18.
btnPauseOrResume.setTag(
"pause"
);

19.
btnPauseOrResume.setText(R.string.download_resume);

20.

21.
AsyncpauseTask = 
null
;

22.
// 判断当前被停止的这个任务在任务列表中是否存在,如果存在就暂停

23.
if
(AppConstants.linkedMapDownloading.containsKey(urlString)) {

24.
pauseTask = AppConstants.linkedMapDownloading.get(urlString);

25.
if
(pauseTask !=
null
) {

26.
pauseTask.pause();

27.
}

28.
}

29.
}

30.

31.
private
void
resumeDownload(){

32.
btnPauseOrResume.setTag(
"resume"
);

33.
btnPauseOrResume.setText(R.string.download_pause);

34.

35.
AsynccontinueTask = 
null
;

36.
// 判断当前被停止的这个任务在任务列表中是否存在,如果存在就继续

37.
if
(AppConstants.linkedMapDownloading.containsKey(urlString)) {

38.
continueTask = AppConstants.linkedMapDownloading.get(urlString);

39.
if
(continueTask !=
null
) {

40.
continueTask.continued();

41.
}

42.
}

43.
handler.postDelayed(runnable,
1000
);

44.
}


简单吧,就是判断一下当前按钮的Tag,然后根据Tag的值,来判断是继续下载,还是暂停下载。而这个暂停还是继续,其实只是修改下Async中的暂停标记的值,即paused是true还是false。 到此,核心功能展示完毕。附效果图一张

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: